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

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 session

3️⃣ 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

Pro Tips

📋 Quick Reference

ConceptSyntax / ideaNote
config class@ConfigurationSource of bean defs
factory bean@BeanOn a method
default scopesingletonOne per container
new each time@Scope("prototype")Fresh instance
web scopesrequest / sessionWeb apps only
default choice@PrimaryWhen several match
pick by name@Qualifier("name")Overrides @Primary

📚 Resources / Keep Learning

🎉 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

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.