10 Outdated Spring Boot Patterns and Their Modern Replacements
This article details ten deprecated Spring Boot patterns — from field injection and @Value overuse to WebSecurityConfigurerAdapter and RestTemplate — and shows the recommended modern alternatives for Spring Boot 3.x and 4.x migrations.
1. Introduction
Spring Boot evolves rapidly. Practices once considered best practice are now deprecated, discouraged, or removed. Maintaining older Spring Boot applications likely means your codebase contains outdated patterns. Migrating from Spring Boot 2.x to 3.x and eventually 4.x involves significant API, configuration, and ecosystem convention changes.
2. Outdated Patterns and Modern Replacements
2.1 Stop Using @Autowired Field Injection
Field injection was once the most common way to inject dependencies:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepo;
@Autowired
private PaymentGateway paymentGateway;
}Constructor injection is the recommended approach (officially recommended since Spring 4.x):
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentGateway paymentGateway;
public OrderService(OrderRepository orderRepository, PaymentGateway paymentGateway) {
this.orderRepository = orderRepository;
this.paymentGateway = paymentGateway;
}
}Problems with field injection:
Dependencies cannot be defined as final
Dependencies are hidden from the class constructor
Unit testing becomes more difficult
Higher coupling to the Spring container
Class can be instantiated without required dependencies
Adding more injected fields can bloat the class without revealing design issues
Constructor injection makes dependencies explicit. If a constructor requires ten different dependencies, the class likely has too many responsibilities.
2.2 Don't Use @Value for Large Configuration Objects
@Valueis still useful for injecting single configuration values:
@Value("${pack.app.host}")
private String host;
@Value("${pack.app.port}")
private Integer port;When a service has ten to twenty related configuration properties, scattering them across fields becomes unmaintainable. Use @ConfigurationProperties instead:
@Configuration
@ConfigurationProperties(prefix = "pack.app")
@Validated
public class AppProperties {
@NotBlank
private String host;
@Min(1)
@Max(65535)
private int port;
@NotNull
private Duration timeout;
}Configuration is now centralized in a dedicated object rather than scattered across the application.
2.3 WebSecurityConfigurerAdapter No Longer Exists
This is a major Spring Security change. Old applications typically extended WebSecurityConfigurerAdapter:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**").permitAll()
.anyRequest().authenticated();
}
} WebSecurityConfigurerAdapterwas deprecated in Spring Security 5.7 and removed in Spring Security 6.0. The modern approach defines a SecurityFilterChain bean:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/**").permitAll()
.anyRequest().authenticated());
return http.build();
}
}2.4 RestTemplate Is Becoming Legacy
RestTemplate was the standard for HTTP calls in Spring applications. The ecosystem is transitioning to newer HTTP client APIs. For synchronous calls, the recommended modern alternative is RestClient:
RestClient restClient = RestClient.create();
User user = restClient.get()
.uri("https://api.pack.com/users/1")
.retrieve()
.body(User.class);This API provides a modern fluent programming model while remaining synchronous. For reactive applications and streaming scenarios, WebClient remains appropriate. The key point: don't adopt reactive programming just because it's newer; choose the HTTP client based on your application's actual concurrency and I/O requirements.
Note: RestTemplate is fully deprecated starting from Spring Boot 4.2+.
2.5 WebMvcConfigurerAdapter Deprecated
Older Spring MVC applications may contain:
public class WebConfig extends WebMvcConfigurerAdapter {}Due to Java 8's default interface methods, this adapter is deprecated. Now simply implement WebMvcConfigurer:
@Configuration
public class WebConfig implements WebMvcConfigurer {}2.6 javax.* → jakarta.*
One of the major ecosystem changes in Spring Boot 3 is the Java EE namespace migration:
javax.servlet → jakarta.servlet
javax.persistence → jakarta.persistence
javax.validation → jakarta.validation
javax.annotation → jakarta.annotation
javax.transaction → jakarta.transaction
javax.mail → jakarta.mailThis is not just a superficial import change. Third-party dependencies must also be Jakarta-compatible; otherwise, classpath and compatibility issues arise. For large codebases, automated refactoring tools like OpenRewrite can assist with migration.
2.7 JUnit 4 Patterns Replaced by JUnit 5
Early Spring tests looked like:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {}In JUnit 5, Spring extension integration is built into the modern test environment, so you can write:
@SpringBootTest
public class UserServiceTest {}2.8 spring.factories → AutoConfiguration.imports
If you create custom Spring Boot Starters, this migration is critical. Old auto-configuration registration used: META-INF/spring.factories Modern Spring Boot uses:
META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.importsThis migration became essential starting from Spring Boot 2.7, especially when maintaining custom starters.
2.9 Use ProblemDetail for Standardized Error Responses
Many legacy applications return custom error mappings via exception handlers: return Map.of("code", 500, "msg", "xxx"); Spring 6 and Spring Boot 3 introduced support for ProblemDetail, based on RFC 7807. This provides a standardized structure for representing errors in APIs, enabling clients to handle error responses more consistently. Instead of every service inventing its own error format, applications can expose a predictable structure — especially important for microservices and distributed systems.
2.10 Virtual Threads Change the Concurrency Discussion
An interesting development is the arrival of virtual threads. Spring Boot 3.2+ supports virtual threads, enabled via configuration:
spring:
threads:
virtual:
enabled: trueThis allows traditional Spring MVC applications to handle significantly higher concurrency without adopting a reactive programming model.
Important architectural point: high concurrency doesn't mandate WebFlux. For many applications, Spring MVC combined with virtual threads offers a much simpler programming model. Reactive programming still has clear use cases, particularly for streaming and certain high-concurrency workloads. Adopt it because the workload demands it, not because it's trendy.
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.
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.
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.
