Spring Security
antMatcher
antMatchers
web security
Java development

Spring security application of antMatcher vs. antMatchers

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

In older Spring Security configuration, antMatcher and antMatchers looked almost identical but served different roles. One selected which requests a whole security configuration applied to, while the other declared authorization rules inside that configuration. Confusing them often produced code that looked reasonable but did not protect the intended routes.

What antMatcher Did

In the WebSecurityConfigurerAdapter style, http.antMatcher("/api/**") scoped the entire HttpSecurity configuration to matching requests.

java
1import org.springframework.context.annotation.Configuration;
2import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
4import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
5
6@Configuration
7@EnableWebSecurity
8public class ApiSecurityConfig extends WebSecurityConfigurerAdapter {
9    @Override
10    protected void configure(HttpSecurity http) throws Exception {
11        http
12            .antMatcher("/api/**")
13            .authorizeRequests()
14                .anyRequest().authenticated()
15            .and()
16            .httpBasic();
17    }
18}

That means this configuration chain only applies to paths under /api/. Requests outside that scope are handled elsewhere.

What antMatchers Did

Inside authorizeRequests(), antMatchers(...) defined authorization rules for specific request patterns within the active security chain.

java
1@Override
2protected void configure(HttpSecurity http) throws Exception {
3    http
4        .antMatcher("/api/**")
5        .authorizeRequests()
6            .antMatchers("/api/public/**").permitAll()
7            .antMatchers("/api/admin/**").hasRole("ADMIN")
8            .anyRequest().authenticated()
9        .and()
10        .httpBasic();
11}

So the division of responsibility was:

  • 'antMatcher decided whether the chain applied at all'
  • 'antMatchers expressed access rules inside that chain'

Why the Difference Mattered

Suppose you scoped the chain with http.antMatcher("/api/**") and then wrote an authorization rule for /admin/**. That rule would never run for /admin/** requests because those requests would never enter the /api/** chain in the first place.

That is why the names caused confusion. They sounded interchangeable, but they operated at different structural levels.

Multiple Security Chains Made This Even More Important

The distinction became more obvious when applications used more than one security configuration.

java
1import org.springframework.context.annotation.Configuration;
2import org.springframework.core.annotation.Order;
3import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
5
6@Configuration
7@Order(1)
8class ApiSecurityConfig extends WebSecurityConfigurerAdapter {
9    @Override
10    protected void configure(HttpSecurity http) throws Exception {
11        http
12            .antMatcher("/api/**")
13            .authorizeRequests()
14                .anyRequest().authenticated();
15    }
16}
17
18@Configuration
19@Order(2)
20class WebSecurityConfig extends WebSecurityConfigurerAdapter {
21    @Override
22    protected void configure(HttpSecurity http) throws Exception {
23        http
24            .authorizeRequests()
25                .anyRequest().permitAll();
26    }
27}

Here, order and chain scope both matter. A broader chain can catch requests before a later, more specific chain gets a chance.

The Modern API Replaced These Names

Current Spring Security no longer encourages WebSecurityConfigurerAdapter, antMatcher, or antMatchers. The modern style uses SecurityFilterChain, securityMatcher, and requestMatchers.

java
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4import org.springframework.security.web.SecurityFilterChain;
5
6@Configuration
7public class SecurityConfig {
8    @Bean
9    SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
10        http
11            .securityMatcher("/api/**")
12            .authorizeHttpRequests(auth -> auth
13                .requestMatchers("/api/public/**").permitAll()
14                .requestMatchers("/api/admin/**").hasRole("ADMIN")
15                .anyRequest().authenticated()
16            )
17            .httpBasic();
18
19        return http.build();
20    }
21}

The conceptual split is the same as before: one matcher selects the chain, and another defines rules within it.

Test Matcher Scope Explicitly

Because matcher scope and rule scope are different concerns, integration tests are often the quickest way to confirm that the expected chain is handling the expected requests. A rule that looks correct on paper may still be unreachable if the surrounding chain selector is too narrow or ordered incorrectly.

When debugging legacy configurations, ask two separate questions: did the request enter this chain at all, and if it did, which authorization rule matched inside it. That mental split mirrors the API design and prevents many false assumptions.

Common Pitfalls

A common mistake was treating antMatcher as if it were just a shorter spelling of antMatchers. It was not.

Another was writing rules inside a chain for routes that the chain could never see. Those rules looked valid in code review but had no effect at runtime.

Teams also carried old examples into newer Spring Security versions without updating the API shape. In current code, securityMatcher and requestMatchers are the clearer equivalents.

Summary

  • In legacy Spring Security, antMatcher scoped the whole security chain.
  • 'antMatchers defined authorization rules inside that selected chain.'
  • Similar names caused many misconfigurations where rules never matched real requests.
  • In modern Spring Security, use securityMatcher for chain scope and requestMatchers for route rules.
  • When multiple chains exist, verify both matcher scope and chain order.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.