Beans & Bean Definitions
Beans & Bean Definitions
In Spring, any object that is managed by the IoC container is called a bean. That is the entire definition — but the implications run deep. When you hand an object over to Spring to manage, Spring owns its full lifecycle: it instantiates the object, wires its dependencies, applies any post-processing, and eventually destroys it when the application shuts down. Understanding what makes something a bean, how beans are described to the container, and what happens to them at runtime is the foundation of every Spring application you will ever write.
What Exactly Is a Bean?
A bean is simply a Java object created, configured, and managed by the Spring ApplicationContext. There is nothing special about the class itself — it does not need to implement a Spring interface or extend a Spring base class. A UserRepository, an EmailService, a custom Clock — any of these can be a bean. The difference between a bean and a regular object is who creates it: Spring does, not your code.
The Bean Definition — Spring's Blueprint
Before Spring can create a bean it needs a bean definition: a metadata record that describes the bean's class, its scope, its dependencies, any initialization or destruction methods, and other configuration details. You rarely interact with bean definitions directly — you express them via annotations or Java config, and Spring translates them into BeanDefinition objects internally. But knowing what information lives in a bean definition helps you understand every configuration option Spring exposes.
A bean definition captures:
- Class — which Java class to instantiate.
- Name / ID — the identifier used to look up the bean.
- Scope —
singleton(one shared instance, the default) orprototype(a new instance on every request), among others. - Constructor arguments and property values — what to inject.
- Initialization & destruction callbacks — methods to call after wiring and before shutdown.
- Lazy initialization flag — whether to create the bean immediately at context startup or only on first use.
Defining Beans with @Bean in a @Configuration Class
The most explicit way to define a bean is a @Bean method inside a @Configuration class. Spring calls this method once (for singleton-scoped beans), stores the returned object, and registers it under the method name as the bean name.
userService() calls userRepository(), you might expect a second JdbcUserRepository to be created. It is not. Spring subclasses your @Configuration class with CGLIB and intercepts inter-bean method calls, returning the already-created singleton. This is why @Configuration classes must not be final.
Defining Beans with @Component (Annotation-Driven)
For your own classes, marking them with @Component (or one of its stereotypes) and enabling component scanning is usually more concise. Spring scans the specified packages, finds annotated classes, and registers a bean definition for each one automatically.
With component scanning active (covered in detail in lesson 6), both classes above become beans automatically. The container detects the single-constructor pattern and injects the matching dependency without any explicit wiring instruction.
Bean Scope: Singleton vs Prototype
Scope controls how many instances the container maintains and when they are created.
- Singleton (default): One shared instance per container. Created at startup (unless lazy), reused for every injection point. Suitable for stateless services, repositories, and most application components.
- Prototype: A brand-new instance is created every time the bean is requested or injected. Suitable for stateful, non-thread-safe objects that must not be shared.
ApplicationContext.getBean() or a Provider<ReportBuilder> to request a fresh instance each time.
Lifecycle Callbacks: @PostConstruct and @PreDestroy
Spring can call methods on your bean after all dependencies have been injected and before the context shuts down. These are the standard hooks for initializing resources (opening a connection pool, warming a cache) and releasing them (closing connections, flushing buffers).
javax.annotation.* to jakarta.annotation.*. Always use the jakarta import in new projects.
Lazy Initialization
By default, singleton beans are created eagerly — at context startup. This catches misconfiguration errors immediately (a missing bean dependency fails fast). Sometimes you want a bean to be created only on first use, for example if it connects to an external service that may not be available at startup. Add @Lazy:
In large applications you can set lazy as the default for all beans with @ComponentScan(lazyInit = true) or spring.main.lazy-initialization=true in application.properties, which improves startup time at the cost of moving errors from startup to first use.
Summary
A Spring bean is any object whose lifecycle is managed by the IoC container. Each bean is backed by a bean definition — a blueprint specifying the class, scope, dependencies, and lifecycle callbacks. You express bean definitions either explicitly via @Bean methods in @Configuration classes, or implicitly by annotating your own classes with @Component (or a stereotype) and enabling component scanning. The default singleton scope is correct for stateless collaborators; prototype scope exists for inherently stateful objects. Lifecycle hooks (@PostConstruct, @PreDestroy) give you clean entry and exit points for resource management. Every other Spring feature — dependency injection, AOP, transactions — builds on this bean model.