Multiple Bean Selection

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 smsNotificationService matches 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

Scroll to Top