Skip to main content

Command Palette

Search for a command to run...

Reasons Spring Boot 3 Changed WebSecurityConfigurerAdapter — and How to Migrate Gracefully

Every developer who has touched Spring Security before 2023 remembers one thing:WebSecurityConfigurerAdapter was the loyal gatekeeper of your web fortress. You’d override a few methods, call http.authorizeRequests(), and voilà — your app was safe...

Published
5 min read
Reasons Spring Boot 3 Changed WebSecurityConfigurerAdapter — and How to Migrate Gracefully
T

I am Tuanh.net. As of 2024, I have accumulated 8 years of experience in backend programming. I am delighted to connect and share my knowledge with everyone.

1. Introduction: When the Old Guard Retires

1.1 Why WebSecurityConfigurerAdapter Was Removed

The core idea behind the removal is composition over inheritance.Spring Security’s team wanted to make configuration more predictable, testable, and compatible with the new Spring Boot 3 / Spring Framework 6 stack.

Under the old model, developers would subclass WebSecurityConfigurerAdapter and override configure(HttpSecurity) or configure(AuthenticationManagerBuilder). This inheritance pattern made configuration hard to reason about, especially when multiple adapters or configurations were combined — which led to mysterious behavior and security bugs.

Now, instead of extending, we declare beans directly:

  • SecurityFilterChain defines what to protect and how.
  • AuthenticationManager and UserDetailsService are beans in the context.
  • Everything is wired explicitly, so Spring can easily optimize and inspect it.

This change isn’t just syntactic sugar — it improves startup performance, makes security debugging easier, and prepares your app for reactive, modular, and native (GraalVM) environments.

1.2 Old vs. New: The Practical Comparison

Let’s look at how your code likely looked before Spring Boot 3:

// Old style (Spring Boot 2.x)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/public/").permitAll()
.antMatchers("/api/admin/
").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin")
.password("{noop}123456")
.roles("ADMIN");
}
}

That pattern worked fine — until Spring Boot 3 told you WebSecurityConfigurerAdapter no longer exists.

Here’s how to write the same configuration in the new world:

// New style (Spring Boot 3.x)
@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/").permitAll()
.requestMatchers("/api/admin/
").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}

@Bean
public UserDetailsService users() {
UserDetails admin = User.withUsername("admin")
.password("{noop}123456")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(admin);
}
}

It’s cleaner, explicit, and surprisingly flexible. Each configuration component (csrf, authorizeHttpRequests, etc.) is now a lambda-based builder — a step toward the new functional DSL mindset.

1.3 Deep Dive: What’s Really Happening Under the Hood

When you declare a SecurityFilterChain bean, you are effectively describing how Spring Security should build its chain of filters — like a precise blueprint.

Instead of inheriting behavior, your SecurityConfig now declares it.This aligns with the new Spring Bean lifecycle rules in Boot 3, where every major configuration (web, data, security) prefers bean-based declarations.

  • HttpSecurity is no longer configured implicitly.
  • UserDetailsService is injected directly into the context.
  • Password encoding, CSRF handling, and exception translation are now modular components you can inject or override at will.

This separation also makes testing easier.You can now write:

@AutoConfigureMockMvc
@SpringBootTest
class SecurityTests {

@Autowired
private MockMvc mockMvc;

@Test
void shouldAllowPublicEndpoint() throws Exception {
mockMvc.perform(get("/api/public/hello"))
.andExpect(status().isOk());
}
}

Without any weird @WithMockUser issues that used to arise from improperly configured WebSecurityConfigurerAdapter.

1.4 Migration Pitfalls You’ll Likely Encounter

Upgrading from Boot 2.x to 3.x usually triggers three main pain points:

  1. Compile-time errors:Your extends WebSecurityConfigurerAdapter is gone — and all configure() overrides fail.Solution: Replace them with @Bean SecurityFilterChain.

  2. Broken antMatchers():antMatchers() is replaced by requestMatchers().It now supports both servlet and reactive environments through a unified interface.

  3. AuthenticationManager confusion:Since AuthenticationManager is no longer auto-created, you might get“No AuthenticationManager defined” errors.To fix this, define it explicitly:

    @Beanpublic AuthenticationManager authManager(UserDetailsService userDetailsService) {    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();    provider.setUserDetailsService(userDetailsService);    provider.setPasswordEncoder(NoOpPasswordEncoder.getInstance());    return new ProviderManager(provider);}

This modular approach might seem verbose at first, but it grants total control — perfect for microservices or custom security flows.

2. Beyond Migration: Embracing the New Paradigm

The move away from WebSecurityConfigurerAdapter is more than a refactor; it’s a paradigm shift.Spring Security now behaves like modern Java — explicit, modular, and optimized for native and reactive use cases.

2.1 Why This Matters for Real Applications

In large distributed systems (e.g., microservices protected by Keycloak, Okta, or JWT tokens), declarative configurations make it easier to compose multiple SecurityFilterChains. You can now define one chain per context path:

@Bean
@Order(1)
SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
http.securityMatcher("/api/**")
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.oauth2ResourceServer(oauth -> oauth.jwt());
return http.build();
}

and a second one for form login:

@Bean
@Order(2)
SecurityFilterChain webChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.formLogin(Customizer.withDefaults());
return http.build();
}

Each chain operates independently — a feature nearly impossible under the old inheritance model.

2.2 Security in the Age of GraalVM and Native Images

Spring Boot 3 was built with GraalVM in mind. Reflection-heavy code like WebSecurityConfigurerAdapter doesn’t play well with AOT (ahead-of-time) compilation. The new bean-based API is AOT-friendly, enabling native builds without obscure configuration hints.

In other words, your migration isn’t just about keeping up with syntax — it’s about future-proofing your application architecture for the next decade of Java development.

3. Conclusion

The death of WebSecurityConfigurerAdapter wasn’t a bug — it was a signal.Spring Security is stepping into a declarative, testable, cloud-native era.Yes, migration might sting a little, but it unlocks better control, clearer configuration, and performance gains for modern Java environments.

By embracing SecurityFilterChain and functional builders, you don’t just fix your app — you future-proof it.

Question for You:Have you faced a tricky migration case or a custom AuthenticationProvider that broke after upgrading to Spring Boot 3? Share your experience or question below — let’s untangle it together.

Read more at : Reasons Spring Boot 3 Changed WebSecurityConfigurerAdapter — and How to Migrate Gracefully

More from this blog

T

tuanh.net

540 posts

Are you ready to elevate your Java, OOP, Spring, and DevOps skills? Look no further!