When to use Java-Based Configuration and Annotation-Based Configuration in Spring?

In this blog post, let's dive into specific use-case scenarios to illustrate when you might opt for Java-based configuration (@Configuration and @Bean) versus annotation-based configuration (@Component and its derivatives):

Use Java-Based Configuration (@Configuration and @Bean)

Third-Party Library Integration

Scenario: You're integrating with a third-party library that provides a PaymentGateway class. This class isn't annotated with any Spring annotations, but you need to create and manage its lifecycle using Spring.

Solution: Use @Bean inside a @Configuration class to instantiate, configure, and return a PaymentGateway instance.

Example:
@Configuration
public class PaymentConfig {
    @Bean
    public PaymentGateway paymentGateway() {
        return new PaymentGateway(...);
    }
}
When integrating a third-party caching library like EhCache or a connection pool like HikariCP, you can't modify those classes to add @Component. So you use a @Bean method to define and configure these beans.

Dynamic Bean Creation Based on Conditions

Scenario: Depending on the environment or some runtime condition, you want to instantiate one of two possible implementations of a DatabaseConnector interface. 

Solution: Use conditional logic within a @Bean method to decide which implementation to return.

Example:

@Configuration
public class DatabaseConfig {
    @Bean
    public DatabaseConnector databaseConnector() {
        if (/* some condition */) {
            return new MySQLDatabaseConnector();
        } else {
            return new PostgreSQLDatabaseConnector();
        }
    }
}

Advanced Configuration Using Externalized Values

Scenario: You have a DataSource that you need to configure using values from an external properties file. 

Solution: In a @Configuration class, use @Value annotations to inject property values into a @Bean method to configure and return the DataSource.

Example:
@Configuration
public class DatabaseConfig {
    @Value("${db.url}")
    private String url;
    
    @Value("${db.username}")
    private String username;
    
    @Value("${db.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        BasicDataSource ds = new BasicDataSource();
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}

Few More Use Cases

Suppose you want to create different beans based on a profile (e.g., dev, test, prod) then you can use Java-based configuration.

When integrating a third-party caching library like EhCache or a connection pool like HikariCP, you can't modify those classes to add @Component. So you use a @Bean method to define and configure these beans.

If you need multiple instances of a RestTemplate with different configurations.
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplateOne() {
        return new RestTemplateBuilder()
                   .setConnectTimeout(Duration.ofSeconds(30))
                   .build();
    }
    
    @Bean
    public RestTemplate restTemplateTwo() {
        return new RestTemplateBuilder()
                   .setReadTimeout(Duration.ofSeconds(30))
                   .build();
    }
}

Use Annotation-Based Configuration (@Component and its derivatives) 

Automatic Component Scanning 

Scenario: You're building a standard CRUD application with multiple services and repositories. 

Solution: Annotate your service classes with @Service, your repository classes with @Repository, and enable component scanning. This will reduce the manual configuration overhead and will automatically detect, instantiate, and wire these components.

@Service
public class UserService {
    // ...
}

@Repository
public class UserRepository {
    // ...
}

Clear Semantic Roles in MVC Applications

Scenario: You're building a Spring MVC web application. 

Solution: Use @Controller or @RestController to annotate your web controller classes. This not only registers them as beans but also clearly indicates their role in the application.
@RestController
public class UserController {
    // ...
}

Autowiring Dependencies

Scenario: Your OrderService needs an instance of OrderRepository. 

Solution: Annotate OrderService with @Service and use @Autowired to automatically inject an instance of OrderRepository.

@Service
public class OrderService {
    private final OrderRepository repository;

    @Autowired
    public OrderService(OrderRepository repository) {
        this.repository = repository;
    }
    // ...
}

Conclusion

In summary, the choice between Java-based and annotation-based configurations is often influenced by the specific use case at hand. While annotation-based configuration is concise and suitable for auto-detection and wiring of components you define, Java-based configuration offers more control and flexibility, especially when dealing with third-party classes or complex instantiation logic.

In a real-world project, you'll most likely use a combination of both approaches, based on the specific needs and constraints you encounter.

Comments