Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 50

Programmatic Bean Definition with @Configuration and @Bean

Recommended resources

Hello! In our last lesson, we focused on how Spring automatically wires dependencies together using @Autowired, emphasizing constructor injection as the industry best practice. You now have a solid understanding of how Spring can discover your application's components using stereotype annotations like @Component and @Service, and then connect them to form a working application.

So far, we've relied on Spring to automatically scan our packages and find classes we've written and annotated. But what happens when you need Spring to manage an object from a class you don't control? For instance, a connection pool, a JSON mapper from a third-party library, or any class where you can't just add a @Component annotation to its source code.

This lesson introduces the solution: Java-based configuration. You will learn how to define beans explicitly and programmatically using the @Configuration and @Bean annotations. This gives you fine-grained control over bean creation and is a fundamental skill for integrating external libraries and managing complex configurations in enterprise applications.

1. The Basics: @Configuration and @Bean

The core idea behind Java-based configuration is to create special classes that act as a blueprint for the Spring container. Instead of Spring scanning for components, you explicitly tell it, "Here is a method that produces an object; please manage it as a bean."

  • @Configuration: This class-level annotation tells Spring that the class is a source of bean definitions. Spring will process this class at startup to find and register the beans you define within it.
  • @Bean: This method-level annotation is used inside a @Configuration class. It signals that the method instantiates, configures, and returns an object that should be managed by the Spring container.

By default, the name of the method becomes the name of the bean in the Spring container. The object returned by the method is the bean instance itself.

The following image provides a perfect high-level summary of this process.

This diagram shows a `@Configuration` class (`AppConfig`) with two `@Bean` methods. The `main` method of the application uses `AnnotationConfigApplicationContext` to load this configuration class, and then retrieves a bean (`ClientBean`) from the context that was created programmatically.

Let's walk through a concrete example to see this in action.

Spring @Configuration Annotation with Example

This article from GeeksforGeeks provides a clear, step-by-step example of moving from XML-based (or component-scan based) configuration to pure Java-based configuration.

First, read the introduction to understand the role of @Configuration. Next, look at Step 1 and the initial text of Step 2. The key idea is in this sentence: "So, what can we do...". This sets up the problem we are solving. Now, study the code in the sections for Step 2 and Step 3. This is the core of our lesson. Notice the CollegeConfig class is annotated with @Configuration and contains a collegeBean() method annotated with @Bean. Finally, look at Step 5. This shows how to start the Spring container using our Java configuration class with AnnotationConfigApplicationContext.

As you saw in the article, we created a College object and registered it as a bean without ever touching the College class itself. This is the power of programmatic configuration.


2. When to Use @Bean vs. @Component

You now know two ways to create beans: auto-detection with @Component and programmatic definition with @Bean. A common point of confusion for newcomers is knowing which one to choose. The distinction is crucial for writing clean and maintainable applications.

Dan Vega offers an excellent explanation that contrasts these two approaches with a very practical example.

Spring Beans Showdown: Unraveling the Mystery of @Component vs @Bean!

This video, "Spring Beans Showdown," clearly articulates the different use cases for @Component and @Bean.

First, watch this short segment to refresh your understanding of what a Spring Bean is: What is a Spring Bean?. Next, watch the segment on @Component. This recaps what you already know about component scanning and how annotations like @Service are specializations of @Component: @Component deep dive. Finally, and most importantly, watch the section on @Bean. Dan explains precisely when you would use it—for classes you don't control—and demonstrates it by creating a RestTemplate bean, which is a classic use case: @Bean explanation.

To summarize the key distinction:

  • @Component (and its stereotypes like @Service, @Repository, @Controller): Use this for your own application's classes. This is the primary mechanism. You are marking a class and letting Spring's component scanning automatically detect and register it. It's declarative and requires minimal configuration.

  • @Bean: Use this in a @Configuration class when:

    1. You need to create a bean for a third-party class that you cannot modify (e.g., RestTemplate, ObjectMapper, a DataSource).
    2. The creation logic for a bean is complex. A @Bean method can contain any Java code you need to construct and configure the object before returning it.

3. Wiring Programmatically Defined Beans

Just because a bean is defined in a @Configuration class doesn't mean it lives in isolation. These beans participate in dependency injection just like any other. You can inject beans into your @Bean methods, and you can inject beans created by @Bean methods into your other components.

Injecting Dependencies into a @Bean method

Let's say BeanA depends on BeanB. When defining BeanA programmatically, you can simply declare BeanB as a parameter to your @Bean method. Spring will see this and automatically inject the BeanB instance from the container.

This works just like the constructor injection we championed in the previous lesson.

@Configuration
public class AppConfig {

    @Bean
    public BeanB beanB() {
        return new BeanB();
    }

    @Bean
    public BeanA beanA(BeanB myBeanB) { // Spring injects the BeanB instance here
        return new BeanA(myBeanB);
    }
}

This powerful feature allows you to build up complex object graphs with clear, type-safe Java code.

Using the @Bean Annotation :: Spring Framework

The official Spring documentation provides a concise explanation of how dependencies are handled for @Bean methods.

Please read the section titled Bean Dependencies. Focus on the code example showing transferService requiring an AccountRepository. This demonstrates exactly the pattern described above.

Injecting a @Bean's result into another component

This is the most common scenario. You define a bean for a third-party class using @Bean, and then inject it into one of your services or repositories.

For example, let's stick with the RestTemplate example.

  1. You define the RestTemplate bean in a @Configuration class.
  2. You use standard constructor injection to inject it into your @Service.
// In your configuration file
@Configuration
public class WebConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

// In your service class
@Service
public class ExternalApiService {

    private final RestTemplate restTemplate;

    // Standard constructor injection works perfectly!
    // Spring finds the RestTemplate bean you defined in WebConfig.
    public ExternalApiService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public void fetchData() {
        // use the restTemplate...
    }
}

This demonstrates how seamlessly beans from different definition styles can be integrated. The ExternalApiService (found via component scanning) depends on RestTemplate (defined programmatically), and Spring's dependency injection handles the wiring automatically.


4. Example: Configuring Multiple Implementations

Another powerful use for @Bean is to control which implementation of an interface is used in your application. Imagine you have a PaymentService interface with implementations for CreditCardService and GooglePayService. Using @Bean, you can easily decide which one becomes the active bean for injection.

Understanding Beans and ApplicationContext in Spring Framework

The presenter in this CodeSnippet video provides an excellent demonstration of this concept.

Watch the segment from this timestamp. He creates a PaymentService interface, two implementing classes (GooglePayService, CreditCardService), and then uses a PaymentConfig class with @Bean methods to create beans for both. This shows how you can programmatically manage different implementations.

This technique gives you a single place in your configuration to swap out implementations, which is invaluable for feature flagging or adapting to different environments without changing your core business logic.


Conclusion

You have now learned the second major way of defining beans in Spring. While component scanning with @Component is your day-to-day tool for your own code, programmatic configuration with @Configuration and @Bean provides the control and flexibility needed for real-world enterprise applications that integrate with external systems.

Key Takeaways:

  • Use @Configuration on a class to mark it as a source of bean definitions.
  • Use @Bean on a method within a @Configuration class to define a single bean. The object returned from the method becomes the bean instance.
  • By default, the method name is the bean's name. You can override it with @Bean("custom-name").
  • Use @Component and its stereotypes for your own application code that can be auto-detected.
  • Use @Bean when you need to create beans from third-party classes or when the initialization logic is complex.
  • Dependencies can be injected into @Bean methods by adding them as method parameters.

In our next lesson, we will explore bean scopes. Now that we know two ways to create beans, we will learn about their lifecycle and how Spring manages their instances. You'll understand the crucial difference between the default singleton scope (one shared instance) and the prototype scope (a new instance for every request), which is vital for managing state correctly in your application.