Stage 20: compare singleton and prototype lookups

Ask Spring for the same bean twice and observe whether it returns the same object.

Open this stage in the online learning path

Before you start

Use JDK 21 or newer and Maven 3.9 or newer. Verify with java -version and mvn -version. Extract this project into its own folder; do not merge its files with another stage.

This lesson reuses the product catalog and Todo responsibilities from Stages 8–14. Only the feature described below is new. Read the ordered explanation before opening the complete files.

Singleton lookup → Shared catalog → Prototype lookup → New report draft

What we change and why

  1. Compare references, not class names

    Both ReportDraft objects have the same Java class. The == comparison checks whether two references identify the exact same object.

  2. Use the default singleton

    SharedCatalog has one shared instance per bean definition per context. The lookup does not create a fresh instance.

  3. Request a prototype

    @Scope("prototype") requests a new instance on each resolution of that bean. It does not mean one instance per web request.

Run this example

Open a terminal in the extracted folder containing pom.xml. Run one command at a time.

Terminal · Windows, macOS and Linux
mvn test
mvn spring-boot:run

This is a console lesson. It does not open a web port. Application.main starts a Spring context, calls the demonstration and closes the context. Read the terminal, not a browser. Spring Boot’s Maven plugin is used as a convenient launcher; these examples show Spring Framework directly.

Expected result

Expected output · ignore framework startup messages
Singleton references equal: true
Prototype references equal: false

Object identifiers and elapsed times can vary. Compare the relationship between values rather than copying an identifier.

Understand each complete file

Create the following files at exactly these paths, or use the ZIP. Source files include their imports. The test checks the behaviour described in the lesson.

pom.xml
pom.xml · complete file
<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>4.1.1</version><relativePath/></parent>
  <groupId>com.edupoly</groupId><artifactId>spring-stage-20-scopes</artifactId><version>1.0.0</version>
  <properties><java.version>21</java.version></properties>
  <dependencies>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></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></plugin></plugins></build>
</project>
src/main/java/com/edupoly/lesson/SharedCatalog.java
src/main/java/com/edupoly/lesson/SharedCatalog.java · complete file
package com.edupoly.lesson;

import org.springframework.stereotype.Component;
@Component public class SharedCatalog { }
src/main/java/com/edupoly/lesson/ReportDraft.java
src/main/java/com/edupoly/lesson/ReportDraft.java · complete file
package com.edupoly.lesson;

import org.springframework.stereotype.Component;
import org.springframework.context.annotation.Scope;
@Component @Scope("prototype")
public class ReportDraft { }
src/main/java/com/edupoly/lesson/Lesson.java
src/main/java/com/edupoly/lesson/Lesson.java · complete file
package com.edupoly.lesson;

import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component public class Lesson {
    private final ApplicationContext context;
    public Lesson(ApplicationContext context) { this.context = context; }
    public void run() {
        System.out.println("Singleton references equal: " + (context.getBean(SharedCatalog.class) == context.getBean(SharedCatalog.class)));
        System.out.println("Prototype references equal: " + (context.getBean(ReportDraft.class) == context.getBean(ReportDraft.class)));
    }
}
src/main/java/com/edupoly/lesson/Application.java
src/main/java/com/edupoly/lesson/Application.java · complete file
package com.edupoly.lesson;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Application {
    public static void main(String[] args) throws Exception {
        try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
            context.getBean(Lesson.class).run();
        }
    }
}
src/main/java/com/edupoly/lesson/AppConfig.java
src/main/java/com/edupoly/lesson/AppConfig.java · complete file
package com.edupoly.lesson;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.edupoly.lesson")
public class AppConfig { }
src/test/java/com/edupoly/lesson/LessonTest.java
src/test/java/com/edupoly/lesson/LessonTest.java · complete file
package com.edupoly.lesson;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

class LessonTest {
    @Test void scopeControlsIdentity() {
        try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
            assertSame(context.getBean(SharedCatalog.class), context.getBean(SharedCatalog.class));
            assertNotSame(context.getBean(ReportDraft.class), context.getBean(ReportDraft.class));
        }
    }
}

Try a failure, then correct it

Remove @Scope("prototype") from ReportDraft. The second comparison becomes true. Restore it and explain the change.

Words you can now explain

WordMeaning in this example
SingletonOne shared SharedCatalog bean per definition in this context.
PrototypeEach getBean(ReportDraft.class) resolves a new ReportDraft.

Practice assignment

Perform three prototype lookups and confirm all three references differ. Explain why singleton scope does not make mutable fields thread-safe.

Read the official documentation →