NailYourInterview
Structural Design Patterns

Adapter Design Pattern in Java with Real Example

Understand the Adapter Design Pattern in Java using real-world examples like third party payment APIs. Learn how to bridge incompatible interfaces cleanly.

Adapter Design Pattern

Video thumbnail

The Adapter Design Pattern helps two things work together even if they don't naturally fit. It acts like a translator, converting one type of interface into the format the other one understands.

Real Life Examples

Payments

Let’s imagine a practical scenario.

You’ve built a website with some premium features. To accept payments, you explore different payment gateways. After your research, you discover:

  • Stripe has lower fees for international (especially US) customers.

  • Razorpay is cheaper for Indian users.

So, you decide to support both Stripe and Razorpay.

Here’s where things get tricky.

  • Stripe’s SDK provides a method like this: makePayment(cardNumber, amountInUSD)

  • Razorpay’s API expects something different: createPayment(phoneNumber, amountInPaise)

This means:

  1. You’ll have to write if-else checks everywhere to decide which payment gateway to use.

  2. These APIs are third-party so they can change their method names or signatures anytime, which would break your code.

To solve this and make your code neat and future-proof, we can use the Adapter Design Pattern.

We’ll create a common interface called pay(amountInUSD). No matter what gateway is selected - Stripe or Razorpay, you’ll always call the same method: pay(amountInUSD).

Also, these are third-party APIs. If they suddenly change their method names or parameters, your app will break and you'll need to update your code everywhere you’ve used it.

The adapter will handle the conversion behind the scenes.

So whether the API takes amount in dollars, rupees, or even bananas 🍌 - you don’t care! Your system just calls pay(amountInUSD) and the adapter takes care of the rest.

Adapter Design Pattern Example - Payments
Adapter Design Pattern Example - Payments
public interface PaymentProcessor {
    void pay(double amountInUSD);
}

This is where the user selects a payment method. Based on that, we use the appropriate adapter.

Main.java
public class Main {
    public static void main(String[] args) {
        String selectedMethod = "razorpay";
 
        PaymentProcessor processor = null;
 
        // we can use factory design pattern for creating adapter based on user selection
        switch (selectedMethod.toLowerCase()) {
            case "stripe":
                processor = new StripeAdapter("4111-1111-1111-1111");
                break;
            case "razorpay":
                processor = new RazorpayAdapter("9876543210");
                break;
            default:
                throw new IllegalArgumentException("Invalid payment method selected");
        }
 
        // here, we used strategy design pattern since at runtime pay() will behave differently
        // we also used adapter design pattern so that we can have unified pay() method and talk with 3rd party api
        processor.pay(50.0); 
    }
}

As you can see, no matter which gateway is selected, your code just calls one method: pay(amountInUSD).

Data Format Conversion

Now let’s talk about another scenario.

{ "name": "John Doe", "age": 30 }

But now, you need to use an older service - maybe built 10 years ago—that still returns data in XML format:

<user><name>John Doe</name><age>30</age></user>

You don’t want your app to handle both JSON and XML (that would make your code messy). So, you create an adapter that takes XML and converts it into JSON. Problem solved.

Adapter Design Pattern Example - Data Format
Adapter Design Pattern Example - Data Format
public interface UserService {
    public String fetchData();
}

The Adapter Wraps around the old service and converts data to JSON:

public class XmlServiceAdapter implements UserService {
    private LegacyXmlService legacyXmlService;
 
    public XmlServiceAdapter(LegacyXmlService legacyXmlService) {
        this.legacyXmlService = legacyXmlService;
    }
 
    @Override
    public String fetchData() {
        String xmlData = legacyXmlService.fetchData();
        // convert to json
        return "{ \"name\": \"John Doe\", \"age\": 30 }";  // JSON response
    }
}

UML Diagram

Let’s quickly go over the main players in this design pattern:

  1. Adapter is the middleman which translates the incompatible code into a format your system understands.

  2. Adaptee is existing/legacy/third-party code you're trying to use.

  3. Target Interface is the format or interface that your app wants everything to follow.

When Should You Use the Adapter Pattern?

  1. You want to work with third-party APIs, but they have different method names or data formats.

  2. You’re dealing with legacy code that you cannot change.

  3. You want to offer your system a unified interface so it’s easier to maintain and update in the future.

Key Points of the Adapter Design Pattern

On this page