Convert the interface of a class into another interface that clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
The Adapter pattern is a structural design pattern that acts as a bridge between two incompatible interfaces. It wraps an existing class with a new interface so that it becomes compatible with the client's expectations — without changing either the client or the existing class.
Think of a travel power adapter: your Indian charger (two round pins) doesn't fit a UK socket (three rectangular pins). The adapter plugs into the UK socket on one side and accepts your Indian charger on the other — neither the socket nor the charger changes, but they can now work together.
In software, this is exactly what happens when you integrate a third-party library, a legacy system, or any class whose interface doesn't match what your code expects. Instead of rewriting either side, you write a thin adapter in between.
Key Idea: Make incompatible interfaces compatible by introducing a translator (adapter) between them — without modifying either the client or the existing class.
The interface that the client already uses and expects. All classes that the client works with must conform to this interface. The adapter must implement this interface so the client can use it transparently.
The existing class or library that has the functionality we want, but with an incompatible interface. We cannot or do not want to modify this class — it could be a third-party SDK, a legacy system, or a class from another team.
The translation layer. It implements the Target interface and holds a reference to an Adaptee object. It translates every Target interface call into the corresponding Adaptee call, handling any data conversion or method renaming needed.
The existing code that uses the Target interface. The client is completely unaware of the adapter or the adaptee — it simply works with the Target interface as it always has.
Your application is built around a specific interface. A new library, service, or legacy module does exactly what you need — but its API looks completely different. Without Adapter, your only options are:
// Problem: RazorpaySDK has a different interface
// Our app calls: processor.processPayment(orderId, amount, currency)
// Razorpay needs: sdk.initiateCharge(amount, ref, currency)
// Without Adapter: conversion code leaks everywhere
class CheckoutService {
void checkout(String orderId, double amount) {
// Forced to know Razorpay's API details here — wrong!
razorpaySDK.initiateCharge(amount, orderId, "INR");
// Every caller needs to know this translation. Brittle!
}
}// Adapter centralises the translation in one place
class RazorpayAdapter implements PaymentProcessor {
void processPayment(String orderId, double amount, String currency) {
razorpaySDK.initiateCharge(amount, orderId, currency); // translated
}
}
// Client code stays clean and unchanged
processor.processPayment(orderId, amount, "INR"); // works with any gatewayDetermine the interface your client already uses. The adapter must implement this so the client needs zero changes.
The existing class or library you want to use. Understand its API — method names, parameters, return types.
Implement the Target interface. Hold a reference to the adaptee. Translate each Target method call into the appropriate adaptee call.
Pass the adapter wherever the client expects the Target interface. The client calls the Target API; the adapter translates to the adaptee behind the scenes.
Translation Flow
There are two ways to implement the Adapter pattern — the approach you choose depends on your language and situation:
Uses composition — the adapter holds an instance of the adaptee as a field and delegates calls to it. This is the more common and flexible approach.
class MyAdapter implements Target {
private Adaptee adaptee; // composition
MyAdapter(Adaptee a) { this.adaptee = a; }
void request() { adaptee.specificRequest(); }
}✓ Works in any language
✓ Can adapt multiple adaptees
✓ Can override adaptee behaviour
Uses multiple inheritance — the adapter extends both the target and the adaptee simultaneously. Only possible in languages that support multiple inheritance (C++, Python via mixins).
// Python example (multiple inheritance)
class MyAdapter(Target, Adaptee):
def request(self):
self.specific_request() # from Adaptee✓ No extra object needed
✗ Not possible in Java (single inheritance)
✗ Tightly couples adapter to both classes
Recommendation: Prefer the Object Adapter. It is more flexible, works in all languages, and aligns with the principle of favouring composition over inheritance.
// Adapter Pattern — Payment Gateway Integration
// ─── Existing client interface ───────────────────────────────
// This is what our application already uses everywhere
interface PaymentProcessor {
void processPayment(String orderId, double amount, String currency);
boolean refundPayment(String transactionId);
}
// ─── Our existing implementation ─────────────────────────────
class StripeProcessor implements PaymentProcessor {
@Override
public void processPayment(String orderId, double amount, String currency) {
System.out.println("[Stripe] Processing ₹" + amount + " for order " + orderId);
}
@Override
public boolean refundPayment(String transactionId) {
System.out.println("[Stripe] Refunding transaction: " + transactionId);
return true;
}
}
// ─── Incompatible third-party library ─────────────────────────
// Razorpay has a completely different interface — we cannot change it
class RazorpaySDK {
public void initiateCharge(double rupees, String ref, String curr) {
System.out.println("[Razorpay SDK] Charging ₹" + rupees
+ " | Ref: " + ref + " | Currency: " + curr);
}
public String cancelCharge(String paymentId) {
System.out.println("[Razorpay SDK] Cancelling payment: " + paymentId);
return "CANCELLED";
}
}
// ─── Adapter ──────────────────────────────────────────────────
// Wraps RazorpaySDK and makes it look like a PaymentProcessor
class RazorpayAdapter implements PaymentProcessor {
private RazorpaySDK razorpay; // The adaptee
public RazorpayAdapter(RazorpaySDK razorpay) {
this.razorpay = razorpay;
}
@Override
public void processPayment(String orderId, double amount, String currency) {
// Translate our interface call → Razorpay SDK call
razorpay.initiateCharge(amount, orderId, currency);
}
@Override
public boolean refundPayment(String transactionId) {
// Translate + convert return type
String result = razorpay.cancelCharge(transactionId);
return result.equals("CANCELLED");
}
}
// ─── Client code ──────────────────────────────────────────────
// Client only knows PaymentProcessor — doesn't care which gateway
public class CheckoutService {
private PaymentProcessor processor;
public CheckoutService(PaymentProcessor processor) {
this.processor = processor;
}
public void checkout(String orderId, double amount) {
processor.processPayment(orderId, amount, "INR");
}
public static void main(String[] args) {
// Use Stripe directly
CheckoutService stripeCheckout = new CheckoutService(new StripeProcessor());
stripeCheckout.checkout("ORD-001", 1500.00);
// Plug in Razorpay via adapter — zero changes to CheckoutService!
RazorpaySDK razorpaySDK = new RazorpaySDK();
CheckoutService razorpayCheckout = new CheckoutService(
new RazorpayAdapter(razorpaySDK)
);
razorpayCheckout.checkout("ORD-002", 2200.00);
// Output:
// [Stripe] Processing ₹1500.0 for order ORD-001
// [Razorpay SDK] Charging ₹2200.0 | Ref: ORD-002 | Currency: INR
}
}When you want to use an external SDK or library but its interface doesn't match what your application already expects.
RazorpayAdapter, StripeAdapter, TwilioAdapterWhen connecting old systems with new code — you can't rewrite the legacy system, so an adapter bridges the gap.
LegacyDBAdapter, OldAPIAdapter, SOAPToRESTAdapterWhen you want to support multiple providers (payment gateways, cloud storage, SMS services) behind a single unified interface.
S3Adapter, GCSAdapter, AzureBlobAdapterWhen two systems exchange data in different formats — XML vs JSON, CSV vs database rows, metric vs imperial units.
XMLToJSONAdapter, CSVToDBAdapter, UnitConverterWhen adapting a real service to a mock interface during testing — your tests use the same interface, the adapter decides which backend to use.
MockPaymentAdapter, FakeEmailAdapter, StubSMSAdapterAdapter is often confused with a few similar structural patterns. Here's how they differ:
| Pattern | Intent | Interface |
|---|---|---|
| Adapter | Make incompatible interfaces work together | Converts one interface to another |
| Decorator | Add new behaviour to an object | Keeps the same interface |
| Proxy | Control access to an object | Keeps the same interface |
| Facade | Simplify a complex subsystem | Defines a new, simpler interface |
| Bridge | Separate abstraction from implementation | Designed upfront to be extensible |
Quick rule: If you're trying to make two things work together after the fact — it's Adapter. If you're adding behaviour to something — it's Decorator. If you're simplifying something — it's Facade.
Arrays.asList() is a classic adapter — it wraps a plain array and adapts it to the List interface, letting you use array data anywhere a List is expected without copying data.
Spring MVC uses HandlerAdapter to adapt different types of handler objects (annotated controllers, HttpRequestHandlers, Servlets) to a uniform interface that the DispatcherServlet can call without knowing which type it's dealing with.
io.TextIOWrapper adapts a raw binary stream (io.RawIOBase) to a text stream interface — it translates between bytes and strings so code expecting text can work with binary sources.
ORMs like Sequelize, Hibernate, and SQLAlchemy use adapters (database drivers/dialects) to translate a common query interface into database-specific SQL — your code uses the same ORM API whether the backend is PostgreSQL, MySQL, or SQLite.
Redux's applyMiddleware() is an adapter that wraps the store's dispatch function, making middleware like redux-thunk and redux-saga compatible with the standard Redux dispatch interface.