Adapter Pattern
BeginnerConverts the interface of a class into another interface clients expect, enabling incompatible interfaces to work together.
Overview
The Adapter (Wrapper) pattern acts as a bridge between two incompatible interfaces — like a power plug adapter when travelling abroad. There are two variants: Object Adapter (uses composition — wraps an instance of the Adaptee) and Class Adapter (uses multiple inheritance — only possible in languages supporting it; in Java, limited to interface adaptation). Object Adapter is preferred in Java because it is more flexible and does not require changing the Adaptee. Common real-world uses: adapting a third-party payment SDK to your PaymentGateway interface, adapting legacy XML APIs to a modern JSON API, java.util.Arrays.asList() adapting an array to a List.
Object Adapter (Composition)
The Adapter class implements the Target interface and holds a reference to the Adaptee. It delegates Target method calls to corresponding Adaptee methods, translating parameters and return types as needed.
// Target interface — what the client expects
public interface PaymentGateway {
boolean charge(String userId, double amountInRupees);
boolean refund(String transactionId);
}
// Adaptee — third-party Stripe SDK (incompatible interface, cannot be changed)
public class StripeSdk {
public StripeResponse createCharge(StripeChargeRequest request) {
System.out.println("Stripe: charging " + request.getAmountInCents() + " cents");
return new StripeResponse("ch_123", true);
}
public StripeResponse reverseCharge(String chargeId) {
System.out.println("Stripe: reversing charge " + chargeId);
return new StripeResponse(chargeId, true);
}
}
// Supporting Stripe DTOs
class StripeChargeRequest {
private long amountInCents;
private String currency;
public StripeChargeRequest(long amountInCents, String currency) {
this.amountInCents = amountInCents;
this.currency = currency;
}
public long getAmountInCents() { return amountInCents; }
}
class StripeResponse {
private final String chargeId;
private final boolean success;
public StripeResponse(String chargeId, boolean success) {
this.chargeId = chargeId; this.success = success;
}
public boolean isSuccess() { return success; }
public String getChargeId() { return chargeId; }
}
// Adapter — wraps Stripe SDK, implements our PaymentGateway interface
public class StripePaymentAdapter implements PaymentGateway {
private final StripeSdk stripe; // composition — holds the adaptee
public StripePaymentAdapter(StripeSdk stripe) {
this.stripe = stripe;
}
@Override
public boolean charge(String userId, double amountInRupees) {
// Translate: rupees → paise (INR smallest unit), double → long
long amountInPaise = Math.round(amountInRupees * 100);
StripeChargeRequest request = new StripeChargeRequest(amountInPaise, "INR");
StripeResponse response = stripe.createCharge(request);
return response.isSuccess();
}
@Override
public boolean refund(String transactionId) {
StripeResponse response = stripe.reverseCharge(transactionId);
return response.isSuccess();
}
}
// Client code — depends only on PaymentGateway, unaware of Stripe
public class CheckoutService {
private final PaymentGateway gateway;
public CheckoutService(PaymentGateway gateway) { this.gateway = gateway; }
public void checkout(String userId, double amount) {
boolean success = gateway.charge(userId, amount);
System.out.println("Payment " + (success ? "succeeded" : "failed"));
}
}
// Wiring
CheckoutService service = new CheckoutService(new StripePaymentAdapter(new StripeSdk()));
service.checkout("user-42", 999.0);Java Standard Library Examples
The Java library is full of Adapters. Arrays.asList() adapts a fixed-length array to a List. Collections.enumeration() adapts an Iterator to an Enumeration. InputStreamReader adapts a byte-stream InputStream to a character-stream Reader.
// Arrays.asList — adapts T[] → List<T>
String[] arr = {"Java", "LLD", "Design"};
List<String> list = Arrays.asList(arr);
// InputStreamReader — adapts InputStream (bytes) → Reader (chars)
InputStream byteStream = new FileInputStream("data.txt");
Reader charReader = new InputStreamReader(byteStream, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(charReader);
// Collections.enumeration — adapts Iterator → Enumeration (legacy API)
List<String> names = List.of("Alice", "Bob");
Enumeration<String> enumeration = Collections.enumeration(names);
// Your own: adapt java.util.logging.Logger to your Logger interface
public interface AppLogger {
void info(String msg);
void error(String msg, Throwable t);
}
public class JulLoggerAdapter implements AppLogger {
private final java.util.logging.Logger logger;
public JulLoggerAdapter(String name) {
this.logger = java.util.logging.Logger.getLogger(name);
}
@Override public void info(String msg) { logger.info(msg); }
@Override public void error(String msg, Throwable t) { logger.log(Level.SEVERE, msg, t); }
}Key Points to Remember
- 1Adapter converts an existing interface into one a client expects — "make it fit".
- 2Object Adapter (composition) is preferred in Java over Class Adapter (inheritance).
- 3Adapter pattern is used when integrating third-party libraries without modifying them.
- 4Arrays.asList(), InputStreamReader, and Collections.enumeration() are canonical Java Adapter examples.
- 5Adapter and Decorator both wrap objects — Adapter changes the interface; Decorator keeps it the same.
Interview Questions
Sign in to ask AriaWhat is the difference between Adapter and Decorator patterns?
What is the difference between Object Adapter and Class Adapter?
Give three examples of the Adapter pattern in the Java standard library.
How would you use the Adapter pattern to integrate a new payment SDK?
Ask Aria about Adapter Pattern
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.