10 Essential Spring Boot Features Every Developer Should Master
This comprehensive guide explores the most powerful Spring Boot capabilities—including @Conditional, @ConfigurationProperties, Actuator, DevTools, Retry, Cache, testing strategies, custom starters, Admin, and CLI—providing code examples and deep analysis to help developers boost productivity, reliability, and maintainability of their applications.
Previous Popular Articles
1. Why most programmers avoid independent development?
2. JDK8 writes 10 lines, JDK17 writes 1 line – why still use it?
3. Spring officially deprecates RestTemplate
4. Lombok caused serious bugs – very risky!
5. Will Meituan's interview format become a new trend?
6. Why BigDecimal can guarantee precision without loss?
7. How to serialize JSON with dynamic fields in Java?
8. Linux disguised as Windows 11 – stunning!
9. Never use the word "User" in your code!
10. Goodbye Jenkins – a more suitable Chinese CI tool!
1. @Conditional Annotation
Sometimes different environments need to load different Bean configurations. The traditional approach uses @Profile, but @Conditional provides more flexible control.
@Configuration
public class DataSourceConfig {
@Bean
@Conditional(ProdDataSourceCondition.class)
public DataSource prodDataSource() {
// Production datasource configuration
return DataSourceBuilder.create()
.url("jdbc:mysql://prod-host:3306/app")
.username("prod-user")
.password("prod-pass")
.build();
}
@Bean
@Conditional(DevDataSourceCondition.class)
public DataSource devDataSource() {
// Development datasource configuration
return DataSourceBuilder.create()
.url("jdbc:h2:mem:testdb")
.username("sa")
.password("")
.build();
}
}
class ProdDataSourceCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String env = context.getEnvironment().getProperty("app.env");
return "prod".equals(env);
}
}
class DevDataSourceCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String env = context.getEnvironment().getProperty("app.env");
return "dev".equals(env) || env == null;
}
}Deep Analysis : @Conditional’s core value lies in implementing "conditional configuration", the cornerstone of Spring Boot auto‑configuration.
By implementing the Condition interface, beans can be loaded based on any condition such as environment variables, system properties, classpath presence, or existing beans.
2. @ConfigurationProperties
Injecting configuration properties one by one with @Value is cumbersome; @ConfigurationProperties offers a more elegant solution.
@Component
@ConfigurationProperties(prefix = "app.datasource")
@Validated
public class DataSourceProperties {
@NotBlank
private String url;
@NotBlank
private String username;
private String password;
@Min(1)
@Max(100)
private int maxPoolSize = 10;
private Duration connectionTimeout = Duration.ofSeconds(30);
private Pool pool = new Pool();
public static class Pool {
private int maxSize = 20;
private int minIdle = 5;
// getters and setters omitted
}
// getters and setters omitted
}
# application.yml
app:
datasource:
url: jdbc:mysql://localhost:3306/app
username: root
password: secret
max-pool-size: 20
connection-timeout: 60s
pool:
max-size: 50
min-idle: 10Deep Analysis : @ConfigurationProperties provides type‑safe binding, supports nested properties, collections, validation, and relaxed binding (e.g., kebab‑case to camelCase).
This embodies Spring Boot’s "convention over configuration" philosophy.
3. Spring Boot Actuator
Production monitoring is essential for system stability; Actuator offers ready‑to‑use monitoring endpoints.
@Configuration
public class ActuatorConfig {
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
@Autowired
private DataSource dataSource;
@Override
public Health health() {
try (Connection conn = dataSource.getConnection()) {
if (conn.isValid(1000)) {
return Health.up()
.withDetail("database", "Available")
.withDetail("validationQuery", "SUCCESS")
.build();
}
} catch (SQLException e) {
return Health.down(e)
.withDetail("database", "Unavailable")
.withDetail("error", e.getMessage())
.build();
}
return Health.unknown().build();
}
}
@Component
public class OrderMetrics {
private final Counter orderCounter;
private final DistributionSummary orderAmountSummary;
public OrderMetrics(MeterRegistry registry) {
this.orderCounter = Counter.builder("order.count")
.description("Total number of orders")
.register(registry);
this.orderAmountSummary = DistributionSummary.builder("order.amount")
.description("Order amount distribution")
.baseUnit("USD")
.register(registry);
}
public void recordOrder(Order order) {
orderCounter.increment();
orderAmountSummary.record(order.getAmount().doubleValue());
}
}
}
# application.yml (management endpoints)
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
show-components: always
metrics:
enabled: trueDeep Analysis : Actuator is more than a monitoring tool; it provides full observability through health checks, metrics, audit events, and HTTP tracing, enabling a complete monitoring system.
4. Spring Boot DevTools
Manual restarts are tedious; DevTools offers an extreme development experience with hot reload.
# application-dev.yml
spring:
devtools:
restart:
enabled: true
exclude: static/**,public/**
additional-paths: src/main/java
livereload:
enabled: true
# Thymeleaf and FreeMarker cache disabled for hot reload
spring:
thymeleaf:
cache: false
freemarker:
cache: false
@Component
public class CustomRestartTrigger implements ApplicationListener<ClassPathChangedEvent> {
private final RestartScope restartScope;
public CustomRestartTrigger(RestartScope restartScope) {
this.restartScope = restartScope;
}
@Override
public void onApplicationEvent(ClassPathChangedEvent event) {
if (event.getChangeSet().isModified()) {
restartScope.clear();
System.out.println("Detected classpath change, preparing restart...");
}
}
}Deep Analysis : DevTools uses class‑loader tricks for fast restarts, provides LiveReload, global configuration, and development‑time property overrides, dramatically improving development efficiency.
5. Spring Retry
In distributed systems, network glitches and temporary service outages are common. Spring Retry offers declarative retry solutions.
@Service
public class PaymentService {
@Retryable(value = {PaymentException.class, NetworkException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2))
public PaymentResult processPayment(PaymentRequest request) {
// Call payment gateway
return paymentGateway.process(request);
}
@Recover
public PaymentResult recover(PaymentException e, PaymentRequest request) {
log.error("Payment processing failed, entering recovery", e);
return PaymentResult.failed("Payment temporarily unavailable");
}
}
@Configuration
@EnableRetry
public class RetryConfig {
@Bean
public RetryTemplate retryTemplate() {
return RetryTemplate.builder()
.maxAttempts(5)
.exponentialBackoff(1000, 2, 10000)
.retryOn(RemoteAccessException.class)
.traversingCauses()
.build();
}
}Deep Analysis : Spring Retry’s core lies in its flexible retry strategies and back‑off mechanisms, allowing developers to handle transient failures declaratively.
6. Spring Cache
Manual cache management is error‑prone; Spring Cache provides a unified abstraction.
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
@Primary
public CacheManager redisCacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withInitialCacheConfigurations(Collections.singletonMap("users",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1))))
.transactionAware()
.build();
}
@Bean
public CacheManager caffeineCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(Duration.ofMinutes(10))
.maximumSize(1000));
return cacheManager;
}
}
@Service
public class UserService {
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
@Cacheable(value = "users", key = "#username", cacheManager = "caffeineCacheManager")
public User getUserByUsername(String username) {
return userRepository.findByUsername(username);
}
@CacheEvict(value = "users", key = "#user.id")
public void updateUser(User user) {
userRepository.save(user);
}
@Caching(evict = {
@CacheEvict(value = "users", key = "#user.id"),
@CacheEvict(value = "users", key = "#user.username")
})
public void deleteUser(User user) {
userRepository.delete(user);
}
}Deep Analysis : Spring Cache offers a unified abstraction layer, allowing seamless switching between implementations such as Redis, Caffeine, or Ehcache while keeping business code clean.
7. Spring Boot Test
Testing guarantees code quality; Spring Boot Test provides comprehensive testing support.
// 1. Unit test – no Spring context
@ExtendWith(MockitoExtension.class)
class UserServiceUnitTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void shouldReturnUserWhenExists() {
// given
User expected = new User(1L, "john");
when(userRepository.findById(1L)).thenReturn(Optional.of(expected));
// when
User actual = userService.getUserById(1L);
// then
assertThat(actual).isEqualTo(expected);
verify(userRepository).findById(1L);
}
}
// 2. Slice test – only part of the container
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void shouldFindByUsername() {
// given
User user = new User(null, "john", "[email protected]");
entityManager.persistAndFlush(user);
// when
User found = userRepository.findByUsername("john");
// then
assertThat(found.getEmail()).isEqualTo("[email protected]");
}
}
// 3. Integration test – full container
@SpringBootTest
@ActiveProfiles("test")
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private TestRestTemplate restTemplate;
@MockBean
private EmailService emailService;
@Test
void shouldCreateUserAndSendEmail() {
// given
UserCreateRequest request = new UserCreateRequest("john", "[email protected]");
doNothing().when(emailService).sendWelcomeEmail(anyString());
// when
User user = userService.createUser(request);
// then
assertThat(user.getUsername()).isEqualTo("john");
verify(emailService).sendWelcomeEmail("[email protected]");
}
@Test
void shouldReturnUserViaRest() {
// when
ResponseEntity<User> response = restTemplate.getForEntity("/users/1", User.class);
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
}
}Deep Analysis : Spring Boot Test’s core value is its layered testing philosophy, allowing precise control over test scope and complexity to balance efficiency and coverage.
8. Spring Boot Starter
When you want to package common functionality, creating a custom starter is the best practice.
// Auto‑configuration class
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyServiceProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyServiceProperties properties) {
return new MyService(properties);
}
@Bean
@ConditionalOnProperty(name = "my.service.metrics.enabled", havingValue = "true")
public MyServiceMetrics myServiceMetrics() {
return new MyServiceMetrics();
}
}
// Properties class
@ConfigurationProperties(prefix = "my.service")
public class MyServiceProperties {
private String endpoint = "http://localhost:8080";
private Duration timeout = Duration.ofSeconds(30);
private int maxConnections = 100;
// getters and setters omitted
}
# spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.myservice.MyServiceAutoConfigurationDeep Analysis : Custom starters are the core mechanism for extending the Spring Boot ecosystem, enabling plug‑and‑play modules with automatic configuration and conditional bean loading.
9. Spring Boot Admin
Actuator provides endpoints, but Spring Boot Admin offers a friendlier management UI.
// Server configuration
@Configuration
@EnableAdminServer
public class AdminServerConfig {
@Bean
public Notifier notifier() {
return new RemindingNotifier(
new FilteringNotifier(new LoggingNotifier(),
instanceEvent -> instanceEvent.getType() == StatusChangeEvent.TYPE),
AdminServerNotifier::shouldNotify,
Duration.ofMinutes(10));
}
}
// Client configuration
@Configuration
public class AdminClientConfig {
@Bean
public SecurityContext securityContext() {
return SecurityContext.builder()
.username("admin")
.password("secret")
.build();
}
}Deep Analysis : Spring Boot Admin’s core value is its customizable notification system and secure UI for monitoring registered services.
10. Spring Boot CLI
For rapid prototyping, the Spring Boot CLI provides an extreme development experience.
# Create a simple web app
echo '@RestController class App { @RequestMapping("/") String home() { "Hello World" } }' > app.groovy
# Run the app
spring run app.groovy
# Add a dependency
spring install com.example:my-starter:1.0.0
# Package the app
spring jar myapp.jar *.groovyDeep Analysis : The CLI’s core value is that it removes the need for boilerplate configuration, allowing developers to focus on business logic using Groovy scripts and automatic dependency management.
Summary
Spring Boot’s core philosophy can be summarized as:
Convention over Configuration : Reasonable defaults and auto‑configuration free developers from tedious setup.
Modular Design : Each starter is a self‑contained module that can be added on demand, keeping applications lightweight.
Production‑Ready : From monitoring to management, health checks to metric collection, Spring Boot provides a complete production solution.
Developer‑Friendly : Features like DevTools hot reload and the CLI prototype workflow demonstrate a strong focus on developer experience.
Understanding and mastering these "artifacts"—the powerful Spring Boot features—will help you build more efficient, reliable, and maintainable applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
