If you remember five things
The entire pattern in one glance. Everything below defends these five.
- Intent. Change the interface of an existing class to one your client already expects. Translation only, never new behavior.
- The trigger. You own interface Y, a third party owns class X, you cannot edit X, and you need X to look like Y. That exact sentence is the pattern.
- Four roles. Client talks to Target, Adapter implements Target and delegates to Adaptee, Adaptee never changes.
- Object adapter wins in Java. Composition over inheritance. The adapter holds a reference to the adaptee rather than extending it.
- The classic gotcha. The moment you add a retry, a cache, or a validation rule, it stops being an adapter. Keep it dumb.
1The problem it solves#
Here is a scene I have actually hit on the job. Walk through it before reaching for the pattern name.
The setup.
- You have a payment service your code talks to through a clean interface you designed:
charge(amount, currency). - The business signs a new payment provider. Their SDK looks nothing like yours.
- Their method is
processTransaction(Map<String, Object> request), and it wants the amount in cents as a string, the currency lowercased, and an idempotency key you have never heard of.
Both of the obvious options are bad.
- Rewrite the whole app to speak the new SDK everywhere: every call site that today says
charge(4999, "USD")now has to build a map and a nonce. - Fork the SDK and reshape it: it breaks the moment the vendor ships an update and you have to re-fork.
The real tension.
- You own one interface, a third party owns the other, and neither side is going to bend.
- You need a piece in the middle that makes the foreign thing look like your thing.
- That piece is an adapter.
You often cannot modify the class you are adapting. It is a library, a legacy system, a vendor SDK, or code another team owns. If you could just edit it, you would not need an adapter. You would change the method signature and move on.
2The idea (mental model)#
A travel plug is the whole pattern in one object.
- Your laptop charger has a flat US plug. The wall in Italy has round holes. Neither one changes.
- You carry a little adapter: flat holes on one face, round pins on the other.
- The laptop never knows. The wall never knows. The adapter does the translation and disappears.
The same shape in code.
- Your code expects a target interface (the flat holes).
- An existing class, the adaptee, already does the work but exposes a different shape (the round pins).
- The adapter implements the target interface and, inside each method, calls the adaptee and converts the data back and forth.
The interview trick.
- When you hear "I have X, but my code wants Y, and I cannot change X," your brain should say adapter.
- That sentence is the pattern. Nothing more to it.
An adapter adds no new behavior. It only changes the interface to existing behavior. If you find yourself adding real logic (new rules, new decisions), you have wandered into Facade or Decorator territory. Keep adapters dumb. Translation only.
3Structure#
There are four roles, and naming them cleanly is half the grade.
- Target: the interface your client code already depends on.
- Client: your code, which only ever touches Target.
- Adaptee: the existing class with the awkward interface you want to reuse.
- Adapter: implements Target, holds a reference to an Adaptee, and forwards calls after translating them.
Two flavors, one you actually draw.
- Object adapter (the one you draw): holds a reference to the adaptee and uses composition.
- Class adapter: uses multiple inheritance to extend the adaptee directly.
- Java has no multiple inheritance of classes, so in Java interviews you almost always build the object adapter.
- Saying "this is favor composition over inheritance in action" signals you know why Java nudges you here.
CheckoutService depends only on PaymentProcessor. StripeLikeAdapter implements that interface and holds a LegacyGateway it delegates to (the o-- composition arrow). The gateway has no idea it is being adapted. Swap in a different provider later and you write a new adapter, nothing in CheckoutService moves. That is the Dependency Inversion Principle paying off: the client depends on the abstraction, not the concrete vendor.4Implementation in Java#
Let us build the payment example end to end. Clean, minimal, no fluff.
First, the target interface my application owns and loves:
public interface PaymentProcessor {
PaymentResult charge(long amountCents, String currency);
}
public record PaymentResult(boolean success, String reference) {}
Now the adaptee. Pretend this came from a vendor jar. I do not control it, and it is clunky:
// Vendor code. I cannot change this.
public class LegacyGateway {
public RawResponse processTransaction(Map<String, Object> request) {
// wants: "amount" as a String, "cur" lowercased, "nonce" required
// ... real network call ...
return new RawResponse(200, UUID.randomUUID().toString());
}
}
public class RawResponse {
public final int statusCode;
public final String txnId;
public RawResponse(int statusCode, String txnId) {
this.statusCode = statusCode;
this.txnId = txnId;
}
}
The adapter does the dirty work of making one look like the other:
public class LegacyGatewayAdapter implements PaymentProcessor {
private final LegacyGateway gateway;
public LegacyGatewayAdapter(LegacyGateway gateway) {
this.gateway = gateway;
}
@Override
public PaymentResult charge(long amountCents, String currency) {
Map<String, Object> request = new HashMap<>();
request.put("amount", String.valueOf(amountCents)); // long -> String
request.put("cur", currency.toLowerCase()); // normalize
request.put("nonce", UUID.randomUUID().toString()); // fill the gap
RawResponse raw = gateway.processTransaction(request);
boolean ok = raw.statusCode == 200; // translate result
return new PaymentResult(ok, raw.txnId);
}
}
And the client never sees any of that mess:
public class CheckoutService {
private final PaymentProcessor processor;
public CheckoutService(PaymentProcessor processor) {
this.processor = processor;
}
public void pay(long amountCents, String currency) {
PaymentResult result = processor.charge(amountCents, currency);
if (!result.success()) {
throw new IllegalStateException("payment failed");
}
// continue with result.reference()
}
}
Wiring it up:
PaymentProcessor processor = new LegacyGatewayAdapter(new LegacyGateway());
CheckoutService checkout = new CheckoutService(processor);
checkout.pay(4999, "USD");
What the adapter does, and pointedly does not do.
- Converts
longtoString, renamescurrencytocurand lowercases it, fills in anoncethe vendor needs, maps the200status to a boolean. - Does not retry, log, validate business rules, or decide anything.
- That restraint is what keeps it an adapter. Add a retry loop or a fraud check inside
chargeand a reviewer is right to call it something else.
Reveal the answer
5A real-world example#
This pattern is everywhere once you can spot it.
- Logging. SLF4J is the poster child. Your app codes against the SLF4J API. Behind it sit bindings that adapt to Log4j, Logback, or
java.util.logging. Each binding is an adapter. Swap the jar, the facade stays, your code never changes a line. - Database drivers. JDBC defines an interface. Postgres, MySQL, and Oracle each ship a driver that adapts their wire protocol to that interface. Your DAO talks
ConnectionandPreparedStatement, unaware of which engine answered. Point the connection string elsewhere and the DAO is untouched. - The Arrays.asList trick.
java.util.Arrays.asList(array)hands you aListview backed by an array. An array is not aList, but the returned object adapts one to the other. Small, and a clean JDK example to name when asked. - Media and format translation in LLD prompts. You render one video format, then need a second whose decoder has a totally different API. Wrap the second decoder in an adapter that implements your
VideoPlayerinterface. Same move for parsing XML when the rest of your system expects JSON: an adapter reads XML and emits your internal model.
Any time you glue your system to something external you cannot reshape, there is probably an adapter at the seam.
6When to use it, and when not to#
Use an adapter
- Reuse
- You want an existing class but its interface does not match yours.
- Integration
- You are wiring in a third-party library or legacy system you cannot modify.
- Vendor isolation
- You want to swap vendors later with minimal blast radius. One adapter changes, not fifty call sites.
- Uniformity
- Several incompatible classes must look the same to the client. One adapter per class, all implementing the same target.
Skip the adapter
- You own both sides
- Just change the interface. An adapter here is ceremony.
- You are adding behavior
- Extra steps around a call is Decorator. Simplifying a messy subsystem is Facade. Picking an implementation at runtime is Strategy.
- Trivial mismatch
- A one-line field rename used in one place. A helper method is cleaner than a whole class.
The honest rule.
- Adapter earns its keep at the boundary of your system.
- Inside your own code, prefer fixing the interface.
7Common mistakes#
Smuggling logic into the adapter. This is the big one.
- Retries, caching, validation, and fallback providers all start creeping in because the adapter is a convenient spot. Resist.
- An adapter that makes decisions is a maintenance trap and stops being an adapter. Put that logic in a Decorator or in the client.
- The tell: if removing the adapter would change what your system does rather than just how it talks, the adapter is carrying too much.
Adapting in both directions by accident.
- Sometimes you genuinely need two-way translation, and that is fine, but be deliberate.
- If your adapter converts requests one way and responses another, document it. People get lost when the mapping is implicit.
Leaking the adaptee's types.
- If
PaymentResultsecretly exposed the vendor'sRawResponse, the abstraction leaks and the client is coupled to the vendor again. - The adapter's whole job is to keep foreign types on its own side of the wall. Return your types, accept your types.
Confusing it with Facade.
- Both wrap something. The difference is intent.
- Adapter changes an interface to a shape you already require. Facade invents a simpler interface over a complicated subsystem.
- If asked "adapter or facade?", the answer turns on whether the target interface pre-existed (adapter) or you made it up to simplify (facade).
Over-adapting.
- One adapter per genuinely foreign interface is healthy.
- A tower of adapters wrapping adapters means something upstream is wrong. Flatten it.
8Where it shows up in interviews#
A few patterns I see when I run these loops.
The direct ask.
- "You have this old class and this new interface. Make them work together without touching the old class."
- That is a gift. Name the pattern, draw the four boxes, write the adapter. Done in under 10 minutes if you do not overthink it.
The disguised ask.
- Payment gateways, notification channels (email, SMS, push, each with a different SDK), third-party auth providers, or "support both the old API and the new API during migration."
- All adapter problems wearing a costume.
- When you hear "integrate," "legacy," "third-party," or "cannot change," your antenna should go up.
What I am actually grading.
- Do you put the adapter at the right seam, between the client and the foreign thing, not sprinkled through the app?
- Does the client depend only on the target interface? If the client ever imports the adaptee, you missed the point.
- Did you keep the adapter free of business logic?
- Can you explain object adapter (composition) versus class adapter (inheritance), and why Java pushes you toward composition? That is the detail that separates a memorized answer from understanding.
Narrate the shape out loud while you draw: "Client talks to Target. Adapter implements Target and delegates to Adaptee. Adaptee never changes." If you say that cleanly and keep the translator free of decisions, I have seen enough. I am far more worried about the candidate who quietly slips a retry or a cache into charge and never notices they left the pattern.
"Object adapter or class adapter, and why?"
"Is this an adapter or a facade?"
"Can I add a retry around the vendor call inside the adapter? It is right there."
"The vendor response has a field my result does not expose. Just surface the RawResponse?"
"We onboard a third payment provider next quarter. What changes?"
What you can now explain in the room
- The intent in one sentence: change an existing interface to one your client already expects, translation only.
- The four roles (Target, Client, Adaptee, Adapter) and that the adaptee never changes.
- Why object adapter (composition) beats class adapter in Java, and the Dependency Inversion payoff.
- A 15-line translator with zero business logic, plus the call-site wiring.
- Three real adapters you use daily: SLF4J bindings, JDBC drivers, and Arrays.asList.
- The classic misuse (smuggling retries or validation in) and how to tell adapter from facade, decorator, and strategy.
Next, look at Facade (simplify a subsystem), Decorator (add behavior through the same interface), and Strategy (swap implementations at runtime). They sit right next to Adapter and interviewers love asking you to tell them apart.
Get a prep plan built around your Adapter Pattern interview
Tell us the role and your interview date. We will send back a dated, day by day plan. Free.