Skip to content
Spring Boot sb core 4 min read

@Configuration & @Bean

Java-based configuration lets you define beans in code instead of XML, giving you a type-safe, refactor-friendly way to register objects the container should manage. A @Configuration class groups @Bean factory methods, and Spring treats it specially so that calls between those methods still return the managed singleton — not a fresh object.

Why Java configuration

Stereotype annotations like @Service work great for classes you own. But you often need beans for types you cannot annotate — a RestClient, a DataSource, an ObjectMapper from a third-party library. Java config solves this: you write a method that constructs the object and mark it @Bean, and the container manages the return value. This complements the component scanning described in Stereotype Annotations.

@Bean factory methods

A @Bean method’s return value becomes a bean. By default the method name is the bean name, and the bean is a singleton.

@Configuration
public class AppConfig {

    @Bean
    public RestClient restClient() {
        return RestClient.builder()
                .baseUrl("https://api.example.com")
                .build();
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper().findAndRegisterModules();
    }
}

You can rename or alias a bean and control lifecycle callbacks:

@Bean(name = "primaryMapper", initMethod = "init", destroyMethod = "close")
public ReportEngine reportEngine() {
    return new ReportEngine();
}

Dependencies between beans

Beans frequently depend on one another. There are two idiomatic ways to wire them.

Method parameters (preferred): declare the dependency as a parameter and Spring injects the matching bean.

@Configuration
public class ServiceConfig {

    @Bean
    public PricingService pricingService() {
        return new PricingService(0.2);
    }

    @Bean
    public CheckoutService checkoutService(PricingService pricingService) {
        return new CheckoutService(pricingService);
    }
}

Method calls (inter-bean reference): call one @Bean method from another. This reads naturally, and thanks to proxying it still returns the same singleton rather than a new instance.

@Configuration
public class ServiceConfig {

    @Bean
    public PricingService pricingService() {
        return new PricingService(0.2);
    }

    @Bean
    public CheckoutService checkoutService() {
        return new CheckoutService(pricingService()); // returns the managed singleton
    }
}

Note: The method-call style only returns the shared singleton because Spring subclasses the @Configuration class with a CGLIB proxy. That behavior is controlled by proxyBeanMethods.

proxyBeanMethods: full vs lite mode

@Configuration has a proxyBeanMethods attribute, and it changes how inter-bean method calls behave.

proxyBeanMethods = true (full mode, default)proxyBeanMethods = false (lite mode)
CGLIB proxy createdYesNo
Inter-bean method call returnsThe shared singletonA new object every call
Startup costSlightly higherLower (no proxy class)
Safe to call @Bean methods directlyYesNo — avoid it
Use whenMethods reference each otherMethods are self-contained

In full mode (the default), the proxy intercepts every @Bean method call and routes it through the container:

@Configuration // proxyBeanMethods = true by default
public class FullModeConfig {

    @Bean
    public Cache cache() {
        return new Cache();
    }

    @Bean
    public Service service() {
        // cache() returns the SAME singleton the container holds
        return new Service(cache());
    }
}

In lite mode, no proxy is created, so calling cache() directly runs the raw method and builds a brand-new Cache — usually a bug. Use lite mode only when methods do not call each other, or wire dependencies via parameters instead:

@Configuration(proxyBeanMethods = false)
public class LiteModeConfig {

    @Bean
    public Cache cache() {
        return new Cache();
    }

    @Bean
    public Service service(Cache cache) { // inject via parameter — safe
        return new Service(cache);
    }
}

Tip: Spring Boot’s own auto-configuration classes use proxyBeanMethods = false to speed up startup. Adopt the same pattern for your config classes when bean methods don’t reference each other — just remember to use method parameters for dependencies.

@Import — composing configurations

@Import pulls additional @Configuration classes (or plain components) into the context. It is useful for assembling modular configuration or for enabling a feature explicitly rather than relying on scanning.

@Configuration
@Import({DataConfig.class, SecurityConfig.class})
public class ApplicationConfig { }

This registers everything defined in DataConfig and SecurityConfig even if those classes live outside the component-scan path. Many @Enable* annotations (such as @EnableScheduling) work by importing configuration under the hood.

@Bean vs stereotypes

// Use a stereotype when you own and can annotate the class:
@Service
public class InvoiceService { }

// Use @Bean when you cannot annotate the type:
@Bean
public DataSource dataSource() {
    return DataSourceBuilder.create().build();
}

Both produce singletons managed identically by the container; the choice is about whether you control the source.

Best Practices

  • Prefer @Bean method parameters over inter-bean method calls — it works in both proxy modes and reads clearly.
  • Keep the default proxyBeanMethods = true when methods call each other; switch to false only when they’re independent.
  • Use stereotypes for classes you own; reserve @Bean for third-party or infrastructure types.
  • Group related beans into focused configuration classes and assemble them with @Import.
  • Avoid mutable shared state in beans created by factory methods, just as with any singleton.
Last updated June 13, 2026
Was this helpful?