Skip to content
3 min read

🔌 Adapter Design Pattern

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.


💡 Real-World Analogy

Think of a Power Adapter

When you travel from the US to Europe, your laptop charger plug won't fit European wall sockets. You use a power adapter — it doesn't change what your charger does, it simply makes the incompatible plug fit into the socket. The Adapter pattern does the same for software interfaces.

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    A["🔌 US Plug"] -->|"doesn't fit"| B["🔄 Adapter"]
    B -->|"converts"| C["🏠 EU Socket"]
    C -->|"powers"| D["⚡ Success!"]

    style A fill:#E3F2FD,stroke:#1565C0,color:#000
    style B fill:#FFF3E0,stroke:#E65100,stroke-width:2px,color:#000
    style C fill:#E8F5E9,stroke:#2E7D32,color:#000
    style D fill:#FFF8E1,stroke:#F9A825,color:#000

📐 Pattern Structure

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    Client["🖥️ Client"] -->|"uses"| Target[["🎯 Target Interface"]]
    Adapter{{"🔄 Adapter"}} -->|"implements"| Target
    Adapter -->|"delegates"| Adaptee(["📦 Adaptee"])

    style Client fill:#E8F5E9,stroke:#2E7D32,color:#000
    style Target fill:#FFF3E0,stroke:#E65100,color:#000
    style Adapter fill:#E3F2FD,stroke:#1565C0,color:#000
    style Adaptee fill:#FFF8E1,stroke:#F9A825,color:#000

UML Class Diagram

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
classDiagram
    class MediaPlayer {
        <<interface>>
        +play(audioType, fileName) void
    }
    class AdvancedMediaPlayer {
        +playVlc(fileName) void
        +playMp4(fileName) void
    }
    class MediaAdapter {
        -advancedPlayer: AdvancedMediaPlayer
        +play(audioType, fileName) void
    }
    class AudioPlayer {
        -mediaAdapter: MediaAdapter
        +play(audioType, fileName) void
    }

    MediaAdapter ..|> MediaPlayer : implements
    AudioPlayer ..|> MediaPlayer : implements
    MediaAdapter *-- AdvancedMediaPlayer : wraps
    AudioPlayer --> MediaAdapter : delegates

Object Adapter vs Class Adapter

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    subgraph OA["Object Adapter (Composition)"]
        direction LR
        OA_Adapter{{"🔄 Adapter"}} -->|"has-a"| OA_Adaptee(["📦 Adaptee"])
        OA_Adapter -->|"implements"| OA_Target[["🎯 Target"]]
    end

    subgraph CA["Class Adapter (Inheritance)"]
        direction LR
        CA_Adapter{{"🔄 Adapter"}} -->|"extends"| CA_Adaptee(["📦 Adaptee"])
        CA_Adapter -->|"implements"| CA_Target[["🎯 Target"]]
    end

    style OA_Adapter fill:#E3F2FD,stroke:#1565C0,color:#000
    style OA_Adaptee fill:#FFF8E1,stroke:#F9A825,color:#000
    style OA_Target fill:#FFF3E0,stroke:#E65100,color:#000
    style CA_Adapter fill:#E3F2FD,stroke:#1565C0,color:#000
    style CA_Adaptee fill:#FFF8E1,stroke:#F9A825,color:#000
    style CA_Target fill:#FFF3E0,stroke:#E65100,color:#000

❌ The Problem

You're integrating a third-party analytics library that returns data in XML format. But your application's reporting module exclusively works with JSON. You can't modify the third-party library, and rewriting your reporting module would break existing code.

Without an adapter, you'd face:

  • Tight coupling to specific data formats
  • Inability to swap implementations
  • Violation of the Open/Closed Principle

Without This Pattern

Java
// BAD: Client directly coupled to third-party XML library
public class ReportGenerator {

    public void generateReport() {
        LegacyXmlService xmlService = new LegacyXmlService();
        String xmlData = xmlService.getXmlData();

        // Parsing XML manually in every client that needs JSON
        String json = xmlData.replace("<data><name>", "{\"name\": \"")
                             .replace("</name></data>", "\"}");

        // What if we switch to a YAML library tomorrow?
        // Every single client class must be rewritten!
        renderChart(json);
    }

    // Another method also needs the conversion — duplicated logic
    public void exportData() {
        LegacyXmlService xmlService = new LegacyXmlService();
        String xmlData = xmlService.getXmlData();
        String json = xmlData.replace("<data><name>", "{\"name\": \"")
                             .replace("</name></data>", "\"}");
        writeToFile(json);
    }
}

Problems:

  • Tight coupling: Every client directly depends on the third-party LegacyXmlService and its XML format — changing the library forces changes everywhere
  • Duplicated conversion logic: XML-to-JSON translation is copy-pasted across multiple methods and classes
  • Violates Open/Closed Principle: Adding a new data source (e.g., CSV) requires modifying all existing client classes
  • Violates Single Responsibility Principle: The ReportGenerator handles both report logic AND data format conversion
  • Pain point: When the third-party library updates its API or you need to swap it for a different vendor, you must hunt down and rewrite every place that touches it

✅ The Solution

The Adapter pattern introduces a wrapper class that translates one interface into another. The client works with the Target interface; the Adapter translates those calls into the format the Adaptee understands.

Two flavors:

Type Mechanism Java Support
Object Adapter Composition (has-a) Preferred in Java
Class Adapter Multiple Inheritance (is-a) Not directly possible (no MI) — use interfaces

🛠 Implementation

Java
// Target interface - what our client expects
public interface MediaPlayer {
    void play(String audioType, String fileName);
}

// Adaptee - incompatible interface (third-party or legacy)
public class AdvancedMediaPlayer {
    public void playVlc(String fileName) {
        System.out.println("Playing VLC file: " + fileName);
    }

    public void playMp4(String fileName) {
        System.out.println("Playing MP4 file: " + fileName);
    }
}

// Adapter - bridges the gap using COMPOSITION
public class MediaAdapter implements MediaPlayer {

    private final AdvancedMediaPlayer advancedPlayer;

    public MediaAdapter() {
        this.advancedPlayer = new AdvancedMediaPlayer();
    }

    @Override
    public void play(String audioType, String fileName) {
        switch (audioType.toLowerCase()) {
            case "vlc" -> advancedPlayer.playVlc(fileName);
            case "mp4" -> advancedPlayer.playMp4(fileName);
            default -> throw new UnsupportedOperationException(
                "Format not supported: " + audioType);
        }
    }
}

// Client code
public class AudioPlayer implements MediaPlayer {

    private final MediaAdapter mediaAdapter = new MediaAdapter();

    @Override
    public void play(String audioType, String fileName) {
        if ("mp3".equalsIgnoreCase(audioType)) {
            System.out.println("Playing MP3 file: " + fileName);
        } else {
            mediaAdapter.play(audioType, fileName);
        }
    }
}
Java
// In Java, we simulate class adapter using interface + extending adaptee
public interface Target {
    String getData();
}

// Adaptee with incompatible interface
public class LegacyXmlService {
    public String getXmlData() {
        return "<data><name>Adapter Pattern</name></data>";
    }
}

// Class Adapter - extends Adaptee AND implements Target
public class XmlToJsonAdapter extends LegacyXmlService implements Target {

    @Override
    public String getData() {
        String xml = getXmlData(); // inherited from LegacyXmlService
        return convertXmlToJson(xml);
    }

    private String convertXmlToJson(String xml) {
        // Conversion logic (simplified)
        return "{\"name\": \"Adapter Pattern\"}";
    }
}

// Client only knows Target interface
public class ReportGenerator {
    private final Target dataSource;

    public ReportGenerator(Target dataSource) {
        this.dataSource = dataSource;
    }

    public void generateReport() {
        String json = dataSource.getData();
        System.out.println("Report data: " + json);
    }
}
Java
// Target interface
public interface UserRepository {
    User findById(Long id);
    List<User> findAll();
    void save(User user);
}

// Adaptee — Legacy JDBC-based DAO
public class LegacyUserDao {
    public Map<String, Object> queryUserById(long id) { /* ... */ }
    public List<Map<String, Object>> queryAllUsers() { /* ... */ }
    public void insertUser(Map<String, Object> userData) { /* ... */ }
}

// Adapter
@Repository
public class UserRepositoryAdapter implements UserRepository {

    private final LegacyUserDao legacyDao;

    public UserRepositoryAdapter(LegacyUserDao legacyDao) {
        this.legacyDao = legacyDao;
    }

    @Override
    public User findById(Long id) {
        Map<String, Object> raw = legacyDao.queryUserById(id);
        return mapToUser(raw);
    }

    @Override
    public List<User> findAll() {
        return legacyDao.queryAllUsers().stream()
            .map(this::mapToUser)
            .collect(Collectors.toList());
    }

    @Override
    public void save(User user) {
        legacyDao.insertUser(mapToRaw(user));
    }

    private User mapToUser(Map<String, Object> raw) {
        return new User((Long) raw.get("id"), (String) raw.get("name"));
    }

    private Map<String, Object> mapToRaw(User user) {
        return Map.of("id", user.getId(), "name", user.getName());
    }
}

🎯 When to Use

  • You want to use an existing class but its interface doesn't match what you need
  • You're building a reusable class that must cooperate with unrelated/unforeseen classes
  • You need to integrate legacy code with a modern system without modifying the legacy code
  • You're wrapping third-party libraries to isolate your code from external changes
  • You need to provide a unified interface for multiple classes with different interfaces

🌐 Real-World Examples

Where Example
JDK Arrays.asList() — adapts array to List interface
JDK InputStreamReader — adapts InputStream to Reader
JDK Collections.enumeration() — adapts Collection to Enumeration
Spring HandlerAdapter — adapts various handler types to a common interface
Spring JpaVendorAdapter — adapts different JPA providers
SLF4J Entire library is an adapter over logging frameworks
Jackson ObjectMapper adapts Java objects to/from JSON

⚠ Pitfalls

Common Mistakes

  • Over-adapting: Creating adapters for classes that can easily be refactored — increases unnecessary indirection
  • Two-way adapters: Trying to adapt both directions makes the adapter overly complex
  • Using Class Adapter in Java: Java doesn't support multiple inheritance of classes — prefer Object Adapter (composition)
  • Leaking Adaptee details: The adapter should completely hide the Adaptee's interface from the client
  • Not following ISP: Creating a "fat" target interface forces the adapter to implement methods it doesn't need

📝 Key Takeaways

Summary

Aspect Detail
Intent Make incompatible interfaces work together
Mechanism Wraps an existing class with a new interface
Preferred Type Object Adapter (composition over inheritance)
Key Principle Open/Closed Principle — extend without modifying
Interview Tip Adapter converts an interface; Decorator adds behavior; Facade simplifies a subsystem
One-liner "If you have a square peg and a round hole, you need an Adapter"