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.
Moshiour Rahman
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

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
| Feature | Details |
|---|---|
| Category | Structural |
| Problem Solved | Incompatible interfaces, integrations |
| Key implementation | Class implements Target and wraps Adaptee |
| Pros | Single Responsibility Principle (Data conversion logic lives in adapter), allows reuse of legacy code |
| Cons | Adds 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.
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
The Visitor Design Pattern: Add Operations Without Modifying Classes
Master the Visitor Pattern in Java with practical examples. Add new operations to object structures without modifying existing classes using double dispatch.
JavaThe Template Method Pattern: The Recipe for Success
Master the Template Method Pattern in Java. Learn how to define the skeleton of an algorithm in a superclass but let subclasses override specific steps.
JavaThe Strategy Design Pattern: Kill the If-Else Statements
Master the Strategy Pattern in Java. Learn how to replace complex if-else logic with interchangeable algorithms and follow the Open/Closed Principle.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.