If you've spent any time around Spring code, you've seen the words bean and component thrown around like they're the same thing. They aren't, but they're closely related — and understanding the difference unlocks a lot of how the framework actually works.
This post walks through both terms, the principles behind them (IoC and DI), and when to reach for @Component versus @Bean.
The principle: Inversion of Control
In a typical Java program, your code creates the objects it needs:
1PaymentService payment = new PaymentService(new StripeGateway(), new EmailNotifier());
This works, but it has a few problems. The class that uses PaymentService has to know about StripeGateway and EmailNotifier. Swapping StripeGateway for PayPalGateway means hunting down every place that constructed it. And testing becomes painful because you can't easily slip in a fake.
Inversion of Control (IoC) flips this around. Instead of your code creating its dependencies, a container creates them and hands them to you. Your class just declares what it needs; the container figures out how to assemble everything.
The Spring IoC container is the engine that does this. The objects it manages — controllers, services, repositories, gateways, anything you ask it to manage — are called beans.
What is a bean, really?
A bean is just a Java object whose lifecycle is managed by the Spring container. The container handles:
- Creation — when to instantiate the object.
- Wiring — what other beans to inject into it.
- Scope — whether it's a singleton (one instance for the whole app) or a new instance per request, etc.
- Destruction — what to clean up when the application shuts down.
This is the surface where Dependency Injection (DI) shows up. DI is the technique IoC uses: dependencies are injected into your class rather than constructed by it.
1@Service
2public class PaymentService {
3
4 private final PaymentGateway gateway;
5 private final Notifier notifier;
6
7 public PaymentService(PaymentGateway gateway, Notifier notifier) {
8 this.gateway = gateway;
9 this.notifier = notifier;
10 }
11}
When Spring creates this PaymentService, it looks at the constructor, sees it needs a PaymentGateway and a Notifier, finds beans of those types in its container, and passes them in.
@Component: marking a class for the container
@Component is a class-level annotation that says: "Hey Spring, please manage instances of this class."
1@Component
2public class StripeGateway implements PaymentGateway {
3 public PaymentResult charge(BigDecimal amount) {
4 // ...
5 }
6}
When the application starts, Spring's component scan sweeps your packages, finds every class annotated with @Component (or a stereotype that builds on it — @Service, @Repository, @Controller), and adds them to the container.
@Bean: registering an instance from a method
@Bean lives on a method, usually inside a @Configuration class. The method's return value becomes a bean in the container.
1@Configuration
2public class ClockConfig {
3
4 @Bean
5 public Clock systemClock() {
6 return Clock.systemUTC();
7 }
8}
Now anywhere else in your codebase, you can inject a Clock and Spring hands you the one from systemClock().
When to use which
The two annotations exist because they solve slightly different problems:
@Component | @Bean | |
|---|---|---|
| Where it goes | On a class | On a method (inside @Configuration) |
| Who controls construction | Spring constructs the class | You write the construction code |
| You own the class? | Yes — you can annotate it | Not necessarily — works for third-party classes |
| Discovery | Found by component scan | Registered explicitly by the method |
The rule of thumb:
- You wrote the class and it should be a Spring-managed object? Use
@Component(or@Service/@Repository/@Controller). - You're integrating a class from a library you don't control? Use
@Bean. - Construction needs more logic than Spring can infer? Use
@Bean. You write the method body; Spring just stores the result.
A practical example
A common pattern: configuring a third-party HTTP client.
src/main/java/com/example/config/HttpConfig.java 1@Configuration
2public class HttpConfig {
3
4 @Bean
5 public OkHttpClient httpClient() {
6 return new OkHttpClient.Builder()
7 .connectTimeout(Duration.ofSeconds(5))
8 .readTimeout(Duration.ofSeconds(10))
9 .build();
10 }
11}
OkHttpClient lives in a third-party library, so we can't slap @Component on it. @Bean lets us configure it the way we need and then inject it elsewhere:
src/main/java/com/example/service/WeatherService.java1@Service
2public class WeatherService {
3
4 private final OkHttpClient client;
5
6 public WeatherService(OkHttpClient client) {
7 this.client = client;
8 }
9}
WeatherService is our code, so it gets @Service (a @Component). Spring scans, finds it, sees the OkHttpClient dependency in the constructor, finds the bean registered by httpClient(), and wires the two together.
Why beans matter
The payoff of doing this at all:
- Loose coupling.
WeatherServicedoesn't know howOkHttpClientis built; it just gets one. - Centralised configuration. Want different timeouts in tests? Override the
@Beanmethod in a test configuration. - Lifecycle hooks. Spring can call
@PostConstructon initialisation and@PreDestroyon shutdown, freeing you from thinking about it. - Scopes. Most beans are singletons, but you can opt into
@Scope("prototype"),@Scope("request"), etc. when you need them. - Testability. In tests, you replace beans with fakes by registering a different
@Beanin a test config. No magic.
Wrap-up
Spring beans are the framework's universal currency. @Component puts your own classes into the container; @Bean puts everything else in. Once both are in there, Spring can wire any object that needs them, hand off the right scope and lifecycle, and let you write code that focuses on behaviour instead of construction.
If you remember just one thing: the bean container exists so your code can declare what it needs instead of building it itself. Everything else — the annotations, the scopes, the configuration classes — is bookkeeping around that idea.
