@Configuration

Declares a class whose @Bean methods define beans, with calls between those methods returning the shared instance by default.

Signature
@Configuration  |  @Configuration(proxyBeanMethods = false)
  • Added in Spring Boot Spring Framework 3.0

Parameters

ParameterTypeDescription
$valueoptionalString= ""Explicit bean name for the configuration class itself.
$proxyBeanMethodsoptionalboolean= trueWhether @Bean methods are intercepted so that calls between them return the shared bean. false skips the generated subclass.
$enforceUniqueMethodsoptionalboolean= trueWhether @Bean methods must have unique names; overloaded @Bean methods fail startup when true.

Return value

n/aRegisters the class as a source of bean definitions. Its @Bean methods are read at startup, and by default the class is subclassed so that @Bean methods calling each other get the container's shared instance.

Changelog

  • PHP Spring Framework 5.2Added proxyBeanMethods.
  • PHP Spring Framework 6.0Added enforceUniqueMethods.

A @Configuration class is where you define beans in code. Each @Bean method returns an object that Spring registers in the context. You use it for objects you cannot annotate (third-party clients, a Clock, an executor), for objects that need construction logic, and to group related beans so they can be switched on or off together with @Profile or a condition.

It is meta-annotated with @Component, so a configuration class under the main package is found by scanning. The main @SpringBootApplication class is itself a configuration class.

What proxyBeanMethods does

With the default proxyBeanMethods = true, Spring generates a subclass of your configuration class at startup. When one @Bean method calls another, the subclass intercepts the call and returns the bean already in the container instead of running the method again. That keeps singletons single even when you write new OrderService(orderRepository()).

With proxyBeanMethods = false (sometimes called "lite mode") there is no subclass, so startup is a little faster and the class can be final. A call between methods is then a plain Java call that builds a second, unmanaged object. Spring Boot's own auto-configuration uses false everywhere and receives collaborators as method parameters instead. That is the safer style for your own code too: declare the dependency as a parameter, and the behaviour is the same in both modes.

Mistakes to avoid

  • Setting proxyBeanMethods = false and keeping method-to-method calls. Two connection pools or two caches appear, and only one of them is the bean other classes see.
  • Putting @Bean methods on a plain @Component. They work, but calls between them are never intercepted, exactly like lite mode.
  • Declaring a BeanFactoryPostProcessor such as PropertySourcesPlaceholderConfigurer from a non-static method. It forces early creation of the whole configuration class; make that @Bean method static.
  • Building one giant AppConfig. Keep configuration next to the feature it configures, so a feature's beans can be found and removed together.

Because enforceUniqueMethods defaults to true, two overloaded @Bean methods with the same name fail startup instead of being treated as one bean whose factory method Spring picks by its parameters.

Example

Example · java
import java.time.Clock;
import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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

class ExportService {

    private final ExecutorService executor;
    private final Clock clock;

    ExportService(ExecutorService executor, Clock clock) {
        this.executor = executor;
        this.clock = clock;
    }

    void exportLater(Runnable job) {
        executor.submit(job);
    }

    Duration uptimeSince(long startMillis) {
        return Duration.ofMillis(clock.millis() - startMillis);
    }
}

@Configuration(proxyBeanMethods = false)
public class ExportConfig {

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

    @Bean(destroyMethod = "close")
    ExecutorService exportExecutor() {
        return Executors.newVirtualThreadPerTaskExecutor();
    }

    // Collaborators arrive as parameters: no method-to-method calls.
    @Bean
    ExportService exportService(ExecutorService exportExecutor, Clock clock) {
        return new ExportService(exportExecutor, clock);
    }
}

When to use it

  • Declaring a third-party HTTP client or an ExecutorService with the settings your application needs
  • Grouping a feature's beans so a single @Profile or condition switches them all
  • Registering @ConfigurationProperties records next to the code that uses them

Frequently asked questions

Should I use proxyBeanMethods = false?

It is a good default when your @Bean methods receive collaborators as parameters, which is what Spring Boot itself does. Keep the default true only if you rely on calling one @Bean method from another.

What is the difference between @Configuration and @Component with @Bean methods?

Both register the @Bean methods. Only @Configuration (with proxyBeanMethods = true) intercepts calls between those methods so they return the shared bean.

More examples

Proving that calls between @Bean methods share the instance

With the default proxyBeanMethods = true, auditLog() is intercepted and both auditors get the container's instance. Change the annotation to proxyBeanMethods = false and the test fails, because each call now creates a new AuditLog.

Example · java
import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

class AuditLog {
}

record OrderAuditor(AuditLog log) {
}

record RefundAuditor(AuditLog log) {
}

@Configuration
class AuditConfig {

    @Bean
    AuditLog auditLog() {
        return new AuditLog();
    }

    @Bean
    OrderAuditor orderAuditor() {
        return new OrderAuditor(auditLog());
    }

    @Bean
    RefundAuditor refundAuditor() {
        return new RefundAuditor(auditLog());
    }
}

class AuditConfigTest {

    private final ApplicationContextRunner runner =
            new ApplicationContextRunner().withUserConfiguration(AuditConfig.class);

    @Test
    void sharesOneAuditLog() {
        runner.run(context -> {
            AuditLog log = context.getBean(AuditLog.class);
            assertThat(context.getBean(OrderAuditor.class).log()).isSameAs(log);
            assertThat(context.getBean(RefundAuditor.class).log()).isSameAs(log);
        });
    }
}

A feature's configuration enabling its own properties

Everything the shipping feature needs is declared in one class that sits in the shipping package. Deleting the feature means deleting the package, with no stray beans left in a central config.

Example · java
import java.time.Duration;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@ConfigurationProperties("app.shipping")
record ShippingProperties(String carrierUrl, Duration quoteTimeout) {
}

class ShippingQuoter {

    private final ShippingProperties props;

    ShippingQuoter(ShippingProperties props) {
        this.props = props;
    }

    String describe() {
        return props.carrierUrl() + " (timeout " + props.quoteTimeout() + ")";
    }
}

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ShippingProperties.class)
public class ShippingConfig {

    @Bean
    ShippingQuoter shippingQuoter(ShippingProperties props) {
        return new ShippingQuoter(props);
    }
}

Static @Bean for a post-processor

A non-static @Bean method returning a BeanFactoryPostProcessor forces the configuration class to be built very early, before @Value and @Autowired processing is ready, so those annotations on it may be ignored. Making the method static avoids that.

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

@Configuration(proxyBeanMethods = false)
public class StartupChecksConfig {

    // Post-processors run before ordinary beans exist. A static method lets
    // Spring call it without first creating StartupChecksConfig.
    @Bean
    static BeanFactoryPostProcessor beanCountReporter() {
        return beanFactory -> System.out.println(
                "Bean definitions: " + beanFactory.getBeanDefinitionCount());
    }
}

Discussion

  • Be the first to comment on this lesson.