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.
| Role | Responsibility | Typical example |
|---|---|---|
| Client | Uses the target interface only | Checkout service in an online shop |
| Target | Defines the expected operations | A PaymentGateway interface |
| Adaptee | Provides behavior with a different interface | A vendor payment library |
| Adapter | Implements target and delegates to adaptee | A VendorPaymentAdapter class |
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.
| Aspect | Object adapter | Class adapter |
|---|---|---|
| Technique | Composition | Inheritance |
| Works with subclasses of adaptee | Yes, any instance can be wrapped | No, bound to one concrete class |
| Can override adaptee behavior | Only by wrapping calls | Yes, by overriding methods |
| Language requirements | Any object-oriented language | Multiple inheritance or similar |
| Common recommendation | Preferred default | Rarely needed |
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.
Adapter compared with related patterns
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.
| Pattern | Main intent | Interface seen by the client |
|---|---|---|
| Adapter | Make an incompatible interface usable | Different from the wrapped object |
| Facade | Simplify a complex subsystem | New, simpler interface over many objects |
| Decorator | Add behavior without changing interface | Same as the wrapped object |
| Proxy | Control access, caching or remoting | Same as the wrapped object |
| Bridge | Separate abstraction from implementation | Designed upfront, not retrofitted |
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.
| Situation | Recommended choice | Reason |
|---|---|---|
| Third-party library with awkward interface | Wrap it in an adapter | Isolates vendor-specific details |
| Legacy module during migration | Adapter to the new interface | Old and new code coexist |
| Several providers for one service | One adapter per provider | Providers become interchangeable |
| Code you own and can change freely | Change the interface directly | An adapter adds needless indirection |
| Complex subsystem with many calls | Consider a facade instead | Simplification is the real need |
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.