LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 1317 min read

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 You Will Learn
  • 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.

EngineService.java
public class EngineService {
public String start() {
return "Engine started";
}
}
AppConfig.java
@Configuration
public 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).

CarService.java
public class CarService {
private final EngineService engineService;
public CarService(EngineService engineService) {
this.engineService = engineService;
}
public String drive() {
return engineService.start() + " -> Car is moving";
}
}
AppConfig.java
@Configuration
public 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.

Main.java
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());
}
}
Console Output

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.

RootConfig.java
@Configuration
@Import({ AppConfig.class, DataSourceConfig.class })
public class RootConfig {
}
ApproachBest For
@Component + @ComponentScanYour own application classes, discovered automatically
@Configuration + @BeanThird-party classes, conditional wiring, explicit control
@ImportCombining several @Configuration classes into one root config

Common Mistakes

Avoid These 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.

Next Lesson →

Component Scanning