Autowiring an Interface With Multiple Implementations in Spring Boot
Last Updated :
23 Jul, 2025
In Spring Boot, dependency injection is a key feature that allows developers to wire components (beans) together. Often, multiple implementations of an interface may exist, and the application needs to decide which implementation to inject at runtime. This is common when designing modular systems with different strategies, such as payment services or notification services.
In this article, we will learn how to autowire an interface with multiple implementations using the Spring @Qualifier annotation to resolve ambiguity.
Autowire an Interface With Multiple Implementations
In Spring Boot, autowiring refers to the automatic injection of dependencies. When a class depends on an interface, Spring attempts to inject an implementation of that interface. However, when there are multiple implementations of a single interface, Spring needs clarification on which implementation to inject.
For example: A payment system might have several ways to process payments, such as credit cards or PayPal. In such scenarios, Spring doesn’t automatically know which implementation to inject, so we use strategies like the @Qualifier annotation to specify it.
Implementation of Autowiring an Interface With Multiple Implementations
We will walk through a simple project that demonstrates autowiring an interface with multiple implementations in a Spring Boot project.
Step 1: Create a New Spring Boot Project
Create a new Spring Boot project using IntelliJ IDEA or any IDE of your choice. Choose the following options:
- Name:
autowiring-example - Language: Java
- Type: Maven
- Packaging: Jar
Click on the Next button.
Step 2: Add the Dependencies
Add the following dependencies into the Spring Boot project.
- Spring Web
- Spring Boot DevTools
- Lombok
Click on the Create button.
Project Structure
After the project creation done, the project folder structure will look like the below image.
Step 3: Configure Application Properties
In the application.properties file, set up basic configurations:
spring.application.name=autowiring-example
server.port=8080
This sets the application name and port for the project.
Step 4: Create the PaymentService Interface
Create a common interface, PaymentService, to define the payment methods that multiple services will implement.
PaymentService.java
Java
package com.gfg.autowiringexample.service;
// Common interface for payment services
public interface PaymentService {
// Method that processes payments, implemented by each payment service
String processPayment(double amount);
}
The PaymentService interface defines a single method processPayment(double amount) that all payment services will implement.
Step 5: Create the CreditCardPaymentService Class
This class implements PaymentService for credit card payments.
CreditCardPaymentService.java
Java
package com.gfg.autowiringexample.service;
import org.springframework.stereotype.Service;
// Service that implements PaymentService for credit card payments
@Service("creditCardService")
public class CreditCardPaymentService implements PaymentService {
@Override
public String processPayment(double amount) {
// Implementation of payment processing using credit card
return "Paid " + amount + " using Credit Card";
}
}
The CreditCardPaymentService implements the processPayment method for credit card payments. It is annotated with @Service and given a custom name "creditCardService" to differentiate it from other implementations.
Step 6: Create the PayPalPaymentService Class
This class implements PaymentService for PayPal payments.
PayPalPaymentService.java
Java
package com.gfg.autowiringexample.service;
import org.springframework.stereotype.Service;
// Service that implements PaymentService for PayPal payments
@Service("payPalService")
public class PayPalPaymentService implements PaymentService {
@Override
public String processPayment(double amount) {
// Implementation of payment processing using PayPal
return "Paid " + amount + " using PayPal";
}
}
The PayPalPaymentService provides the processPayment method for PayPal payments and is annotated with @Service("payPalService") to distinguish it from other implementations.
Step 7: Create the PaymentConfig Configuration
The @Qualifier annotation is used to specify which implementation to inject. In this configuration, we can define and qualify beans manually.
PaymentConfig.java
Java
package com.gfg.autowiringexample.config;
import com.gfg.autowiringexample.service.PaymentService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// Configuration class to define beans
@Configuration
public class PaymentConfig {
// Define CreditCardPaymentService as a bean with a qualifier
@Bean
@Qualifier("creditCard")
public PaymentService creditCardPaymentService() {
return new com.gfg.autowiringexample.service.CreditCardPaymentService();
}
// Define PayPalPaymentService as a bean with a qualifier
@Bean
@Qualifier("payPal")
public PaymentService payPalPaymentService() {
return new com.gfg.autowiringexample.service.PayPalPaymentService();
}
}
In the PaymentConfig class, we manually register CreditCardPaymentService and PayPalPaymentService beans, each qualified with unique names ("creditCard" and "payPal").
Step 8: Create the PaymentController Class
In the controller, we will use the @Qualifier annotation to inject specific implementations.
PaymentController.java
Java
package com.gfg.autowiringexample.controller;
import com.gfg.autowiringexample.service.PaymentService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
// REST controller for payment operations
@RestController
public class PaymentController {
// Inject specific payment services using qualifiers
private final PaymentService creditCardService;
private final PaymentService payPalService;
// Constructor-based dependency injection with @Qualifier
public PaymentController(@Qualifier("creditCardService") PaymentService creditCardService,
@Qualifier("payPalService") PaymentService payPalService) {
this.creditCardService = creditCardService;
this.payPalService = payPalService;
}
// Endpoint for credit card payments
@GetMapping("/pay")
public String payUsingCreditCard(@RequestParam double amount) {
return creditCardService.processPayment(amount);
}
// Endpoint for PayPal payments
@GetMapping("/pay-paypal")
public String payUsingPayPal(@RequestParam double amount) {
return payPalService.processPayment(amount);
}
}
- The
PaymentController class uses constructor-based dependency injection with the @Qualifier annotation to inject the CreditCardPaymentService and PayPalPaymentService implementations. - There are two endpoints:
/pay for credit card payments and /pay-paypal for PayPal payments.
Step 9: Main Application Class
No changes are required in the main class, as Spring Boot will automatically discover the beans.
Java
package com.gfg.autowiringexample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// Main class for Spring Boot application
@SpringBootApplication
public class AutowiringExampleApplication {
public static void main(String[] args) {
SpringApplication.run(AutowiringExampleApplication.class, args);
}
}
pom.xml File
XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gfg</groupId>
<artifactId>autowiring-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>autowiring-example</name>
<description>autowiring-example</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Step 10: Run the Application
Run the Spring Boot application using your IDE or by using the command:
mvn spring-boot:run
It will start and run at port 8080.
Step 11: Testing the Endpoints
Use Postman or a browser to test the endpoints.
1. Credit Card Endpoint
http://localhost:8080/pay?amount=1000
Response:
2. PayPal Endpoint
http://localhost:8080/pay-paypal?amount=2000
Response:
Conclusion
In this article, we covered how to autowire an interface with multiple implementations in Spring Boot using the @Qualifier annotation. We implemented multiple payment services and injected the specific services into the controller to handle different payment methods. This pattern is useful for scenarios where an application needs to support different strategies or algorithms, giving developers control over which implementation to inject.
Explore
Java Enterprise Edition
Multithreading
Concurrency
JDBC (Java Database Connectivity)
Java Frameworks
JUnit