Java-Based Configuration
Configure a Spring application entirely in Java using @Configuration classes and @Bean methods, without a line of XML.
Introduction
Early Spring applications were configured almost entirely in XML files. Modern Spring applications configure beans in plain Java instead, using classes annotated with @Configuration. This gives you compile-time checking, easy navigation in your IDE, and the full power of the Java language for wiring your application together.
- What @Configuration and @Bean do
- How to wire dependencies between beans in Java
- How to bootstrap an ApplicationContext from a configuration class
- How to combine multiple configuration classes with @Import
Why Java Configuration?
Component scanning with @Component is convenient for classes you own, but sometimes you need to configure beans from third-party libraries, build objects conditionally, or simply keep configuration decisions in one visible place. Java-based configuration handles all of these cases cleanly, and it has become the default style for Spring and Spring Boot applications.
@Configuration and @Bean
A class annotated with @Configuration tells Spring that it declares beans. Each method inside it annotated with @Bean returns an object that Spring registers in the application context, using the method name as the bean name by default.
public class EngineService { public String start() { return "Engine started"; }}@Configurationpublic class AppConfig {
@Bean public EngineService engineService() { return new EngineService(); }}Wiring Beans Together
To wire one bean into another, call the producing method directly from within the configuration class. Spring intercepts that call and makes sure it returns the same singleton instance rather than creating a new object each time, as long as the class is a genuine @Configuration class (not @Component with @Bean methods).
public class CarService {
private final EngineService engineService;
public CarService(EngineService engineService) { this.engineService = engineService; }
public String drive() { return engineService.start() + " -> Car is moving"; }}@Configurationpublic class AppConfig {
@Bean public EngineService engineService() { return new EngineService(); }
@Bean public CarService carService() { return new CarService(engineService()); }}Bootstrapping the Context
To start a container from a Java configuration class, use AnnotationConfigApplicationContext and pass the configuration class itself.
public class Main { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
CarService carService = context.getBean(CarService.class); System.out.println(carService.drive()); }}Click Run to see what this code prints.
Combining Multiple Configuration Classes
As an application grows, it is common to split configuration into several focused classes, for example one per module. Use @Import to pull them together, or pass every class to AnnotationConfigApplicationContext at once.
@Configuration@Import({ AppConfig.class, DataSourceConfig.class })public class RootConfig {}| Approach | Best For |
|---|---|
| @Component + @ComponentScan | Your own application classes, discovered automatically |
| @Configuration + @Bean | Third-party classes, conditional wiring, explicit control |
| @Import | Combining several @Configuration classes into one root config |
Common Mistakes
- Annotating a configuration class with @Component instead of @Configuration and expecting cross-bean method calls to still return a singleton — they will not, since CGLIB proxying only applies to true @Configuration classes.
- Forgetting to register the configuration class with AnnotationConfigApplicationContext, or forgetting to add @ComponentScan when mixing styles.
- Manually calling "new" on a dependency instead of injecting it through a constructor or another @Bean method.
- Duplicating bean names across configuration classes, causing one definition to silently override the other.
Best Practices
- Use @Bean for third-party classes and objects that need conditional or complex construction logic.
- Keep each @Configuration class focused on a single concern (data source config, security config, and so on).
- Prefer constructor parameters over field access when wiring dependencies inside @Bean methods.
- Name configuration classes clearly, such as DataSourceConfig or SecurityConfig, so their purpose is obvious.
Frequently Asked Questions
Yes, and most real projects do. Use @ComponentScan for your own classes and @Bean methods for anything that needs manual construction, such as third-party clients.
Spring generates a CGLIB subclass of the @Configuration class at runtime. That subclass intercepts method calls and returns the cached singleton from the container instead of running the method body again.
Almost never. Java-based configuration and component scanning cover the vast majority of use cases, and Spring Boot relies on them exclusively.
Key Takeaways
- @Configuration marks a class as a source of bean definitions.
- @Bean methods register the object they return with the container.
- Calling one @Bean method from another wires the beans together and still returns the shared singleton.
- AnnotationConfigApplicationContext bootstraps a container from one or more configuration classes.
- @Import combines multiple configuration classes into a single root configuration.
Summary
Java-based configuration gives you a type-safe, IDE-friendly way to describe your application's beans and how they connect. It works hand in hand with component scanning, which is the topic of the next lesson: letting Spring discover your own classes automatically instead of registering every one by hand.