Adapter Pattern in a Spring Boot Project

📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.

🎓 Top 15 Udemy Courses (80-90% Discount): My Udemy Courses - Ramesh Fadatare — All my Udemy courses are real-time and project oriented courses.

▶️ Subscribe to My YouTube Channel (176K+ subscribers): Java Guides on YouTube

▶️ For AI, ChatGPT, Web, Tech, and Generative AI, subscribe to another channel: Ramesh Fadatare on YouTube

When working on real projects, we often integrate external systems — payment providers, messaging APIs, file services, etc. These systems usually have their own interfaces or SDKs, which don’t always match the way we design things internally.

If you’ve ever had to “wrap” or “translate” one interface into another, you’ve used a version of the Adapter Pattern.

In this article, we’ll walk through:

  • What the Adapter Pattern is
  • Why it’s useful in Spring Boot projects
  • A real-world example: integrating an external SMS provider
  • Step-by-step code with explanations
  • When to use and avoid this pattern

Let’s get started.


What is the Adapter Pattern?

The Adapter Pattern is a structural pattern that lets you convert the interface of one class into another one your application expects.

Think of it like a plug adapter — it allows two systems with different shapes to work together.

In software, it means:

  • You wrap a third-party class with a new class
  • This new class implements your expected interface
  • Internally, it delegates the call to the third-party method

Real-World Use Case: SMS Notification with Adapter

Scenario:

You're building a Spring Boot application. You already have a NotificationService interface used to send messages.

Now, you want to integrate with an external SMS provider like Twilio, Nexmo, or a local vendor. But their SDK doesn’t match your internal interface.

We’ll solve this by writing an adapter that wraps the external client and makes it work with your app’s interface.


✅ Step 1: Define Your Expected Interface

This is the interface your system understands.

public interface NotificationService {
    void send(String to, String message);
}

This keeps your application decoupled from specific providers.


✅ Step 2: Simulate External SMS Client

Let’s say the vendor gives you a client like this:

public class ThirdPartySmsClient {
    public void sendSms(String phone, String content) {
        System.out.println("Sending SMS to " + phone + ": " + content);
        // Real API would hit an external service here
    }
}

This class doesn’t implement your NotificationService. So if you inject it directly, it won’t work with the rest of your code.


✅ Step 3: Create an Adapter

This is the key part.

You write a class that implements NotificationService and wraps ThirdPartySmsClient.

import org.springframework.stereotype.Component;

@Component("smsAdapter")
public class SmsNotificationAdapter implements NotificationService {

    private final ThirdPartySmsClient smsClient;

    public SmsNotificationAdapter() {
        this.smsClient = new ThirdPartySmsClient(); // In real life, you'd inject config
    }

    @Override
    public void send(String to, String message) {
        smsClient.sendSms(to, message); // adapting method
    }
}

Now you have a bridge between your system’s interface and the external client’s implementation.

This is the Adapter Pattern in action.


✅ Step 4: Use the Adapter in a Service

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class AlertService {

    private final NotificationService notificationService;

    public AlertService(@Qualifier("smsAdapter") NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    public void sendOrderAlert(String phone, String message) {
        notificationService.send(phone, message);
    }
}

We’re injecting our adapter as a NotificationService. From the service’s point of view, it doesn’t matter whether the implementation is using Twilio, Nexmo, or something else.


✅ Step 5: Create a Controller to Trigger It

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/alerts")
public class AlertController {

    @Autowired
    private AlertService alertService;

    @PostMapping
    public ResponseEntity<String> sendAlert(@RequestParam String phone, @RequestParam String message) {
        alertService.sendOrderAlert(phone, message);
        return ResponseEntity.ok("Alert sent to " + phone);
    }
}

Test with:

POST /api/alerts?phone=9876543210&message=Your+order+is+confirmed

You’ll see:

Sending SMS to 9876543210: Your order is confirmed

Why This Pattern Works Well in Spring Boot

Reason Benefit
Decouples system from vendor You can replace the vendor without touching core logic
Easier to test You can mock NotificationService easily in unit tests
Makes switching easy Want to move from local vendor to Twilio? Just write a new adapter
Clean code Keeps vendor logic out of your main service classes

Testing the Adapter

Write a test using a mock version of the adapter:

@SpringBootTest
public class AlertServiceTest {

    @Test
    public void testSendOrderAlert() {
        NotificationService mockNotification = Mockito.mock(NotificationService.class);
        AlertService service = new AlertService(mockNotification);

        service.sendOrderAlert("12345", "Test message");

        Mockito.verify(mockNotification).send("12345", "Test message");
    }
}

You didn’t test the real SMS client — and that’s okay. You tested the service logic independently.


Bonus: Add More Notification Types

Let’s say later, you add email or push notifications.

Create new adapters:

@Component("emailAdapter")
public class EmailNotificationAdapter implements NotificationService {
    @Override
    public void send(String to, String message) {
        System.out.println("Sending Email to " + to + ": " + message);
    }
}

Now you can dynamically choose adapters using a NotificationFactory.


Factory for Selecting Notification Channel (Optional)

@Component
public class NotificationFactory {

    private final Map<String, NotificationService> serviceMap;

    @Autowired
    public NotificationFactory(Map<String, NotificationService> serviceMap) {
        this.serviceMap = serviceMap;
    }

    public NotificationService getService(String type) {
        NotificationService service = serviceMap.get(type + "Adapter");
        if (service == null) {
            throw new IllegalArgumentException("Unsupported type: " + type);
        }
        return service;
    }
}

Inject and use it:

NotificationService service = factory.getService("sms");
service.send(to, message);

✅ When to Use Adapter Pattern

Use Adapter Pattern when:

  • You’re integrating third-party services or SDKs
  • You want to hide vendor-specific code from your application logic
  • You need to follow your own interface, not the one provided
  • You plan to support multiple types of similar functionality (SMS, Email, Push)

⚠️ When NOT to Use Adapter Pattern

Avoid using it when:

  • The vendor library already fits your app’s interface
  • You don’t need to swap or extend implementations
  • The logic is simple and not worth abstracting

Like all patterns, Adapter is useful when the complexity justifies the extra code.


Summary

Step What We Did
1 Defined our expected interface (NotificationService)
2 Wrapped third-party SDK using an adapter class
3 Used Spring’s DI to inject the adapter
4 Sent notifications without caring about the actual vendor
5 Wrote unit tests easily by mocking the adapter

The Adapter Pattern gives you flexibility and clean boundaries in your code — especially when working with third-party tools.


Final Thoughts

You don’t need to use design patterns everywhere. But when integrating external systems into Spring Boot apps, the Adapter Pattern is a clean and proven solution.

It helps you:

  • Keep vendor code isolated
  • Make testing easier
  • Replace or upgrade third-party tools with zero impact on business logic

Start with simple adapters. Then, if needed, add factories to select between them dynamically.

Clean code isn’t about fewer lines — it’s about fewer surprises. The Adapter Pattern helps with that.

Comments

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare