Beans, Scopes & the Lifecycle
What a bean actually is, the scopes it can live in, and the lifecycle callbacks from instantiation to destruction.
On this page
A bean is simply an object that the Spring container creates, wires, and
manages. That's the whole definition. Your BookService, once registered with the
container, is a bean. The interesting questions are: how many of each bean exist,
and what happens from birth to death.
Scopes: how many instances?
A bean's scope decides how many instances the container hands out.
| Scope | Meaning |
|---|---|
| singleton (default) | One shared instance for the entire container. Everyone who needs it gets the same object. |
| prototype | A new instance every time the bean is requested. |
| request | One instance per HTTP request (web apps). |
| session | One instance per HTTP session (web apps). |
| application | One instance per servlet context (web apps). |
@Service // singleton by default
class BookService { }
@Service
@Scope("prototype") // a fresh instance on every injection/lookup
class ReportBuilder { }Singleton means shared - so keep beans stateless
Because the default singleton is shared across the whole application (and across threads), a bean should not hold mutable per-user state in fields. Keep beans stateless; pass request-specific data as method arguments. This is the single most common Spring beginner bug.
The singleton bean is the office printer: there's one, everybody shares it, and it had better not keep one person's document lying around for the next person. A prototype bean is a sticky note: you tear off a brand-new one every time you need it, use it, and throw it away.
The lifecycle: birth to death
For a singleton, the container runs a precise sequence when it starts up (and when it shuts down). You can hook into the important moments:
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@Service
class SearchIndex {
private final BookRepository repository;
SearchIndex(BookRepository repository) { // 1. constructed & dependencies injected
this.repository = repository;
}
@PostConstruct // 2. runs after wiring is complete
void warmUp() {
// safe to use injected dependencies here - build the initial index
}
@PreDestroy // 3. runs on container shutdown
void flush() {
// release resources, flush caches
}
}The order matters: @PostConstruct runs after all dependencies are injected,
so it's the right place for initialization that needs collaborators. A constructor
runs before the object is fully part of the container, so heavy init belongs in
@PostConstruct, not the constructor.
new BookService(...)The container calls the constructor and creates the raw bean instance. With constructor injection, dependencies are passed in right here.
Prototype beans get no shutdown callback
Spring fully manages the singleton lifecycle, including @PreDestroy. For
prototype beans it steps back after creation - it won't call @PreDestroy,
because it isn't tracking every copy. Clean those up yourself.
You have a SearchIndex bean that needs its injected BookRepository to build an
index at startup. A teammate puts the index-building code in the constructor.
Why is @PostConstruct the safer choice - and what could go wrong relying on field
injection plus a constructor here?
What is the default scope of a Spring bean, and what does it imply?
Key takeaways
- A bean is any object the Spring container creates, wires, and manages.
- Scope controls instance count: singleton (one, shared - the default), prototype (new each time), plus web scopes request/session/application.
- Singletons are shared across threads, so keep beans stateless - a top source of subtle bugs.
- The lifecycle runs construct -> inject -> @PostConstruct -> in use -> @PreDestroy; do startup work in @PostConstruct, after dependencies are wired.
- Spring calls @PreDestroy for singletons but not for prototype beans.