
Adapter Pattern: The Universal Travel Plug
Connecting incompatible interfaces. How to make old code work with new code without rewriting.

Connecting incompatible interfaces. How to make old code work with new code without rewriting.
Why does my server crash? OS's desperate struggle to manage limited memory. War against Fragmentation.

A deep dive into Robert C. Martin's Clean Architecture. Learn how to decouple your business logic from frameworks, databases, and UI using Entities, Use Cases, and the Dependency Rule. Includes Screaming Architecture and Testing strategies.

Two ways to escape a maze. Spread out wide (BFS) or dig deep (DFS)? Who finds the shortest path?

Fast by name. Partitioning around a Pivot. Why is it the standard library choice despite O(N²) worst case?

I was on a trip to the US. I arrived at my hotel room, ready to charge my Korean phone. I pulled out my charger and approached the wall socket.
Then I froze. The plug didn't fit.
Korea uses 220v two-prong plugs. The US uses 110v three-prong sockets. Physically incompatible.
Panicking, I went down to the hotel lobby and asked, "Do you have an adapter?" The receptionist smiled and handed me a Travel Adapter.
That's when it hit me. "This is the Adapter Pattern in software."
The Adapter Pattern is a converter that connects incompatible interfaces.
In code terms:
I was upgrading the payment system for an e-commerce platform I ran.
A decade ago, an outsourced vendor built a PaymentService class.
class OldPaymentService {
void payCash(int cents) {
// Payment in cents
System.out.println("Payment: " + cents + " cents");
}
}
This code was embedded everywhere.
Order processing, refund logic, settlement systems... at least 30 files called payCash.
Our Payment Gateway (PG) company offered a new API. 30% lower fees. International payment support.
The catch? Different interface:
interface ModernPaymentGateway {
void processPayment(double dollars);
}
payCash(int cents) - Cents unit, int type.processPayment(double dollars) - Dollar unit, double type."Let's just open all 30 files and replace payCash(cents) with processPayment(dollars)."
But modifying 30 files meant:
And if we later adopt another payment system? Modify those 30 files again.
"Don't touch the existing code. Just insert a translation layer (Adapter)."
class PaymentAdapter implements ModernPaymentGateway {
private OldPaymentService oldService;
public PaymentAdapter(OldPaymentService old) {
this.oldService = old;
}
@Override
public void processPayment(double dollars) {
// 1. Convert dollars to cents
int cents = (int) (dollars * 100);
// 2. Delegate to old system
oldService.payCash(cents);
}
}
Now I don't touch those 30 files. Just plug this adapter into the new API.
// Before: Old code
OldPaymentService payment = new OldPaymentService();
payment.payCash(5000); // $50 = 5000 cents
// After: Adapter applied
ModernPaymentGateway payment = new PaymentAdapter(new OldPaymentService());
payment.processPayment(50.00); // $50
OldPaymentService class: Not a single line modified.
Touching verified code introduces new bugs.
Adapters follow the SOLID principle: "Open for extension, closed for modification."
The existing code (OldPaymentService) has been battle-tested for 10 years.
Only the adapter needs testing.
@Test
void testAdapter() {
PaymentAdapter adapter = new PaymentAdapter(new OldPaymentService());
adapter.processPayment(50.00);
// Output: "Payment: 5000 cents"
// ✅ Just verify dollar→cent conversion
}
If 6 months later we add another payment provider (e.g., Stripe)? Just create one more adapter:
class StripeAdapter implements ModernPaymentGateway {
private StripeAPI stripe;
@Override
public void processPayment(double dollars) {
stripe.charge(dollars);
}
}
The old code (OldPaymentService, 30 files)? Still untouched.
Another project where I used the Adapter Pattern.
Our internal company system used XML format for data exchange (2010s legacy).
class LegacySystem {
String getDataAsXML() {
return "<user><name>John</name></user>";
}
}
But our new web app only understood JSON.
// React component expects JSON
fetch('/api/user')
.then(res => res.json()) // Expects JSON
.then(data => console.log(data.name));
class XmlToJsonAdapter {
private LegacySystem legacy;
public XmlToJsonAdapter(LegacySystem legacy) {
this.legacy = legacy;
}
public String getDataAsJSON() {
String xml = legacy.getDataAsXML();
// Parse XML, convert to JSON
return "{\"name\": \"John\"}";
}
}
At the API layer:
@GetMapping("/api/user")
public String getUser() {
XmlToJsonAdapter adapter = new XmlToJsonAdapter(new LegacySystem());
return adapter.getDataAsJSON(); // Returns JSON
}
Now React gets JSON, and the legacy system (LegacySystem) remains unchanged.
Both "wrap an existing object", but:
| Pattern | Purpose | Example |
|---|---|---|
| Adapter | Interface conversion. Make incompatible things compatible. | 110v → 220v converter |
| Decorator | Add功能. Add new features to existing object. | Add milk to coffee |
I made this mistake. "Adapters are great!" So I wrapped everything.
// ❌ Unnecessary adapter
class StringAdapter {
private String str;
public String getString() { return str; }
}
This is just a wrapper. If there's no conversion, it's not an adapter.
Actual code from my Next.js blog.
Supabase returns dates in ISO 8601 format (2025-06-03T00:00:00Z).
But my UI component only understands YYYY-MM-DD.
class DateAdapter {
constructor(private isoDate: string) {}
getFormattedDate(): string {
return this.isoDate.split('T')[0]; // "2025-06-03"
}
}
// Usage
const post = await supabase.from('posts').select('created_at').single();
const adapter = new DateAdapter(post.created_at);
console.log(adapter.getFormattedDate()); // "2025-06-03"
Supabase code (created_at format) untouched. My UI gets the format it wants.
In design patterns theory (GoF), there are actually two types of Adapters.
This is what I showed above. It uses Composition. The adapter holds an instance of the legacy class.
class Adapter implements Target {
private Legacy legacy; // Composition
public Adapter(Legacy legacy) {
this.legacy = legacy;
}
}
This uses Multiple Inheritance (C++ style). In Java, it uses extends.
// Logic: "I am both a Target AND a Legacy"
class Adapter extends Legacy implements Target {
@Override
public void request() {
super.specificRequest();
}
}
My Verdict: Always use Object Adapter. Composition over Inheritance!
The Adapter Pattern is the ultimate cheat code for LSP. LSP says: "Subtypes must be substitutable for their base types."
If you have a LegacyService that behaves wildly differently from NewService, you can't just inherit.
The Adapter acts as a Translator that forces the Legacy code to behave like a respectful subtype of the new interface.
It smooths out the rough edges so the rest of your system can treat everything uniformly (Polymorphism).
| Feature | Description |
|---|---|
| Single Responsibility | ✅ Separates the interface conversion logic from business logic. |
| Open/Closed | ✅ Adds new behavior without changing old code. |
| Complexity | ❌ Increases the number of classes. "Why do we have so many files?" |
| Performance | ⚠️ Slight overhead (method forwarding), but negligible in modern CPUs. |
If you are a Java developer, you use adapters every day. JDBC (Java Database Connectivity) is one giant Adapter Pattern.
java.sql.Connection, java.sql.ResultSet.You write code against java.sql.* interfaces.
You never touch the Oracle proprietary API directly.
The Driver adapts the Oracle API to the Standard SQL Interface.
This allows you to switch databases by changing one configuration line (Driver Class Name).
In Python, we don't strictly need interfaces, but the pattern is still useful.
class Dog:
def bark(self):
return "Woof!"
class Cat:
def meow(self):
return "Meow!"
class CatAdapter:
def __init__(self, cat):
self.cat = cat
def bark(self):
# Translate meow to bark
return self.cat.meow()
def make_noise(animal):
print(animal.bark())
dog = Dog()
cat = Cat()
adapted_cat = CatAdapter(cat)
make_noise(dog) # Woof!
make_noise(adapted_cat) # Meow! (But called via bark method)
Even in dynamic languages, adapting method names meow() -> bark() allows polymorphism.
Q: Is Adapter same as Bridge Pattern? A: No.
Q: Can I use it to wrap multiple classes? A: Yes! That's called a Facade sometimes, but if it respects an interface, it's an Adapter. You can adapt a whole subsystem to a single interface.
When I first learned coding, I thought "If it's wrong, fix it!" But in production, I learned: "If it works, don't touch it."
The Adapter Pattern is this philosophy implemented.
Just like packing a travel adapter for your trip, pack an adapter in your codebase. It saves you from rewriting the world.
"The best refactoring is often no refactoring at all. Just adapt."