On this page
  1. Why do we need Beans?
  2. Defining a Bean
  3. @Bean or @Component?
  4. How Spring finds them
  5. Bean Scopes
  6. The lifecycle

What is a Spring @Bean?

Updated 8 September 20264 min read

  • Spring Boot
  • Java
  • Dependency injection
  • Configuration

In the Spring Framework, a Bean is an object that is instantiated, assembled, and managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application.

That definition is accurate and almost entirely useless the first time you read it. Here is the practical version: a bean is an object you have handed to Spring, so that Spring can hand it to whatever needs it later.

Why do we need Beans?

Beans allow Spring to manage the lifecycle of your objects and handle dependency injection. This promotes loose coupling and makes your application easier to test and maintain.

To see why that matters, look at what happens without it. Suppose OrderService needs a PaymentClient, which needs an HttpClient, which needs a configured timeout:

public class OrderService {
    private final PaymentClient client = new PaymentClient(new HttpClient(5000));
}

Two problems. The wiring is baked into the class, so a test cannot swap the client for a fake one. And every other class that needs a PaymentClient builds its own, so a change to that timeout means finding every construction site.

Handing the object to Spring instead means the wiring lives in one place, and OrderService just declares what it needs:

@Service
public class OrderService {
    private final PaymentClient client;

    public OrderService(PaymentClient client) {
        this.client = client;
    }
}

That constructor is doing the real work here. If you are used to putting @Autowired on fields instead, it is worth reading why constructor injection is the style to prefer.

Defining a Bean

The most common way to define a bean in modern Spring Boot applications is using the @Bean annotation within a @Configuration class.

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

When the application starts, Spring detects the @Bean annotation and executes the method. The returned object is then registered in the application context.

@Bean or @Component?

There are two ways to get an object into the container, and the choice is not really a matter of taste.

@Component (and its specialisations @Service, @Repository, @Controller) goes on the class itself. Spring finds it by scanning the packages below your main application class and constructs it for you:

@Service
public class MyServiceImpl implements MyService { }

@Bean goes on a factory method, and you construct the object. Use it when you cannot annotate the class, which in practice means one of two situations:

  • The class comes from a library, so you do not own the source.
  • Building it takes real logic: reading configuration, choosing between implementations, calling a builder.
@Configuration
public class HttpConfig {

    @Bean
    public RestClient paymentRestClient(PaymentProperties properties) {
        return RestClient.builder()
            .baseUrl(properties.baseUrl())
            .defaultHeader("X-Api-Key", properties.apiKey())
            .build();
    }
}

Note that the method takes PaymentProperties as a parameter. Spring resolves that from the container too, so @Bean methods can depend on other beans exactly like constructors can.

The rule of thumb: your own classes get @Component, third party classes get @Bean.

How Spring finds them

@SpringBootApplication implies @ComponentScan, which scans the package containing that class and everything beneath it. This is why the standard project layout matters, and why a class in a sibling package is silently never registered.

If a bean you expected is missing, the cause is nearly always one of three things: it is outside the scanned package tree, it has no stereotype annotation at all, or a @Conditional on it evaluated to false.

Bean Scopes

Spring beans can have different scopes. The default scope is Singleton, meaning only one instance of the bean is created per Spring container.

  • Singleton: (Default) Single object instance per Spring IoC container.
  • Prototype: A new instance is created each time the bean is requested.
  • Request: A single instance per HTTP request (Web apps).
  • Session: A single instance per HTTP session (Web apps).

The default is the one that catches people out, because a single shared instance across every request has consequences for what you are allowed to store on it. That is covered in what a Spring singleton actually guarantees.

The lifecycle

Once a bean is registered, Spring owns it from startup to shutdown. Singletons are created eagerly when the context starts, dependencies are injected, and then any initialisation callback runs:

@Service
public class CacheWarmer {

    @PostConstruct
    void warm() {
        // Runs after injection, before the bean is handed to anyone.
    }

    @PreDestroy
    void flush() {
        // Runs on graceful shutdown.
    }
}

Doing this work in the constructor instead is a common mistake: at that point the object is not fully wired, and throwing from a constructor fails the whole context start with a much worse error message.