RizTech Academy logo
RizTech Academy
Spring FundamentalsLesson 3 of 530 min

Beans, the application context and scopes

The previous lesson said Spring's container creates your objects and injects their dependencies. This lesson names the pieces of that container properly: a bean is an object Spring manages, the application context is the container that holds them, and a scope decides how many of each exist. These are the words every Spring developer uses daily, and knowing precisely what they mean is the difference between reasoning about your application and guessing.

A bean is just an object Spring manages

A bean is nothing exotic — it is an ordinary Java object whose lifecycle Spring controls: Spring creates it, injects its dependencies, and manages it until shutdown. The only thing that makes an object a bean is that Spring is managing it rather than you new-ing it yourself. You tell Spring which classes to manage in two main ways:

// 1. Stereotype annotations — Spring discovers and manages these automatically
@Service
public class ParcelService { ... }        // a bean

@Repository
public class ParcelRepository { ... }      // a bean

// 2. @Bean methods in a @Configuration class — for objects you construct yourself
@Configuration
public class AppConfig {
    @Bean
    public RestClient courierApiClient() {   // the returned object is a bean
        return RestClient.create("https://partner-api.example.com");
    }
}

Use stereotype annotations (@Service, @Repository, @Controller, @Component) for your own classes — Spring's component scanning finds them. Use @Bean methods when you need to construct the object yourself (a third-party class you cannot annotate, or one needing custom setup). Both produce beans; the difference is only who writes the construction code — Spring (from the class) or you (in the method).

The application context is the container

The application context is Spring's IoC container — the object that holds all your beans and wires them together. When your application starts, Spring:

  1. Scans for your components (the annotated classes).
  2. Creates an instance of each — the beans.
  3. Injects each bean's dependencies (via constructors, as the DI lesson showed).
  4. Holds them all, ready to use, for the application's lifetime.

That collection of ready, wired beans is the application context. When a request arrives and a controller needs a service, the service already exists in the context, fully wired — Spring is not building it on the spot, it built it at startup. This is why a Spring app takes a moment to start (it is constructing and wiring the whole object graph) and is then fast per request (everything is already made). Understanding the context as "the bag of pre-built, pre-wired beans" demystifies a lot: your objects are not created per request; they are created once, at startup, and reused.

Scopes: how many of each bean exist

A scope decides how many instances of a bean the context keeps and how long each lives. The default is the one that surprises newcomers:

  • Singleton (the default). One instance per application context, shared everywhere it is injected. Every class that injects ParcelService gets the same ParcelService object. This is Spring's default, and it is why the container builds each bean once at startup.
  • Prototype. A new instance every time the bean is requested. Rare.
  • Request / Session (web only). One instance per HTTP request, or per user session.

The critical consequence of the singleton default: because one shared instance serves every request and every thread, a singleton bean must not hold mutable per-request state in its fields. If ParcelService stored the "current parcel" in a field, two simultaneous requests would clobber each other — a concurrency bug (the concurrency module returns to this). This is why the DI lesson insisted on final, injected dependencies and no mutable state: singleton beans are shared across threads, so they must be effectively stateless (their fields are their injected collaborators, set once, never changing). Get this wrong and you have a bug that only appears under concurrent load — invisible in development, real in production.

Seeing the context, and why "no such bean" happens

Two practical points that turn context errors from mysterious to obvious:

  • Component scanning has a root. Spring Boot scans the package of your main @SpringBootApplication class and its sub-packages. A component in a package outside that tree is not found, so it is not a bean, so injecting it fails with "no qualifying bean of type…". The usual cause of that error is a class in the wrong package, or a missing stereotype annotation — now you know where to look.
  • The context is inspectable. Spring Boot's actuator (later module) can list every bean, and startup failures name the bean that could not be created and why. When wiring fails, the error is telling you which bean, which dependency — read it rather than randomly adding @Autowired.

The mental model to carry: your application is a graph of beans, built and wired once into the application context at startup, most of them singletons shared across all requests. When something is not injected, a bean was not found (scanning/annotation); when state leaks between requests, a singleton is wrongly holding mutable state. Both become easy to reason about once you see the context for what it is.

Check your work

What a bean is. An ordinary object whose lifecycle Spring manages (creates, injects, holds) — declared by a stereotype annotation (@Service etc., for your classes) or a @Bean method (for objects you construct, e.g. third-party classes).

What the application context is. Spring's IoC container: at startup it scans for components, creates each, injects dependencies, and holds the wired beans for the app's lifetime — built once, reused per request.

Scopes. Singleton (default: one shared instance per context), prototype (new each time), request/session (web). The default is singleton.

The singleton consequence. One instance is shared across all requests/threads, so a singleton bean must be effectively stateless — no mutable per-request state in fields, or you get concurrency bugs under load.

Why "no qualifying bean" happens. Component scanning covers the main class's package and sub-packages only; a class outside that tree, or without a stereotype annotation, is not a bean. Read the startup error; it names the bean and dependency.

Practice

  1. Mark a class @Service, inject it into two other beans, and confirm (log its identity hash) that both receive the same instance — the singleton default.
  2. Add a mutable field to a singleton bean, set it from two "requests", and reason about why that is a concurrency bug waiting to happen.
  3. Declare a @Bean method for an object you construct yourself (e.g. a configured client) and inject it — note it is a bean just like an annotated class.
  4. Move a @Service class to a package outside your main application class's tree; start the app and read the "no qualifying bean" / not-found error, then move it back.
  5. Change a bean's scope to prototype and confirm you get a new instance each request; reason about when that is (rarely) what you want.
  6. Explain in one sentence why the application taking a moment to start makes each request faster.

Official documentation

Next: Spring Boot, starters and auto-configuration.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship