@Bean

Marks a method whose return value Spring registers as a bean, for objects you cannot annotate or that need construction logic.

Signature
@Bean  |  @Bean(name = "x", initMethod = "start", destroyMethod = "stop")
  • Added in Spring Boot Spring Framework 3.0

Parameters

ParameterTypeDescription
$value / nameoptionalString[]= {}Bean name and aliases. When empty, the method name is the bean name.
$autowireCandidateoptionalboolean= trueWhether the bean can be injected by type at all.
$defaultCandidateoptionalboolean= trueWhether the bean is injected by type when no qualifier is given. false keeps it out of plain injection while still allowing an explicit @Qualifier match.
$bootstrapoptionalBean.Bootstrap= Bean.Bootstrap.DEFAULTDEFAULT or BACKGROUND initialisation during startup.
$initMethodoptionalString= ""Name of a method to call after the bean is created and injected.
$destroyMethodoptionalString= "(inferred)"Name of a method to call on shutdown. By default a public close() or shutdown() method is detected and called; set "" to disable.

Return value

n/aRegisters the method's return value as a bean. The method's parameters are resolved from the container, and by default the bean is a singleton named after the method.

Changelog

  • PHP Spring Framework 6.2Added defaultCandidate and bootstrap.

@Bean goes on a method in a @Configuration class. Spring calls the method once at startup, registers what it returns, and injects that object wherever the type is asked for. The bean name is the method name unless you set name.

@Bean
Clock clock() {
    return Clock.systemUTC();
}

When to use @Bean instead of @Component

  • The class is not yours: a java.time.Clock, an ExecutorService, a client from an SDK. You cannot add annotations to it.
  • Construction needs logic: choosing an implementation, reading settings, calling a builder.
  • You need two differently configured instances of the same type, such as a fast and a slow executor.

For your own classes with a simple constructor, @Component (or @Service) is less code.

Parameters are injected

Anything the bean needs can be a method parameter: other beans, an ObjectProvider for optional ones, or @Value placeholders. Prefer this over calling other @Bean methods, which only returns the shared instance when the class is a proxied @Configuration.

Lifecycle and shutdown

By default Spring infers a destroy method: if the returned object has a public close() or shutdown() method, it is called when the context closes. That is right for pools and executors, but surprising for an object you share with code outside Spring. Disable it with destroyMethod = "". initMethod names a method to call after injection, for third-party classes that need an explicit start.

Return the most specific type you can

Spring decides which beans match an injection point partly from the method's declared return type before the bean exists. Returning an interface hides other interfaces the object implements, which matters for conditions such as @ConditionalOnMissingBean. Return the concrete type when callers may need it.

defaultCandidate

Since Spring Framework 6.2, defaultCandidate = false registers a bean that is only injected where a matching @Qualifier asks for it. It is a neat way to add a second RestClient or ExecutorService for one special case without making every plain injection of that type ambiguous.

Example

Example · java
import java.time.Clock;
import java.time.LocalDate;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;

@Configuration(proxyBeanMethods = false)
class TimeConfig {

    // Clock is a JDK class: it cannot carry @Component, so declare it here.
    @Bean
    Clock clock() {
        return Clock.systemUTC();
    }
}

@Service
public class SubscriptionService {

    private final Clock clock;

    public SubscriptionService(Clock clock) {
        this.clock = clock;
    }

    public boolean isExpired(LocalDate paidUntil) {
        return LocalDate.now(clock).isAfter(paidUntil);
    }
}

When to use it

  • Registering a Clock so time-dependent code can be tested with a fixed clock
  • Creating two executors for different workloads without making injection ambiguous
  • Wrapping a vendor SDK client that needs explicit start and stop calls

Frequently asked questions

What is the bean name of a @Bean method?

The method name, unless name (or value) is set. Two @Bean methods with the same name in different configuration classes override each other, which Spring Boot rejects by default.

Why was my object closed when the application stopped?

Spring infers a destroy method: a public close() or shutdown() is called on shutdown. Set destroyMethod = "" on the @Bean to turn that off.

More examples

Start and stop hooks on a class you do not own

initMethod runs after the bean is fully created; destroyMethod runs when the application shuts down, so buffered data is flushed during a graceful stop rather than lost.

Example · java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// Imagine this comes from a vendor library.
class MetricsAgent {

    private final String endpoint;

    MetricsAgent(String endpoint) {
        this.endpoint = endpoint;
    }

    public void start() {
        System.out.println("Agent reporting to " + endpoint);
    }

    public void stop() {
        System.out.println("Agent flushed and stopped");
    }
}

@Configuration(proxyBeanMethods = false)
public class AgentConfig {

    @Bean(initMethod = "start", destroyMethod = "stop")
    MetricsAgent metricsAgent() {
        return new MetricsAgent("https://metrics.internal.example");
    }
}

A specialised bean kept out of plain injection

Without defaultCandidate = false, the plain ExecutorService parameter would see two candidates and startup would fail. Both executors are closed at shutdown through the inferred close() method.

Example · java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;

@Configuration(proxyBeanMethods = false)
class ExecutorConfig {

    @Bean
    ExecutorService ioExecutor() {
        return Executors.newVirtualThreadPerTaskExecutor();
    }

    // Only injected where @Qualifier("cpuExecutor") asks for it.
    @Bean(defaultCandidate = false)
    ExecutorService cpuExecutor() {
        return Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }
}

@Service
public class ImageService {

    private final ExecutorService io;
    private final ExecutorService cpu;

    public ImageService(ExecutorService io, @Qualifier("cpuExecutor") ExecutorService cpu) {
        this.io = io;
        this.cpu = cpu;
    }

    public void process(byte[] upload) {
        cpu.submit(() -> resize(upload));
        io.submit(() -> store(upload));
    }

    private void resize(byte[] image) { }

    private void store(byte[] image) { }
}

Choosing an implementation in code

A @Bean method can run any logic before returning. For a simple on/off choice, separate @Bean methods with @ConditionalOnProperty are easier to read and to test.

Example · java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

interface FileStore {
    void put(String key, byte[] data);
}

class LocalFileStore implements FileStore {
    public void put(String key, byte[] data) { /* write under a local folder */ }
}

class BucketFileStore implements FileStore {
    private final String bucket;
    BucketFileStore(String bucket) { this.bucket = bucket; }
    public void put(String key, byte[] data) { /* upload to the bucket */ }
}

@Configuration(proxyBeanMethods = false)
public class StorageConfig {

    @Bean
    FileStore fileStore(@Value("${app.storage.bucket:}") String bucket) {
        return bucket.isBlank() ? new LocalFileStore() : new BucketFileStore(bucket);
    }
}

Discussion

  • Be the first to comment on this lesson.