Adapter Pattern
IntermediateAdapter converts the interface of a class into another interface clients expect, enabling incompatible interfaces to work together.
Overview
The Adapter pattern acts as a bridge between two incompatible interfaces. It wraps an existing class (adaptee) and provides a target interface the client expects. There are two variants: object adapter (wraps via composition — preferred) and class adapter (wraps via inheritance). Adapters are extremely common in Java — Arrays.asList(), Collections.enumeration(), and InputStreamReader are all adapters in the JDK.
Object Adapter (Composition)
The adapter holds a reference to the adaptee and translates calls. This is the preferred form because it works with adaptee subclasses and does not couple the adapter to the adaptee hierarchy.
The client only sees the target interface; it does not know an adapter is involved.
// Target interface (what the client expects)
public interface PaymentGateway {
PaymentResult charge(String customerId, double amount, String currency);
}
// Existing class with incompatible interface (adaptee)
public class LegacyPaymentSystem {
public String processPayment(int custId, long amountCents) {
// old implementation
return "TXN-" + custId + "-" + amountCents;
}
}
// Adapter — wraps LegacyPaymentSystem, implements PaymentGateway
public class LegacyPaymentAdapter implements PaymentGateway {
private final LegacyPaymentSystem legacy;
public LegacyPaymentAdapter(LegacyPaymentSystem legacy) {
this.legacy = legacy;
}
@Override
public PaymentResult charge(String customerId, double amount, String currency) {
// Translate: String → int, double → long cents
int custId = Integer.parseInt(customerId);
long amountCents = Math.round(amount * 100);
String txnId = legacy.processPayment(custId, amountCents);
return new PaymentResult(txnId, "SUCCESS");
}
}
// Client — uses target interface, unaware of legacy system
PaymentGateway gateway = new LegacyPaymentAdapter(new LegacyPaymentSystem());
PaymentResult result = gateway.charge("12345", 99.99, "USD");Adapter in the JDK
The JDK is full of adapters: - Arrays.asList() adapts an array to List interface - InputStreamReader adapts InputStream (bytes) to Reader (chars) - Collections.enumeration() adapts List to legacy Enumeration - ExecutorService.submit(Runnable) adapts Runnable to Callable
Recognising these helps understand why the pattern matters.
// Arrays.asList — array → List adapter
String[] array = {"Alice", "Bob", "Charlie"};
List<String> list = Arrays.asList(array);
// InputStreamReader — InputStream → Reader adapter
Reader reader = new InputStreamReader(
new FileInputStream("data.txt"), StandardCharsets.UTF_8);
// Collections.enumeration — Collection → Enumeration adapter
Enumeration<String> en = Collections.enumeration(list);
// Runnable → Callable adapter
ExecutorService exec = Executors.newSingleThreadExecutor();
Future<?> future = exec.submit(() -> System.out.println("Runnable"));
// submit(Runnable) internally wraps it in a Callable adapterAdapter vs Decorator vs Facade
These three structural patterns are often confused:
Adapter — changes the interface without changing the behaviour. Bridges incompatibility. Decorator — keeps the same interface but adds behaviour. Enhances without changing the contract. Facade — provides a simplified interface over a complex subsystem. Reduces complexity.
A useful mnemonic: Adapter translates, Decorator enhances, Facade simplifies.
// ADAPTER — converts interface (old XML API → new JSON API)
public class XmlToJsonAdapter implements JsonReporter {
private XmlReporter xmlReporter;
@Override public String generate(Report r) {
String xml = xmlReporter.generate(r);
return convertXmlToJson(xml); // translates
}
}
// DECORATOR — same interface, adds caching
public class CachingReporter implements JsonReporter {
private JsonReporter delegate;
private Map<String, String> cache = new HashMap<>();
@Override public String generate(Report r) {
return cache.computeIfAbsent(r.getId(),
id -> delegate.generate(r)); // adds behaviour
}
}
// FACADE — hides complexity of multiple subsystems
public class ReportFacade {
public String generateFullReport(String userId) {
User user = userService.find(userId);
List<Order> orders = orderService.findByUser(userId);
Analytics stats = analyticsService.summarise(orders);
return formatter.format(user, orders, stats); // simplifies
}
}Key Points to Remember
- Adapter converts one interface to another — it does not add behaviour, only translates.
- Prefer object adapter (composition) over class adapter (inheritance) for flexibility.
- Arrays.asList, InputStreamReader, and Collections.enumeration are JDK Adapters.
- Adapter bridges incompatibilities; Decorator enhances; Facade simplifies.
- The client depends on the target interface, not on the adaptee — good dependency inversion.
Practice Adapter Pattern in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat is the difference between Adapter and Decorator patterns?
Give a real example of the Adapter pattern in the JDK.
What is the difference between class adapter and object adapter?
How would you use Adapter to integrate a third-party library without coupling your code to it?
Explain the difference between Adapter, Decorator, and Facade.
Ask Aria about Adapter Pattern
Your personal AI tutor — ask anything about this concept