Java 3 min read

The Adapter Design Pattern: Making Incompatible Code Work Together

Master the Adapter Design Pattern in Java. Learn how to integrate legacy code and 3rd-party libraries using the 'Travel Adapter' concept.

MR

Moshiour Rahman

Advertisement

The Problem: The Square Peg in a Round Hole

Imagine you have a modern application that expects data in JSON format.

interface ModernClient {
    void process(String jsonData);
}

But you need to integrate a legacy 3rd-party library (maybe an old banking system) that only outputs XML.

// You can't change this code (it's a library)
class LegacyBankSystem {
    public String getAccountDataXML() {
        return "<account><id>123</id></account>";
    }
}

The interfaces don’t match. You can’t rewrite the legacy library, and you don’t want to pollute your modern app with XML parsing logic everywhere.

The Solution: The Adapter Pattern

The Adapter Pattern allows objects with incompatible interfaces to collaborate. It acts as a bridge (or wrapper) between two objects.

Real-Life Analogy: The Travel Adapter 🔌

  • You (The Client): Have a US laptop plug (Type A).
  • The Wall (The Service): Is a European socket (Type C).
  • The Adapter: A small device that takes your US plug and fits it into the European socket.

It doesn’t change the electricity; it just adapts the interface.

Visualizing the Pattern

Adapter Pattern

Implementation

1. The Target Interface (What your app expects)

public interface ModernPaymentGateway {
    void pay(String currency, double amount);
}

2. The Adaptee (The incompatible 3rd party)

// Legacy code we can't change
public class LegacyPayPal {
    // Note different method name and parameter order
    public void sendPayment(double amount, String currencyCode) {
        System.out.println("Processing PayPal: " + amount + " " + currencyCode);
    }
}

3. The Adapter (The Bridge)

public class PayPalAdapter implements ModernPaymentGateway {
    
    private final LegacyPayPal output;

    public PayPalAdapter(LegacyPayPal output) {
        this.output = output;
    }

    @Override
    public void pay(String currency, double amount) {
        // 1. Convert input if necessary (e.g., currency codes)
        // 2. Delegate to the legacy method (Adapt the call)
        output.sendPayment(amount, currency);
    }
}

Usage

public class Shop {
    private ModernPaymentGateway gateway;

    public Shop(ModernPaymentGateway gateway) {
        this.gateway = gateway;
    }

    public void checkout() {
        // The shop only knows about ModernPaymentGateway
        // It doesn't know it's actually using LegacyPayPal underneath
        gateway.pay("USD", 50.00); 
    }
}

// In your setup code:
LegacyPayPal oldSystem = new LegacyPayPal();
ModernPaymentGateway adapter = new PayPalAdapter(oldSystem);
Shop shop = new Shop(adapter);
shop.checkout();

In The Wild (Real World Examples)

1. java.util.Arrays.asList()

This adapts an Array (Adaptee) to the List interface (Target).

String[] array = {"A", "B"};
List<String> list = Arrays.asList(array); 

2. InputStreamReader

Adapts an InputStream (byte-oriented) to a Reader (character-oriented). It acts as a bridge between byte streams and character streams.

InputStream stream = new FileInputStream("file.txt");
Reader reader = new InputStreamReader(stream); // Adapter!

Cheat Sheet

FeatureDetails
CategoryStructural
Problem SolvedIncompatible interfaces, integrations
Key implementationClass implements Target and wraps Adaptee
ProsSingle Responsibility Principle (Data conversion logic lives in adapter), allows reuse of legacy code
ConsAdds complexity of raw data conversion

Tips to Remember 🧠

  • “Wrapper”: The Adapter pattern is also called “Wrapper”.
  • Legacy Code: Whenever you touch legacy code or 3rd party libraries, think Adapter. Isolate the ugliness inside the Adapter so your main code stays clean.

Advertisement

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.