Convert the interface of a class into another interface that clients expect — make incompatible interfaces work together.
BeginnerPattern #06 of 23
01 — Intent
What is the Adapter Pattern?
The Adapter pattern acts as a bridge between two incompatible interfaces. It wraps an existing class (the Adaptee) with a new interface (the Target) that the client expects — without modifying either the client or the adaptee.
Think of it as a translator: the client speaks one language, the adaptee speaks another, and the adapter translates between them in real time.
Real-world analogy: A power travel adapter. Your laptop charger has a US plug (Adaptee), the European wall socket expects a different shape (Target interface). The travel adapter sits in between — neither the charger nor the wall is modified.
02 — Problem
The Problem It Solves
You're integrating a third-party analytics library into your app. Your code calls analytics.track(Event) but the library exposes logger.log(String, Map). You can't change the library, and you don't want to scatter conversion logic throughout your codebase.
You need a single place that translates your Event calls into the library's expected format — without coupling your business logic to the third-party API.
03 — Solution
Participants
Target (interface)
The interface the client expects and works with. Defines the domain-specific interface.
Adaptee
The existing class with an incompatible interface — the thing you want to reuse but can't change.
Adapter
Implements the Target interface and wraps the Adaptee. Translates Target calls into Adaptee calls.
Client
Collaborates with the Target interface only — completely unaware of the Adaptee's existence.
04 — Structure
Visual Flow Diagram
05 — Implementation
Java Code Example
Java — Object Adapter (composition) + Class Adapter (inheritance)
// ── OBJECT ADAPTER (preferred — uses composition) ──────────────────// Target — what the client expectspublic interfaceMediaPlayer {
voidplay(String filename);
}
// Adaptee — existing class with incompatible interfacepublic classVLCPlayer {
public voidplayVLC(String file) {
System.out.println("VLC playing: " + file);
}
}
public classMP4Player {
public voidplayMP4(String file) {
System.out.println("MP4 playing: " + file);
}
}
// Adapter — wraps adaptee, exposes Target interfacepublic classMediaAdapterimplementsMediaPlayer {
privateVLCPlayer vlc;
privateMP4Player mp4;
publicMediaAdapter(String type) {
if (type.equals("vlc")) vlc = newVLCPlayer();
if (type.equals("mp4")) mp4 = newMP4Player();
}
public voidplay(String filename) {
if (vlc != null) vlc.playVLC(filename);
if (mp4 != null) mp4.playMP4(filename);
}
}
// Client — only knows MediaPlayerpublic classAudioPlayerimplementsMediaPlayer {
public voidplay(String filename) {
String ext = filename.substring(filename.lastIndexOf('.') + 1);
if (ext.equals("mp3")) {
System.out.println("Playing MP3: " + filename);
} else if (ext.equals("vlc") || ext.equals("mp4")) {
newMediaAdapter(ext).play(filename); // delegates via adapter
} else {
System.out.println("Unsupported format");
}
}
}
// ── CLASS ADAPTER (uses multiple inheritance — Java: extend + implement) ─public classClassAdapterextendsVLCPlayerimplementsMediaPlayer {
public voidplay(String filename) {
playVLC(filename); // inherited from VLCPlayer directly
}
}
// Client usageAudioPlayer player = newAudioPlayer();
player.play("song.mp3");
player.play("movie.vlc");
player.play("video.mp4");
06 — Applicability
When to Use / Avoid
✓ Use When
You want to reuse an existing class but its interface doesn't match what you need
Integrating third-party or legacy code without modifying it
You want to create a reusable class that cooperates with unrelated classes
Multiple adaptees need to be unified behind one interface
✕ Avoid When
You can modify the source class — just align the interfaces directly
Too many adaptees — may signal a design that needs rethinking
The translation is so complex it introduces significant overhead or bugs
07 — Real World
Real-World Examples
java.util.Arrays.asList() — adapts an array to the List interface
InputStreamReader — adapts byte-based InputStream to character-based Reader
Collections.list(Enumeration) — adapts legacy Enumeration to ArrayList
Spring's HandlerAdapter — adapts different controller types to a uniform handler interface
JDBC — ResultSet adapts DB-specific data to a standard Java interface
08 — Trade-offs
Pros & Cons
Pros
Single Responsibility — translation logic isolated in one class
Open/Closed — add new adapters without changing client or adaptee
Enables reuse of existing classes with incompatible interfaces
Object Adapter can adapt multiple adaptees at once via composition
Cons
Overall complexity increases — extra indirection layer
Class Adapter is inflexible — adapts only one specific class
If many methods need adapting, the adapter can become unwieldy
09 — Break & Prevent
How Adapter Can Be Broken
⚠ Attack Vectors
Leaking the adaptee: Adapter exposes a getter for the wrapped Adaptee — clients obtain it directly and call its specific methods, bypassing the abstraction entirely
Incomplete translation: Adapter silently swallows some Target methods (empty body or no-op) because the Adaptee has no equivalent — clients get unexpected null/void behaviour with no error
Adapter chains: Adapter wraps another Adapter wraps another — deep nesting causes performance issues and makes debugging a nightmare
State mismatch: Adapter holds stale state if the Adaptee's internal state changes externally and the Adapter caches a translated copy
Class Adapter over-coupling: Using inheritance-based Class Adapter makes the Adapter tightly coupled to the specific Adaptee subclass — any change in Adaptee hierarchy breaks the Adapter
✓ Prevention
Never expose the adaptee: Keep the Adaptee reference private with no getter — the only way to interact is through the Target interface
Throw on unimplemented methods: If a Target method has no Adaptee equivalent, throw UnsupportedOperationException rather than silently doing nothing — fail fast
Prefer Object Adapter over Class Adapter: Composition over inheritance gives flexibility to swap the adaptee implementation and avoids fragile base class issues
Limit adapter nesting: If you need Adapter of Adapter, it's a signal to refactor — redesign the interfaces or create one direct adapter from source to final target
Delegate, don't cache: Always forward calls directly to the Adaptee in real time rather than caching translated values
Java — Break & Fix
// ❌ BREAKING — leaking the adapteepublic classLeakyAdapterimplementsMediaPlayer {
privateVLCPlayer vlc = newVLCPlayer();
publicVLCPlayergetVLC() { return vlc; } // ❌ exposes internalspublic voidplay(String f) { vlc.playVLC(f); }
}
// Client now calls getVLC().playVLC() — adapter is useless// ✅ FIX — no getter, adaptee strictly privatepublic classSafeAdapterimplementsMediaPlayer {
private finalVLCPlayer vlc; // private, final, no getterpublicSafeAdapter(VLCPlayer vlc) { this.vlc = vlc; }
public voidplay(String f) { vlc.playVLC(f); }
}
// ❌ BREAKING — silent no-op on unimplemented methodpublic classBrokenAdapterimplementsMediaPlayer {
public voidplay(String f) { /* TODO — nothing happens! */ }
}
// ✅ FIX — fail fast with UnsupportedOperationExceptionpublic voidplay(String f) {
throw newUnsupportedOperationException(
"play() not supported by this adapter");
}
10 — Related Patterns
How Other Patterns Relate
Bridge
Often Confused
Both involve a level of indirection. Bridge is designed upfront to separate abstraction from implementation. Adapter is a retrofit — it reconciles existing incompatible interfaces after the fact.
Decorator
Often Confused
Decorator wraps to add behaviour while keeping the same interface. Adapter wraps to change the interface. Both use composition but for different purposes.
Facade
Often Confused
Facade defines a new simplified interface for a subsystem. Adapter reuses an existing interface. Facade hides complexity; Adapter resolves incompatibility.
Proxy
Often Confused
Proxy keeps the same interface and controls access. Adapter changes the interface to resolve incompatibility. Key distinction: same interface vs different interface.
11 — Quick Recap
Interview Cheat Sheet
What: Wraps an incompatible class (Adaptee) with the interface the client expects (Target). A translator between two interfaces — neither side is modified.
Two variants: Object Adapter (composition — wraps via field, preferred) vs Class Adapter (inheritance — extends Adaptee and implements Target, less flexible).
Key distinction from similar patterns: Adapter = changes interface. Decorator = same interface + adds behaviour. Facade = new simplified interface. Proxy = same interface + controls access.