Implement Java adapters by keeping the target interface narrow, preferring object adapters, and isolating translation logic at the dependency boundary.
Adapter: A wrapper that translates one interface into another so existing client code can depend on the contract it actually wants.
In Java, the strongest adapters are boring. They do not invent new workflow. They do not become policy objects. They just translate between a target interface the application wants and an adaptee interface a dependency already exposes.
Define the contract your application should depend on first:
1public interface PaymentProcessor {
2 Receipt charge(Money amount, String token);
3}
That interface should reflect application language, not vendor language.
Composition is usually the default Java implementation:
1public final class LegacyGatewayAdapter implements PaymentProcessor {
2 private final LegacyGateway gateway;
3
4 public LegacyGatewayAdapter(LegacyGateway gateway) {
5 this.gateway = gateway;
6 }
7
8 @Override
9 public Receipt charge(Money amount, String token) {
10 LegacyResponse response =
11 gateway.makePayment(amount.toMinorUnits(), token);
12
13 return new Receipt(
14 response.reference(),
15 response.approved()
16 );
17 }
18}
This keeps three responsibilities in one place:
That is enough for most adapters.
An adapter may perform format conversion, type conversion, or error translation. It should not quietly become:
If those concerns matter, keep them in separate collaborators around the adapter.
Java can implement a class adapter by extending one type and implementing another interface, but that approach is constrained by single inheritance and tends to couple the adapter tightly to a concrete adaptee. Unless the inheritance relationship is clearly worth it, object adapters are easier to evolve and easier to test.
The biggest practical payoff is not elegance. It is churn control. If the vendor SDK changes from:
makePayment(long cents, String token)to:
submitCharge(BigDecimal amount, CardToken token)the application should not need to care beyond the adapter boundary.
When reviewing a Java adapter, ask:
Adapter is strong when it localizes mismatch. It is weak when it becomes a dumping ground for everything the integration team did not know where else to place.