When several beans implement the same interface, Spring cannot decide which one to inject and fails with a “NoUniqueBeanDefinitionException”. You resolve it with @Primary (the default choice), @Qualifier("beanName") (an explicit choice), or by injecting all the beans as a Map or List. It is used when one feature has several implementations, such as Email and SMS notifications, or different payment gateways. Your code depends only on the interface, so adding another implementation needs no change to the callers.
Types
@Primary: marks one bean as the default for its type.@Qualifier("name"): picks a specific bean by name at the injection point.- Parameter or field name match: a parameter named
smsNotificationServicematches the bean of that name (works, but fragile). Map<String, Type>/List<Type>: injects every bean of that type, so you can choose at runtime.- Custom qualifier annotations: your own
@Qualifier-based annotation for cleaner code in bigger projects.
Hands-on Experiment: Creating a spring boot application with multi-bean selection
The following steps are involved in developing this hands-on experiment.
Step 1: Generate the Project
Step 2: Project Structure
Step 3: pom.xml
Step 4: application.yml
Step 5: Create Entity Class
Step 6: Create Repository Interface
Step 7: Create Service Class
Step 8: Create Controller Class
Step 9: Main Application Class
Step 10: Run the Main Application Class
Step 1: Configure the Project on Spring Initializr
Go to start.spring.io and set:
- Project: Maven
- Language: Java
- Spring Boot version: 3.2.x
- Group: com.example
- Artifact: multibean
- Name: multibean
- Package name: com.example.multibean
- Packaging: Jar
- Java: 17
- Dependencies: Spring Web, Spring Data JPA, MySQL Driver
Click Generate to download the ZIP, then extract and import it into your IDE as a Maven project.
Step 2: Generated Project Structure

Step 3: Explore pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://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.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>student-multibean-demo</artifactId>
<version>1.0.0</version>
<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-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Step 4: application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/student_multibean_db?createDatabaseIfNotExist=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
username: root
password: password
jpa:
hibernate:
ddl-auto: update
show-sql: true
server:
port: 8082Code language: JavaScript (javascript)
Step 5: Create Entity class
package com.example.multibean.entity;
import jakarta.persistence.*;
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private String phone;
private String course;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getCourse() { return course; }
public void setCourse(String course) { this.course = course; }
}
Step 6: Create Repository Interface
package com.example.multibean.repository;
import com.example.multibean.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StudentRepository extends JpaRepository<Student, Long> {
}
package com.example.multibean.notification;
// One interface, two beans -> Spring must be told which one to inject
public interface NotificationService {
String send(String to, String message);
}
Step 7: Create Service classes
package com.example.multibean.service;
import com.example.multibean.entity.Student;
import com.example.multibean.notification.NotificationService;
import com.example.multibean.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Service
public class StudentService {
private final StudentRepository repository;
// 1) No qualifier -> Spring picks the @Primary bean (Email)
private final NotificationService defaultNotifier;
// 2) @Qualifier -> explicitly asks for the SMS bean by name
private final NotificationService smsNotifier;
// 3) Map -> Spring injects ALL NotificationService beans (key = bean name)
private final Map<String, NotificationService> allNotifiers;
public StudentService(StudentRepository repository,
NotificationService defaultNotifier,
@Qualifier("smsNotificationService") NotificationService smsNotifier,
Map<String, NotificationService> allNotifiers) {
this.repository = repository;
this.defaultNotifier = defaultNotifier;
this.smsNotifier = smsNotifier;
this.allNotifiers = allNotifiers;
System.out.println(">>> default (primary): " + defaultNotifier.getClass().getSimpleName());
System.out.println(">>> qualified (sms) : " + smsNotifier.getClass().getSimpleName());
System.out.println(">>> all beans : " + allNotifiers.keySet());
}
// Welcome message always goes through the default (Email) notifier
public Student save(Student student) {
Student saved = repository.save(student);
defaultNotifier.send(saved.getEmail(), "Welcome " + saved.getName() + "!");
return saved;
}
public List<Student> findAll() {
return repository.findAll();
}
public Student findById(Long id) {
return repository.findById(id).orElseThrow(() -> new RuntimeException("Student not found: " + id));
}
// Reminder always goes through SMS (qualified bean)
public String sendReminder(Long id) {
Student s = findById(id);
return smsNotifier.send(s.getPhone(), "Class reminder for " + s.getCourse());
}
// Channel chosen at runtime: "email" or "sms"
public String notifyBy(Long id, String type, String message) {
NotificationService notifier = allNotifiers.get(type + "NotificationService");
if (notifier == null) {
throw new IllegalArgumentException("Unknown type '" + type + "'. Use email or sms");
}
Student s = findById(id);
String to = type.equals("sms") ? s.getPhone() : s.getEmail();
return notifier.send(to, message);
}
}
package com.example.multibean.notification;
import org.springframework.stereotype.Service;
// Bean name = "smsNotificationService" (class name, first letter lowercase)
@Service
public class SmsNotificationService implements NotificationService {
@Override
public String send(String to, String message) {
String result = "SMS sent to " + to + ": " + message;
System.out.println(">>> " + result);
return result;
}
}
package com.example.multibean.notification;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
// @Primary -> the default choice when nothing else is specified
@Primary
@Service
public class EmailNotificationService implements NotificationService {
@Override
public String send(String to, String message) {
String result = "EMAIL sent to " + to + ": " + message;
System.out.println(">>> " + result);
return result;
}
}
Step 8: Create Controller class
package com.example.multibean.controller;
import com.example.multibean.entity.Student;
import com.example.multibean.service.StudentService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/students")
public class StudentController {
private final StudentService service;
public StudentController(StudentService service) {
this.service = service;
}
@PostMapping
public Student create(@RequestBody Student student) {
return service.save(student);
}
@GetMapping
public List<Student> getAll() {
return service.findAll();
}
@GetMapping("/{id}")
public Student getById(@PathVariable Long id) {
return service.findById(id);
}
// Always SMS (@Qualifier)
@PostMapping("/{id}/reminder")
public String reminder(@PathVariable Long id) {
return service.sendReminder(id);
}
// Choose at runtime: ?type=email or ?type=sms
@PostMapping("/{id}/notify")
public ResponseEntity<String> notify(@PathVariable Long id,
@RequestParam String type,
@RequestBody Map<String, String> body) {
try {
return ResponseEntity.ok(service.notifyBy(id, type, body.get("message")));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}
Step 9: Main Application
package com.example.multibean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MultiBeanApplication {
public static void main(String[] args) {
SpringApplication.run(MultiBeanApplication.class, args);
}
}
Step 10: Run the MultiBeanApplication


