Adapter pattern

On this page 7

The adapter pattern is a structural design pattern that converts the interface of an existing class or component into the interface that client code expects, so that otherwise incompatible parts can work together without changing either of them. In software architecture, the adapter is the standard way to connect legacy code, third-party libraries and external services to a design that should not depend on their particular shapes.

The pattern was cataloged among the classic object-oriented design patterns and is often described with the analogy of a travel plug adapter: neither the wall socket nor the device changes, yet the connection works because a small piece in between translates one form into the other.

How the adapter pattern works

The pattern involves four roles. The client is the code that needs a service. The target is the interface the client is written against. The adaptee is the existing class with useful behavior but the wrong interface. The adapter implements the target interface and translates each call into one or more calls on the adaptee.

The translation can be simple, such as renaming a method, or substantial, such as converting units, data formats or error models. For example, a payment adapter might accept an amount in cents as charge(amountCents) and call a provider library that expects a decimal string and a currency code, then map provider exceptions to the error types the application already handles.

RoleResponsibilityTypical example
ClientUses the target interface onlyCheckout service in an online shop
TargetDefines the expected operationsA PaymentGateway interface
AdapteeProvides behavior with a different interfaceA vendor payment library
AdapterImplements target and delegates to adapteeA VendorPaymentAdapter class
Roles in the adapter pattern

Because the client knows only the target interface, the adaptee can later be replaced by another library with a new adapter and no change to the client. This is the main architectural value of the pattern.

Object adapter and class adapter

Two variants exist. An object adapter holds a reference to the adaptee and forwards calls to it, using composition. A class adapter inherits from the adaptee and implements the target interface at the same time, which requires a language that supports multiple inheritance or interfaces combined with class inheritance.

AspectObject adapterClass adapter
TechniqueCompositionInheritance
Works with subclasses of adapteeYes, any instance can be wrappedNo, bound to one concrete class
Can override adaptee behaviorOnly by wrapping callsYes, by overriding methods
Language requirementsAny object-oriented languageMultiple inheritance or similar
Common recommendationPreferred defaultRarely needed
Object adapter compared with class adapter

Most modern guidance favors the object adapter because composition over inheritance keeps coupling low and makes testing easier. In functional or dynamic languages, an adapter can also be a simple function that wraps another function and reshapes its arguments and results.

Several structural patterns also place an object between a client and another object, and they are easy to confuse. The difference lies in intent rather than structure: the adapter changes an interface, while the others add behavior, simplify a subsystem or control access.

PatternMain intentInterface seen by the client
AdapterMake an incompatible interface usableDifferent from the wrapped object
FacadeSimplify a complex subsystemNew, simpler interface over many objects
DecoratorAdd behavior without changing interfaceSame as the wrapped object
ProxyControl access, caching or remotingSame as the wrapped object
BridgeSeparate abstraction from implementationDesigned upfront, not retrofitted
How the adapter differs from neighboring patterns

In hexagonal architecture, adapters are a central idea: the application core defines ports as interfaces, and adapters implement those ports for databases, message queues, user interfaces and external APIs. The repository pattern often appears as such an adapter between domain code and storage. This supports separation of concerns and dependency inversion.

When to use an adapter

An adapter is the right choice when useful code exists but its interface does not fit, and changing that code is impossible or undesirable. Typical cases are third-party libraries, generated clients, legacy modules and services owned by another team.

SituationRecommended choiceReason
Third-party library with awkward interfaceWrap it in an adapterIsolates vendor-specific details
Legacy module during migrationAdapter to the new interfaceOld and new code coexist
Several providers for one serviceOne adapter per providerProviders become interchangeable
Code you own and can change freelyChange the interface directlyAn adapter adds needless indirection
Complex subsystem with many callsConsider a facade insteadSimplification is the real need
Decision guide for the adapter pattern

Adapters also make testing easier, because a fake implementation of the target interface can replace the real adapter in unit tests. Combined with dependency injection, the client receives whichever implementation suits the environment.

Pitfalls and common mistakes

The pattern is simple, but a few mistakes are frequent. The most serious is a leaky adapter that exposes adaptee types, exceptions or concepts through the target interface, so the client still depends on the vendor library after all.

  • Leaking adaptee details, such as vendor error codes, defeats the purpose of the adapter.
  • Business logic inside adapters makes them hard to replace; keep them thin translators.
  • Adapter chains, where one adapter wraps another, often signal that the target interface needs redesign.
  • Silent data loss happens when the adaptee lacks a feature and the adapter quietly ignores it instead of failing clearly.

A good adapter is small, well tested and boring. Its tests should cover the translation of edge cases, such as empty values, time zones, units and errors, because that is exactly where integration bugs appear.

In simple terms

The adapter pattern works like a travel plug adapter. Your phone charger and the hotel wall socket do not match, so you put a small adapter in between. Nothing about the charger or the socket changes, but now they work together.

In software, the adapter is a small piece of code that translates between two parts that speak slightly different languages, so neither part has to be rewritten.

A practical example

A logistics company has an order system written against its own ShippingService interface with a single method that takes a parcel and returns a tracking number. The company wants to add a second carrier whose library expects weights in pounds instead of kilograms, returns results as an XML document and reports errors with numeric codes.

The team writes a carrier adapter of about 150 lines that converts kilograms to pounds, builds the request, parses the XML and maps the numeric codes to the exceptions the order system already handles. No line of the order system changes. When the company later replaces the carrier, it writes another adapter in two days. The result is two interchangeable carriers behind one stable interface.