Featured resource
Tech Upskilling Playbook 2025
Tech Upskilling Playbook

Build future-ready tech teams and hit key business milestones with seven proven plays from industry leaders.

Learn more
  • Labs icon Lab
  • Core Tech
Labs

Guided: Spring Certified Professional - Java Configuration and Bean Management

This code lab will teach you how to configure Spring applications using Java-based annotations. You'll practice defining Spring beans, composing configurations across multiple files, and managing dependencies, including handling multiple beans of the same type.

Labs

Path Info

Level
Clock icon Intermediate
Duration
Clock icon 59m
Last updated
Clock icon Aug 29, 2025

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Introduction

    Welcome to the lab Guided: Spring Certified Professional - Java Configuration and Bean Management.

    Spring Framework provides a powerful way to configure applications using Java-based configuration. This approach leverages annotations to define beans and their relationships, making your configuration type-safe and refactoring-friendly.

    In this lab, you'll work with an application that simulates a Smart Home system that includes the following functionality:

    • Defining smart home devices (light, thermostat, security camera) as Spring beans
    • Defining automation rules (Schedule, Trigger) as Spring beans
    • Wiring these devices and rule beans into a central HomeAutomationService
    • Initializing the Spring application context using Java configuration
    • Retrieving and using beans from the context, including handling multiple beans of the same type

    By the end of the lab, you'll understand how to leverage Spring's Java configuration to build modular, maintainable, and type-safe applications. ---

    Familiarizing with the Program Structure

    The application includes the following classes in the src/main/java directory:

    • com.smarthome.device.Device: An interface representing any device in the smart home system.
    • com.smarthome.device.Light, com.smarthome.device.Thermostat, and com.smarthome.device.SecurityCamera: Concrete classes implementing the Device interface.
    • com.smarthome.automation.Rule: An interface for automation rules.
    • com.smarthome.automation.Schedule and com.smarthome.automation.Trigger: Concrete classes implementing the Rule interface.
    • com.smarthome.service.HomeAutomationService: The core service that orchestrates devices and rules.
    • com.smarthome.config.AppConfig, com.smarthome.config.DeviceConfig, and com.smarthome.config.AutomationConfig: Configuration classes that you'll complete to define and wire beans.
    • com.smarthome.Main: The application entry point that bootstraps the Spring context.

    The device and automation classes, as well as the HomeAutomationService, are fully implemented. You'll focus on completing the configuration classes (AppConfig, DeviceConfig, and AutomationConfig) and the Main class to properly initialize and use the Spring context.

    You can compile and run the application using the Run button. Initially, the application will compile but won't work correctly until you complete the configuration.

    Begin by examining the code to understand the program's structure. Once you're ready, start implementing the Spring configuration step by step.

    info> If you need help, a solution directory is available for reference, containing subdirectories for each step with solution files following the naming convention [step]-[task]-[file].java (e.g., 2-1-AppConfig.java in the step2 directory).

  2. Challenge

    Declaring Configuration Classes and Beans

    Spring Framework provides two primary annotations to configure your application using Java-based configuration instead of XML: @Configuration and @Bean.

    The @Configuration Annotation

    The @Configuration annotation marks a class as a source of bean definitions. It tells Spring that this class contains methods that will be used to instantiate and configure beans in the Spring application context:

    @Configuration
    public class MyApplicationConfig {
      // Bean definitions go here
    }
    

    The @Bean Annotation

    The @Bean annotation is used on methods within a @Configuration class to define beans. When Spring processes your configuration, it will call these methods to create the beans and register them in the application context.

    @Configuration
    public class MyApplicationConfig {
      @Bean
      public MyService myService() {
        return new MyServiceImpl();
      }
    }
    

    When you define a bean, Spring:

    1. Calls the annotated method to create the bean instance
    2. Registers the returned object in the Spring application context
    3. Makes the bean available for dependency injection into other beans

    By default, the bean name is the same as the method name. You can customize this by specifying a name in the annotation: @Bean("customBeanName").

    Now you're ready to start implementing the Spring configuration for the Smart Home application. You'll start with the @Configuration annotation. --- Now, you'll add the @Bean annotation to the methods that create Spring beans.

  3. Challenge

    Qualifying Multiple Beans of the Same Type

    When working with Spring applications, you may encounter situations where you need multiple beans of the same type. In such cases, Spring needs a way to distinguish between these beans when injecting them into other components.

    Using @Qualifier for Bean Identification

    The @Qualifier annotation helps Spring identify which specific bean to inject when multiple beans of the same type exist. It works in two parts:

    1. Bean Definition: You add @Qualifier with a name to the @Bean method
    2. Injection Point: You use the same @Qualifier at the injection point to specify which bean you want
    // Bean definition
    @Bean
    @Qualifier("primaryDs")
    public DataSource primaryDataSource() {
      return new DataSource("main-db");
    }
    
    @Bean
    @Qualifier("backupDs")
    public DataSource backupDataSource() {
      return new DataSource("backup-db");
    }
    
    // Injection point
    @Autowired
    @Qualifier("primaryDs")
    private DataSource dataSource;
    

    Without qualifiers, Spring would throw a NoUniqueBeanDefinitionException when trying to autowire a type with multiple bean definitions.

    Next, you'll implement qualified beans for the Smart Home lights.

  4. Challenge

    Composing Configuration Files

    As applications grow, organizing configuration becomes increasingly important. Spring allows you to modularize your configuration by splitting it across multiple files and then combining them when needed.

    Composing Configuration with @Import

    The @Import annotation allows you to import bean definitions from other configuration classes. This promotes modularity and separation of concerns in your configuration:

    @Configuration
    @Import({DatabaseConfig.class, SecurityConfig.class})
    public class MainConfig {
      // Additional bean definitions
    }
    

    These are the benefits of using @Import:

    • Modularity: Group related configurations together
    • Reusability: Import the same configuration in multiple places
    • Clarity: Keep configuration files focused on specific concerns
    • Maintainability: Easier to manage smaller, focused configuration files

    When Spring processes a configuration class with @Import, it processes all the imported configurations as well, making their beans available in the same application context.

    Next, you'll use @Import to compose the Smart Home configuration.

  5. Challenge

    Wiring Beans

    One of the key features of Spring is dependency injection, which allows you to wire beans together. In Java-based configuration, you can inject dependencies by having them as parameters in your @Bean methods.

    Wiring Beans in Java Configuration

    When a @Bean method has parameters, Spring automatically looks for beans of those types in the application context and provides them as arguments when calling the method.

    @Configuration
    public class ServiceConfig {
      @Bean
      public OrderService orderService(
          PaymentService paymentService, 
          InventoryService inventoryService
      ) {
        return new OrderServiceImpl(paymentService, inventoryService);
      }
    }
    

    In this example, Spring will:

    1. Find beans of type PaymentService and InventoryService in the context
    2. Pass them as arguments when calling the orderService() method
    3. Register the returned OrderService bean in the context

    When multiple beans of the same type exist, you can use @Qualifier annotations on the parameters to specify which bean to inject:

    @Bean
    public ReportService reportService(
        @Qualifier("primaryDs") DataSource primaryDs, 
        @Qualifier("archiveDs") DataSource archiveDs
    ) {
      return new ReportServiceImpl(primaryDs, archiveDs);
    }
    

    Next, you'll wire the Smart Home beans together to create the HomeAutomationService.

  6. Challenge

    Initializing the Spring Context

    Now that you have defined the configuration classes and beans, you need to bootstrap the Spring application context. For Java-based configuration, Spring provides the AnnotationConfigApplicationContext class.

    Initializing the Spring Context

    The AnnotationConfigApplicationContext is used to create and configure a Spring application context from Java-based configuration classes:

    // Create a new Spring context using a configuration class
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
    
    // You can also register multiple configuration classes
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(Config.class);
    context.register(AdditionalConfig.class);
    context.refresh(); // Must call refresh() after registering configurations manually
    

    Some important aspects of AnnotationConfigApplicationContext are:

    • It scans the provided configuration classes for @Bean methods and other Spring annotations
    • It processes @Import annotations to include additional configurations
    • It creates and wires all the beans according to your configuration
    • It manages the lifecycle of these beans

    Remember to close the context when you're done with it to release resources. Either in the finally block of a traditional try statement:

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    try {
      // Use the context...
    } finally {
      context.close();
    }
    

    Or by using a try-with-resources statement (AnnotationConfigApplicationContext implements the AutoCloseable interface):

    try (AnnotationConfigApplicationContext context = 
        new AnnotationConfigApplicationContext(Config.class)) {
      // Use the context...
    } // Context is automatically closed when the try block exits
    

    Next, you'll initialize the Spring context for the Smart Home application.

  7. Challenge

    Retrieving and Using Beans From Context

    Once you have initialized the Spring application context, you can retrieve beans from it to use in your application. The context provides several methods to access beans.

    Retrieving Beans from the Context

    The most common ways to retrieve beans from the Spring context are:

    1. By Type: When you know the bean's type but not its name, or when there's only one bean of that type:

      UserService userService = context.getBean(UserService.class);
      
    2. By Name and Type: When multiple beans of the same type exist and you need a specific one:

      DataSource primaryDs = context.getBean("primaryDataSource", DataSource.class);
      
    3. By Name: When you only know the bean's name (not recommended as it requires casting):

      Object bean = context.getBean("userService");
      UserService userService = (UserService) bean;
      

    When you annotate a component with, for example, @Qualifier("foo"), "foo" serves as disambiguation metadata, so you can retrieve the qualified bean by its qualifier. However, the qualifier does not become the bean name unless you explicitly give that name to the bean (for example with @Bean("foo")).

    Next, you'll retrieve the beans from the Smart Home application context.

  8. Challenge

    Conclusion

    Congratulations on successfully completing this Code Lab!

    You can compile and run the application either by clicking the Run button in the bottom-right corner of the screen or by using the Terminal with the following commands:

    1. Compile and package the application:

      mvn clean package
      
    2. Run the application:

      java -jar target/smarthome-hub-1.0-SNAPSHOT-jar-with-dependencies.jar
      

    When you run application, you should see messages indicating the following sequence of events:

    1. The HomeAutomationService starts its runAutomation process.
    2. All configured devices (living room light, bedroom light, thermostat, security camera) are turned ON.
    3. The defined Schedule and Trigger rules are executed, printing their descriptions.
    4. The thermostat's temperature is adjusted, and the security camera simulates recording.
    5. Finally, all devices are turned OFF as the automation sequence concludes.
    6. After the main automation, separate messages will confirm that the specifically qualified Light beans (livingRoomLight and bedroomLight) were successfully retrieved from the context and their turnOn/turnOff methods were invoked individually.

    Seeing this output confirms that your Spring configuration correctly defined all the beans, handled qualified beans, imported configurations, injected dependencies into the HomeAutomationService, and allowed retrieval of specific beans from the application context. ---

    Extending the Program

    Here are some ideas to further enhance your skills and extend the application's capabilities:

    1. Bean Lifecycle Callbacks: Add methods annotated with @PostConstruct and @PreDestroy to one or more device classes (e.g., Thermostat). The @PostConstruct method could print a message like "[Thermostat Name] Initializing..." and the @PreDestroy method could print "[Thermostat Name] Shutting down...". Observe when these messages appear in the console output relative to the application startup and shutdown (when the context closes).

    2. Conditional Configuration with Profiles: Define two profiles, for example, "simple" and "advanced". Use the @Profile("simple") annotation on the Thermostat bean definition in DeviceConfig and @Profile("advanced") on the SecurityCamera bean definition. In AutomationConfig, add @Profile("advanced") to the Trigger bean definition. Modify Main.java to create the context without arguments, set the active profile, register your @Configuration classes, and then refresh. Run with different active profiles and observe which beans are created and injected into HomeAutomationService (you'll need to define two service beans, one for each profile, or make dependencies optional for this).

    3. Component Scanning Alternative: Refactor the AutomationConfig to use component scanning. Remove the @Bean methods. Annotate Schedule and Trigger classes with @Component (or a more specific stereotype like @Service if appropriate). Add @ComponentScan(basePackages = "com.smarthome.automation") to AutomationConfig (or AppConfig). Verify the application still works as expected to demonstrate this alternative approach to bean discovery.

    By implementing these enhancements, you'll gain a deeper understanding of Java Configuration and Bean Management with Spring. ---

    Related Courses in Pluralsight's Library

    If you'd like to continue building your Spring skills or explore related topics, check out these courses available on this path:

    These courses cover a wide range of topics. Explore them to further your learning journey in Spring!

Esteban Herrera has more than twelve years of experience in the software development industry. Having worked in many roles and projects, he has found his passion in programming with Java and JavaScript. Nowadays, he spends all his time learning new things, writing articles, teaching programming, and enjoying his kids.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.