9 Hidden Spring Boot 3 Annotations That Supercharge Your Apps

This article introduces nine often‑overlooked Spring Boot 3 annotations—including @ConditionalOnExpression, @ConstructorBinding, @EventListener, @Lazy, @EnableConfigurationProperties, @Profile, @Scheduled, @Scope, and @Retryable—explaining their purpose, showing practical code examples, and demonstrating how they improve configuration, immutability, event handling, circular‑dependency resolution, and fault tolerance.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
9 Hidden Spring Boot 3 Annotations That Supercharge Your Apps

1. Introduction

In everyday development we often master only the basic Spring Boot annotations and overlook powerful "treasure" annotations that can dramatically improve code quality, type safety, and flexibility. This article selects nine practical yet easily missed annotations covering conditional configuration, event‑driven programming, immutable configuration, and other core scenarios to help you write more elegant and robust code.

2. Practical Cases

2.1 @ConditionalOnExpression

Compared with the simpler @ConditionalOnProperty, @ConditionalOnExpression leverages SpEL to combine multiple factors such as properties and method results, enabling far more complex configuration logic.

@Configuration
@ConditionalOnExpression("${app.cache.enabled:false} and '${spring.profiles.active}' != 'test'")
public class CacheConfiguration {
  @Bean
  CacheManager cacheManager() {
    return new ConcurrentMapCacheManager();
  }
}

The example creates a conditional cache configuration that activates only when two conditions are met: ${app.cache.enabled:false}: checks whether caching is enabled in application.properties (defaults to false). '${spring.profiles.active}' != 'test': ensures the current environment is not the test profile.

You can also base the condition on other bean methods:

@Configuration
@ConditionalOnExpression("@featureChecker.isAdvancedFeatureAvailable()")
public class AdvancedFeatureConfig {
}

The configuration becomes active only when the featureChecker bean’s isAdvancedFeatureAvailable() returns true.

2.2 @ConstructorBinding for Immutable Configuration

Combining @ConfigurationProperties with @ConstructorBinding creates immutable configuration classes, enhancing security, thread‑safety, and maintainability.

@ConfigurationProperties(prefix = "pack.app")
@ConstructorBinding
public class AppProperties {
  private final Boolean enabled;
  private final String apiKey;
  private final String title;
  private final String version;
  public AppProperties(Boolean enabled, String apiKey, String title, String version) {
    this.enabled = enabled;
    this.apiKey = apiKey;
    this.title = title;
    this.version = version;
  }
  // getters only
}
@ConstructorBinding

ensures all properties are set during object construction.

Using final fields prevents accidental modification after initialization. List.copyOf() can create immutable copies of collections.

Absence of setters guarantees immutability.

2.3 @EventListener for Advanced Event Handling

@EventListener

offers sophisticated event‑driven capabilities, allowing conditional processing based on event attributes.

@Component
public class OrderProcessorListener {

  @EventListener(condition = "#event.total > 1000")
  @Order(1)
  public void processLargeOrder(OrderCreatedEvent event) {
    logger.info("处理订单【{}】, 总金额: {}", event.getOrderId(), event.getTotal());
    // TODO
  }
}

The condition attribute filters events so that only orders with a total amount greater than 1000 are processed.

2.4 @Lazy to Resolve Circular Dependencies

@Service
public class CartService {
  private final OrderService orderService;
  public CartService(@Lazy OrderService orderService) {
    this.orderService = orderService;
  }
  public void checkout(Long cartId) {
    // handle checkout
    orderService.createOrder(cartId);
  }
}

@Service
public class OrderService {
  private final CartService cartService;
  public OrderService(CartService cartService) {
    this.cartService = cartService;
  }
  public void cancelOrder(Long orderId) {
    // restore cart
    cartService.restoreCart(orderId);
  }
}

CartService depends on OrderService and vice versa. @Lazy creates a proxy to break the circular dependency.

The actual OrderService bean is instantiated only on first use.

Use cautiously; circular dependencies often indicate design issues.

2.5 @EnableConfigurationProperties for Automatic Property Binding

@EnableConfigurationProperties({DatabaseProperties.class, CacheProperties.class})
@Configuration
public class AppConfig {
  @Bean
  DataSource dataSource(DatabaseProperties props) {
    return DataSourceBuilder.create()
      .url(props.getUrl())
      .username(props.getUsername())
      .password(props.getPassword())
      .build();
  }
}
@EnableConfigurationProperties

registers multiple property classes at once.

Properties are automatically injected into beans that need them.

Provides a centralized location for configuration management.

2.6 @Profile for Configuration Expressions

@Bean
@Profile("prod")
public DataSource prodDataSource() {
  return new ProductionDataSource();
}

@Bean
@Profile("dev | test")
public DataSource devDataSource() {
  return new EmbeddedDataSource();
}
@Profile("prod")

activates when spring.profiles.active=prod. @Profile("dev | test") activates when the active profile is either dev or test.

These annotations decide which configuration beans are loaded based on the spring.profiles.active property.

2.7 Task Scheduling Configuration

@Configuration
public class SchedulingConfig {
  @Scheduled(cron = "${jobs.cleanup.schedule}")
  public void databaseCleanup() {
    // cleanup logic
  }

  @Scheduled(fixedDelayString = "${jobs.health.interval}")
  public void healthCheck() {
    // health check logic
  }
}

Both methods can be configured via external properties, allowing different schedules per environment without code changes.

2.8 @Scope for Request‑Scoped Beans

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserContext {
  private User currentUser;
  public void setCurrentUser(User user) {
    this.currentUser = user;
  }
  public User getCurrentUser() {
    return currentUser;
  }
}

A new instance is created for each HTTP request. proxyMode ensures proper isolation when injected into singleton beans.

Ideal for storing user‑specific data during request processing.

The instance is automatically cleared after the request completes.

2.9 @Retryable for Automatic Retries

@Service
public class PaymentGatewayService {
  @Retryable(
    value = {TimeoutException.class},
    maxAttempts = 3,
    backoff = @Backoff(delay = 1000, multiplier = 2)
  )
  public PaymentResult processPayment(Payment payment) {
    // TODO
  }

  @Recover
  public PaymentResult handlePaymentFailure(TimeoutException e, Payment payment) {
    return PaymentResult.failed(payment.getId());
  }
}

Automatically retries on TimeoutException up to three attempts.

Uses exponential backoff (1 s, 2 s, 4 s). @Recover provides fallback logic when retries are exhausted.

Dependency

<dependency>
  <groupId>org.springframework.retry</groupId>
  <artifactId>spring-retry</artifactId>
</dependency>
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentconfigurationSpring BootAnnotationsCode examples
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.