Annotations in Spring Boot empower you to efficiently manage your application’s beans and dependencies. 

Whether you’re new to Spring Boot or looking to deepen your understanding, this comprehensive guide will walk you through common bean annotations with practical examples. 

You’ll learn how to effectively use annotations like @Bean, @Component, @Autowired, and more to control bean lifecycle, scope, and dependencies. 

Let’s explore these powerful features with hands-on examples that will help you write cleaner, more maintainable Spring Boot applications.

What is Spring Bean ?

A Spring bean is simply a Java object that’s instantiated, assembled, and managed by Spring’s IoC (Inversion of Control) container. 

Beans form the backbone of your Spring applications, as they represent the various components and services that make up your application.

In Spring, beans can be created using XML configuration and with annotations.

Types of Spring Boot Bean Annotations

Some of the most commonly used Spring Boot Bean annotations help you manage dependencies and configure your application effectively. 

Here are the main types:

  • Class-Level annotations
    Help you define Spring-managed components at the class level.
    You can use these annotations to mark your classes for automatic component scanning. 
    Examples: @Component, @Service, @Repository , @Configuration.
  • Method-Level annotations
    Allow you to declare beans programmatically within configuration classes. 
    These annotations give you control over bean lifecycle and configuration.
    Examples: @Bean, @PostConstruct and @PreDestroy.
  • Field-Level annotations
    Enable dependency injection and property configuration in your Spring Boot applications.
    Examples: @Autowired, @Qualifier, @Value.

All these annotations are discussed in the following sections.

Bean Configuration Annotations

Spring’s component scanning mechanism identifies and registers beans in your application context. 

Spring provides several stereotype annotations. These annotations, if written at class level register those classes as spring beans.

@Component
Generic stereotype for Spring-managed components.

@Service
Used for service layer classes. Indicates that the class contains business logic implementation.

@Repository
Used for persistence layer classes. Marks classes that handle data persistence.
Usually applied over interfaces in Spring data JPA implementation.

@Controller
Used for presentation layer classes. Handles web requests in MVC pattern.

@RestController
Combines @Controller and @ResponseBody .

@Service, @Respository, @Controller, @RestController annotations are specialized form of @Component and are used to identify the purpose of your classes.
Using @Component in place of these will also work but not considered a good practice.

Knowing these stereotypes helps you organize your application’s structure effectively.

@Bean and @Configuration

@Configuration annotation is applied at class level and indicates that the class is a source of bean definitions.

@Bean annotation is used to create beans within @Configuration classes. 

Here’s a simple example:

@Configuration 
public class AppConfig { 
  @Bean 
  public UserService userService() { 
    return new UserServiceImpl(); 
  } 
}

Another important aspect of @Bean annotation is its ability to manage dependencies between beans. 

You can specify initialization and destruction methods, define scope, and handle conditional creation as discussed below.

@Scope

Any bean you create in Spring has a scope that determines its lifecycle. 

The default scope is singleton, but you can modify it using @Scope annotation:

@Bean 
@Scope("prototype") 
public UserService userService() { 
  return new UserServiceImpl(); 
}

With different scope options available, you can control how Spring manages your beans’ lifecycles. 

Common scopes include:

a. singleton (default)
One instance per Spring container.
b. prototype 
New instance every time requested. 
c. request
One instance per HTTP request. 
d. session
One instance per HTTP session

Dependency Injection Annotations

@Autowired

If you’re working with Spring’s dependency injection, @Autowired is used for automatically wiring beans together. 

You can apply it to fields, constructors or setter methods. 

Here’s how you can implement it:

@Service 
public class UserService { 
  @Autowired 
  private UserRepository userRepository; 
 
  // Constructor injection (recommended) 
  @Autowired 
  public UserService(UserRepository userRepository) { 
    this.userRepository = userRepository; 
  } 
}

@Qualifier

Assuming you have multiple beans of the same type, you can use @Qualifier to specify which exact bean you want to inject. 

This helps Spring resolve ambiguity in autowiring candidates.

Plus, you can combine @Qualifier with @Autowired to be more specific about your dependencies. Here’s an example:

@Service 
public class NotificationService { 
  @Autowired 
  @Qualifier("emailNotifier") 
  private NotificationSender notificationSender; 
}

@Resource vs @Inject

Autowired isn’t your only option for dependency injection in Spring. @Resource is a Jakarta EE annotation that performs injection by name first, while @Inject is a Java CDI annotation similar to @Autowired but with slight differences in functionality.

Understanding the differences between these annotations can help you make better choices in your code. 

@Resource looks up by name first, then by type, while @Inject and @Autowired look up by type first. Here’s how you can use them:

public class MessageService { 
  @Resource(name = "messageFormatter") 
  private Formatter formatter; 

  @Inject 
  private MessageRepository repository; 
}

Bean Lifecycle Annotations

After creating Spring beans, you need to manage their lifecycle effectively. 

Spring provides specific annotations to handle initialization and cleanup operations. 

Understanding these lifecycle annotations helps you maintain clean and efficient code while ensuring proper resource management.

@PostConstruct

Now you can use @PostConstruct to execute code right after dependency injection is complete but before the bean is put into service. 

Here’s how you implement it:

@Component 
public class UserService { 

  @PostConstruct 
  public void init() { 
    System.out.println("Initializing user configurations…"); 
    // Your initialization code here 
  } 
}

@PreDestroy

Some cleanup operations need to be performed before a bean is removed from the container. 

You can use @PreDestroy to handle these scenarios:

@Component 
public class DatabaseService { 

  @PreDestroy 
  public void cleanup() { 
    System.out.println("Closing database connections…"); 
    // Your cleanup code here 
  } 
}

To ensure proper resource management, you can use @PreDestroy to close connections, release resources, or save state. 

This annotation is particularly useful when working with database connections, file handlers, or other system resources that need proper cleanup.

Initialization and Destruction Callbacks

On top of annotation-based lifecycle management, you can also define initialization and destruction methods in your bean configuration:

@Configuration 
public class AppConfig { 
  @Bean(initMethod = "start", destroyMethod = "stop") 
  public MyService myService() { 
    return new MyService(); 
  } 
}

Another approach to managing bean lifecycle is through the InitializingBean and DisposableBean interfaces. 

These provide alternative ways to handle initialization and cleanup, but annotations are generally preferred for their simplicity and cleaner code structure.

Conditional Bean Registration

To implement flexible and environment-aware applications, Spring Boot offers powerful conditional bean registration capabilities. 

You can control when beans are created based on various conditions, making your application more adaptable to different environments and configurations.

While working with Spring Boot, you can use various built-in conditional annotations like @ConditionalOnClass, @ConditionalOnMissingBean or @ConditionalOnProperty

Here’s a simple example:

@Configuration 
public class DatabaseConfig { 
  @Bean 
  @ConditionalOnProperty(name = "database.type", havingValue = "mysql") 
  public DataSource mysqlDataSource() { 
    return new MySQLDataSource(); 
  } 
}

Bean Ordering and Priority

@Order

@Order annotation helps you control the sequence in which your Spring beans are loaded and executed. 

You can specify the order using a numerical value where lower numbers indicate higher priority. Here’s a basic example:

@Component 
@Order(1) 
public class FirstService { 
  // implementation 
} 

@Component 
@Order(2) 
public class SecondService { 
  // implementation 
}

@Priority

@Priority annotation provides an alternative way to define bean ordering in your Spring applications. 

Unlike @Order, @Priority is a standard Jakarta annotation that works across different frameworks.

Ordering with @Priority follows similar principles to @Order, but it’s particularly useful when you’re working with Jakarta EE applications or when you need cross-framework compatibility. 

Here’s how you can implement it:

@Component 
@Priority(1) 
public class HighPriorityService { 
  // implementation 
} 

@Component 
@Priority(2) 
public class LowPriorityService { 
  // implementation 
}

Profile-Based Bean Configuration

Spring Boot’s profile mechanism offers you a powerful way to maintain different configurations for various environments. 

This feature allows you to activate different beans based on specific profiles, making your application adaptable to different deployment scenarios.

@Profile

Clearly, you can control bean creation using the @Profile annotation. Here’s how you can define beans for specific profiles:

@Configuration 
public class DatabaseConfig { 
  @Bean 
  @Profile("development") 
  public DataSource devDataSource() { 
    return new EmbeddedDatabaseBuilder().
               setType(EmbeddedDatabaseType.H2).
               build(); 
  } 

  @Bean 
  @Profile("production") 
  public DataSource prodDataSource() { 
    return new BasicDataSource(); 
  } 
}

Environment-Specific Beans

You can create separate beans for different environments like development, testing, and production, ensuring your application behaves appropriately in each context.

Configuration of environment-specific beans becomes straightforward when you understand profile-based activation. 

You can define multiple beans of the same type with different profiles, and Spring will instantiate only the ones matching your active profile:

@Component 
@Profile("development") 
public class DevEmailService implements EmailService { 
  // Development implementation 
} 

@Component 
@Profile("production") 
public class ProdEmailService implements EmailService { 
  // Production implementation 
}

Lazy Loading and Performance

By default, Spring creates singleton beans eagerly during context initialization, while @Lazy defers bean creation until first use.

You can use the @Lazy annotation at either class or method level. 

Here’s a simple example: 

@Configuration 
public class AppConfig { 
  @Lazy 
  @Bean 
  public ExpensiveService expensiveService() { 
    return new ExpensiveService(); 
  } 
}

This ensures your bean is only initialized when it’s first requested, rather than at application startup.

Best Practices for Bean Management

Many developers struggle with maintaining clean and efficient bean management in Spring Boot applications. 

Following these best practices will help you create more maintainable and robust applications while avoiding common pitfalls in bean configuration and management.

Naming Conventions

Bean names should follow clear and consistent naming patterns. 

You should use camelCase for method names when declaring beans, and ensure names reflect their functionality. 

For example: 

@Bean 
public UserService userService() { 
  return new UserServiceImpl(); 
}

Dependency Management

Conventions for managing dependencies should prioritize constructor injection over field injection. 

You can improve your code’s testability and make dependencies explicit by using constructor injection:

A well-structured dependency management approach includes using appropriate annotations and following the Single Responsibility Principle. 

Here’s an example of proper constructor injection: 

@Service 
public class UserServiceImpl { 
  private final UserRepository userRepository; 
  
  @Autowired 
  public UserServiceImpl(UserRepository userRepository) { 
    this.userRepository = userRepository; 
  } 
}

Error Handling

For bean-related errors, you should implement proper exception handling using @ExceptionHandler and custom exceptions. 

This helps you manage bean creation and initialization failures gracefully:

Best practices for error handling include creating specific exception classes and using Spring’s error handling mechanisms. 

Here’s an example: 

@ControllerAdvice 
public class BeanExceptionHandler { 
 @ExceptionHandler(NoSuchBeanDefinitionException.class) 
 public ResponseEntity handleException(NoSuchBeanDefinitionException ex) { 
   return new ResponseEntity<>("Bean not found: " + 
                                ex.getMessage(), 
                                HttpStatus.INTERNAL_SERVER_ERROR); 
  } 
}

Summing up

In this article, we explored the important Spring Boot Bean annotations that will enhance your Java development workflow. 

From basic @Bean declarations to advanced configurations using @Lazy, @Scope and @DependsOn, you’ve learned how to manage your application’s components effectively. 

Using these annotations, you can now create more flexible and maintainable Spring Boot applications. 

Your next step is to apply these concepts in your projects to reinforce your learning.