Beans, Scopes & Configuration
Reviewed & published by Brayan K
Define beans explicitly with @Bean in @Configuration classes, control their lifetime with scopes (singleton vs prototype), and resolve ambiguity with @Qualifier and @Primary.
Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
Before You Start
Complete IoC Container & Dependency Injection first — you should already be comfortable with @Component and constructor injection.
What You'll Learn
- Declaring beans with @Bean and @Configuration
- When to prefer @Bean over @Component
- Singleton vs prototype scope
- Web-aware request and session scopes
- Resolving ambiguity with @Primary
- Selecting a specific bean with @Qualifier
The Big Idea — A Recipe Book and a Kitchen Rule
💡 Analogy: A @Configuration class is a recipe book: each @Bean method is a recipe telling the kitchen (container) how to make one dish. Scope is the kitchen rule: a singleton dish is cooked once and shared at a buffet; a prototype dish is freshly cooked for every order. When two cooks could make the same dish, @Primary picks the house default and @Qualifier names a specific cook.
You describe the recipes and rules; the container does the cooking and serving.
1️⃣ Defining Beans Explicitly with @Bean
A method annotated @Bean inside a @Configuration class registers its return value as a managed bean. This is how you create beans for types you don't own and can't annotate.
// (Conceptual — needs Spring on the classpath to run.)
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Clock;
@Configuration // a source of bean DEFINITIONS
public class AppConfig {
// @Bean lets you register a type you DON'T own (here, java.time.Clock).
// The method's return value becomes a container-managed bean.
@Bean
public Clock systemClock() {
return Clock.systemUTC();
}
}2️⃣ Scopes — Singleton vs Prototype
The default singleton scope gives one shared instance per container — perfect for stateless services. Prototype gives a fresh instance on every request. Web apps add request and session scopes.
package com.example.demo;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
// DEFAULT scope is singleton — ONE shared instance per container.
@Component
public class AuditLog { /* shared across the whole app */ }
// PROTOTYPE — a NEW instance every time this bean is requested/injected.
@Component
@Scope("prototype")
public class ShoppingCart { /* fresh per request site that asks for it */ }
// Web-aware scopes (only in a web app):
// @Scope("request") -> one bean per HTTP request
// @Scope("session") -> one bean per HTTP session3️⃣ Resolving Ambiguity — @Primary & @Qualifier
When several beans match a type, Spring needs help. @Primary marks one as the default; @Qualifier("name") selects a specific bean at the injection point and overrides @Primary there.
package com.example.demo;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
public interface Notifier { String send(String msg); }
@Component
@Primary // the DEFAULT choice when several Notifiers exist
public class EmailNotifier implements Notifier {
public String send(String msg) { return "EMAIL: " + msg; }
}
@Component
public class SmsNotifier implements Notifier {
public String send(String msg) { return "SMS: " + msg; }
}package com.example.demo;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class AlertService {
private final Notifier notifier;
// @Qualifier selects a SPECIFIC bean by name, overriding @Primary HERE.
// Bean names default to the class name with a lowercase first letter.
public AlertService(@Qualifier("smsNotifier") Notifier notifier) {
this.notifier = notifier;
}
public String alert(String msg) {
return notifier.send("ALERT: " + msg); // uses the SMS bean
}
}4️⃣ Singleton vs Prototype in Plain Java
To make scope concrete, here is the shared-instance vs new-instance distinction done by hand, with real output.
import java.util.function.Supplier;
public class Main {
// The singleton-vs-prototype idea in plain Java, so it's concrete.
static class AuditLog { final int id; AuditLog(int id){ this.id = id; } }
public static void main(String[] args) {
// SINGLETON-LIKE: build once, share the same reference everywhere.
AuditLog singleton = new AuditLog(1);
AuditLog same = singleton; // same instance
System.out.println("singleton same ref? " + (singleton == same));
// PROTOTYPE-LIKE: a factory hands back a NEW instance each call.
int[] counter = {0};
Supplier<AuditLog> prototype = () -> new AuditLog(++counter[0]);
AuditLog a = prototype.get();
AuditLog b = prototype.get();
System.out.println("prototype same ref? " + (a == b));
System.out.println("prototype ids: " + a.id + ", " + b.id);
}
}import java.util.*;
import java.util.function.Supplier;
public class Main {
// 🎯 YOUR TURN — replace each ___ using the hint beside it.
//
// Bean scope sounds like configuration. It is one if-statement.
enum Scope { SINGLETON, PROTOTYPE }
static class Clock {
private static int made = 0;
private final int serial;
Clock() { serial = ++made; }
public String toString() { return "Clock#" + serial; }
}
static class Beans {
private final Map<String, Supplier<?>> recipes = new LinkedHashMap<>();
private final Map<String, Scope> scopes = new HashMap<>();
private final Map<String, Object> cache = new HashMap<>();
private String primary;
void register(String name, Scope scope, Supplier<?> recipe) {
recipes.put(name, recipe);
scopes.put(name, scope);
}
void primary(String name) { this.primary = name; }
Object get(String name) {
// 1) A singleton is built once and remembered. This line is the
// entire difference between the two scopes.
if (scopes.get(name) == Scope.___ && cache.containsKey(name)) // 👉 replace ___ with SINGLETON
return cache.get(name);
// 2) Otherwise build a fresh one from the recipe.
Object made = recipes.get(name).___(); // 👉 replace ___ with get
// 3) Only a singleton is kept. A prototype is forgotten at once.
if (scopes.get(name) == Scope.SINGLETON) cache.___(name, made); // 👉 replace ___ with put
return made;
}
// 4) @Primary is just "which one do I hand over when asked vaguely".
Object getPrimary() { return get(___); } // 👉 replace ___ with primary
}
public static void main(String[] args) {
Beans beans = new Beans();
beans.register("wallClock", Scope.SINGLETON, Clock::new);
// 5) A new one on every request.
beans.register("stopwatch", Scope.___, Clock::new); // 👉 replace ___ with PROTOTYPE
beans.primary("wallClock");
System.out.println("singleton: " + beans.get("wallClock") + " then " + beans.get("wallClock"));
System.out.println("prototype: " + beans.get("stopwatch") + " then " + beans.get("stopwatch"));
System.out.println("primary: " + beans.getPrimary());
System.out.println("Clocks made: " + Clock.made);
}
}🧠 Quick Recall
Predict before you peek.
Answer: for types you don't own / can't annotate, or when you need custom construction logic.
Answer: singleton — one shared instance per container.
Answer: an explicit @Qualifier at that injection point overrides @Primary.
Common Errors
- ❌ Two beans, no disambiguation. NoUniqueBeanDefinitionException. Fix: add @Primary or @Qualifier.
- ❌ Expecting a fresh prototype inside a singleton. It's injected once. Fix: use ObjectProvider/lookup.
- ❌ Putting @Bean outside @Configuration. May lose proxying semantics. Fix: declare in @Configuration.
- ❌ Stateful singletons. Shared mutable state causes bugs. Fix: keep singletons stateless or use prototype.
- ❌ Using request/session scope in a non-web app. Invalid. Fix: only use them in web applications.
Pro Tips
- 💡 Keep singletons stateless — they're shared across the whole app.
- 💡 Use @Bean for third-party types you cannot annotate.
- 💡 Prefer @Qualifier for clarity when multiple implementations of an interface exist.
- 💡 Reach for @Primary when there's an obvious default implementation.
📋 Quick Reference
| Concept | Syntax / idea | Note |
|---|---|---|
| config class | @Configuration | Source of bean defs |
| factory bean | @Bean | On a method |
| default scope | singleton | One per container |
| new each time | @Scope("prototype") | Fresh instance |
| web scopes | request / session | Web apps only |
| default choice | @Primary | When several match |
| pick by name | @Qualifier("name") | Overrides @Primary |
📚 Resources / Keep Learning
- 📘 @Configuration & @Bean — Spring Framework docs
- 📗 Bean Scopes — Spring Framework docs
- 📗 Fine-tuning with Qualifiers — Spring docs
🎉 Lesson Complete!
You can now declare beans explicitly with @Bean in @Configuration classes, choose the right scope (singleton vs prototype, plus web scopes), and resolve ambiguous dependencies with @Primary and @Qualifier.
Practice quiz
What does a @Bean method do?
- Deletes a bean
- Explicitly defines a bean whose return value the container manages
- Marks a field final
- Starts the server
Answer: Explicitly defines a bean whose return value the container manages. A method annotated @Bean produces an object that Spring registers and manages as a bean — useful for types you can't annotate.
Where do @Bean methods normally live?
- In a class annotated @Configuration
- In a controller
- In application.properties
- In the pom.xml
Answer: In a class annotated @Configuration. @Bean methods are declared inside @Configuration classes, which act as a source of bean definitions.
What is the DEFAULT scope of a Spring bean?
- prototype
- request
- session
- singleton
Answer: singleton. By default a bean is singleton-scoped: one shared instance is created per Spring container.
How does 'prototype' scope differ from 'singleton'?
- Prototype creates a new instance every time the bean is requested
- Prototype is faster
- Prototype is read-only
- There is no difference
Answer: Prototype creates a new instance every time the bean is requested. A prototype-scoped bean yields a fresh instance on each request/injection, rather than sharing one.
When two beans match a dependency, what does @Primary do?
- Throws an error
- Marks one bean as the default choice when several qualify
- Deletes the others
- Renames the bean
Answer: Marks one bean as the default choice when several qualify. @Primary designates a preferred bean so injection succeeds without ambiguity when multiple candidates exist.
What is @Qualifier used for?
- To name a bean and select it by name at the injection point
- To make a bean lazy
- To delete a bean
- To set the port
Answer: To name a bean and select it by name at the injection point. @Qualifier selects a specific bean by name when more than one candidate of a type exists.
Why use @Bean instead of @Component sometimes?
- @Bean is faster
- To register types you don't own / can't annotate (e.g. library classes)
- @Component is deprecated
- @Bean works only in tests
Answer: To register types you don't own / can't annotate (e.g. library classes). @Bean lets you define beans for third-party classes you cannot annotate, with full control over construction.
Which scopes are web-aware (only valid in a web application)?
- singleton and prototype
- request and session
- static and final
- public and private
Answer: request and session. request and session scopes are web-aware: a bean per HTTP request or per HTTP session respectively.
If both @Primary and @Qualifier could apply, which wins at an injection point using @Qualifier?
- @Primary always wins
- Neither — it errors
- @Qualifier wins for that specific injection point
- It is random
Answer: @Qualifier wins for that specific injection point. An explicit @Qualifier at the injection point takes precedence over @Primary for that point.
What annotation sets a non-default scope on a bean?
- @Scope("prototype")
- @Lazy
- @Order
- @Profile
Answer: @Scope("prototype"). @Scope, e.g. @Scope("prototype"), overrides the default singleton scope for that bean.
Continue this course
- Previous: IoC Container & Dependency Injection
- Next: Building REST Controllers — @RestController, @GetMapping/@PostMapping, ResponseEntity and JSON
- Quick reference: Java cheat sheet
Frequently asked questions
What is the difference between @Component and @Bean?
Both register objects with the Spring container, but they apply in different situations. @Component (and its stereotypes @Service, @Repository, @Controller) is placed on a class you own, and component scanning detects it automatically. @Bean is placed on a method inside a @Configuration class, and the object it returns becomes a managed bean. You reach for @Bean when you need a bean for a type you cannot annotate — a class from a third-party library, or one that needs custom construction logic. So: annotate your own classes with @Component; use @Bean factory methods for everything else or when you need fine-grained control over how the instance is built.
What is a bean scope and what is the default?
A scope determines how many instances of a bean the container creates and how long they live. The default is singleton: exactly one shared instance per Spring container, reused everywhere it is injected — ideal for stateless services. The prototype scope produces a brand-new instance every time the bean is requested or injected, which suits stateful or short-lived objects. In a web application there are also request scope (one bean per HTTP request) and session scope (one bean per HTTP session). You override the default with @Scope, for example @Scope("prototype"). Most application beans are stateless services, so singleton is by far the most common choice.
How do I resolve 'expected single matching bean but found 2'?
That error (NoUniqueBeanDefinitionException) means more than one bean satisfies a dependency type and Spring can't choose. You have two standard tools. Mark one candidate with @Primary to make it the default whenever the type is needed but no specific bean is named — good when there's a clear 'usual' choice. Or use @Qualifier("beanName") at the injection point to name exactly which bean you want — good when different callers need different implementations. If both are present, the explicit @Qualifier at the injection point wins for that point. Bean names default to the class name with a lowercase first letter (SmsNotifier becomes smsNotifier).
When should I use prototype scope instead of singleton?
Use prototype when each user of the bean needs its own independent, stateful instance that must not be shared. Classic examples include a builder that accumulates state, a per-operation context object, or a command object you configure and then discard. Be careful, though: a singleton bean that injects a prototype bean receives the prototype only once at construction, so it won't get a fresh instance per call unless you use a provider (such as ObjectProvider or a lookup method). For most service-layer beans, which are stateless, singleton is correct and prototype would just add overhead and confusion.
Does a @Configuration class itself become a bean?
Yes. A @Configuration class is registered as a bean, and Spring also proxies it (in 'full' configuration mode) so that calls between its @Bean methods return the same singleton rather than creating new instances each time. This means if one @Bean method calls another @Bean method, you still get the shared bean, preserving singleton semantics across your configuration. @Configuration classes are the place to centralise bean definitions for third-party types, to wire together beans that need custom construction, and to keep your explicit wiring separate from your annotated component classes.