Spring Security 7 arrives with Spring Boot 4, and the question is the usual one: what breaks when you move from 6.5? Instead of retelling the release notes I put Spring Security 6.5.11 and 7.1.2 side by side: the jars compared class by class, the same Security 6 code compiled against both, and two small programs run on both. What follows is what actually differed.
Versions. Before: Spring Security 6.5.11 on Spring Boot 3.5.16. After: 7.1.2 on Spring Boot 4.1.2 (7.2 is still milestone and release candidate builds, so I did not use it). Compiled with JDK 25 using --release 17. Not covered: the Kotlin DSL beyond what shows in the jar diff, SAML, LDAP, ACL, WebFlux behaviour, and the real OAuth2 flows. I also did not test the Spring Framework 7 and Boot 4 changes that arrive in the same upgrade. For a complete Spring Security 7 application see Spring Security hasPermission(): PermissionEvaluator and @EnableMethodSecurity.
The code is one module of the companion repository: spring-auth-demo/security-7-migration. Every code block and every line of output below is a real file in it, linked under the block. The explanations stay in this post.
The Java baseline does not move
Unlike some upgrades, this one does not raise the Java requirement. I read the class file version of each jar: both lines are compiled for Java 17. What changes is the number of artifacts, as the next sections show.
artifact 6.x jar 7.x jar
spring-security-core 6.5.11 (Java 17) 7.1.2 (Java 17)
spring-security-config 6.5.11 (Java 17) 7.1.2 (Java 17)
spring-security-web 6.5.11 (Java 17) 7.1.2 (Java 17)
spring-security-crypto 6.5.11 (Java 17) 7.1.2 (Java 17)
spring-security-oauth2-core 6.5.11 (Java 17) 7.1.2 (Java 17)
spring-security-oauth2-client 6.5.11 (Java 17) 7.1.2 (Java 17)
spring-security-oauth2-resource-server 6.5.11 (Java 17) 7.1.2 (Java 17)
spring-security-oauth2-jose 6.5.11 (Java 17) 7.1.2 (Java 17)
spring-security-test 6.5.11 (Java 17) 7.1.2 (Java 17)
Output: 01-versions-and-baseline.txt. Generated by api_diff.py, which downloads the jars from Maven Central.
To run the same code against either line, the module keeps both versions behind Maven profiles and imports the Security bill of materials before Boot’s, so the Security version wins:
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- default is the new line; -Psecurity6 selects Security 6.5.11 on Boot 3.5 -->
<boot.version>4.1.2</boot.version>
<security.version>7.1.2</security.version>
</properties>
<profiles>
<profile>
<id>security6</id>
<properties>
<boot.version>3.5.16</boot.version>
<security.version>6.5.11</security.version>
</properties>
</profile>
<profile>
<id>security7</id>
<properties>
<boot.version>4.1.2</boot.version>
<security.version>7.1.2</security.version>
</properties>
</profile>
</profiles>
<dependencyManagement>
<dependencies>
<!-- first import wins, so this pins Security regardless of what Boot manages -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>${security.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Excerpt of pom.xml.
Compile first: Security 6 code against both lines
The quickest way to find out what breaks is to compile your existing configuration against the new jars. I wrote one class with the Security 6 idioms most projects still contain and compiled it against both lines.
Show OldSecurity.java
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import java.util.function.Supplier;
/** Security 6 style code. Compiled by scripts/compile-old-apis.sh against 6.5.11 and 7.1.2; not part of a Maven build. */
@EnableGlobalMethodSecurity(prePostEnabled = true)
class OldSecurity {
void chain(HttpSecurity http) throws Exception {
http.authorizeRequests(a -> a.anyRequest().authenticated());
}
void chainWithAnd(HttpSecurity http) throws Exception {
http.csrf().disable().headers().frameOptions().sameOrigin();
}
Object matcher() {
return new AntPathRequestMatcher("/api/**");
}
DaoAuthenticationProvider provider(UserDetailsService users) {
DaoAuthenticationProvider p = new DaoAuthenticationProvider();
p.setUserDetailsService(users);
return p;
}
Object grant() {
return AuthorizationGrantType.PASSWORD;
}
static class CustomManager implements AuthorizationManager<RequestAuthorizationContext> {
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext ctx) {
return new AuthorizationDecision(authentication.get().isAuthenticated());
}
}
}Source: OldSecurity.java.
On 6.5.11 it compiles. Notice how the compiler already tells you what is coming: it says deprecated and marked for removal on the DSL calls, the Ant matcher and the password grant. Here is the output:
Show the 6.5.11 compiler output (12 warnings)
Spring Security 6.5.11, compiling OldSecurity.java
src/compat/OldSecurity.java:16: warning: [deprecation] EnableGlobalMethodSecurity in org.springframework.security.config.annotation.method.configuration has been deprecated
@EnableGlobalMethodSecurity(prePostEnabled = true)
^
warning: [deprecation] ExpressionUrlAuthorizationConfigurer in org.springframework.security.config.annotation.web.configurers has been deprecated
warning: [deprecation] ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry in ExpressionUrlAuthorizationConfigurer has been deprecated
src/compat/OldSecurity.java:20: warning: [removal] authorizeRequests(Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry>) in HttpSecurity has been deprecated and marked for removal
http.authorizeRequests(a -> a.anyRequest().authenticated());
^
src/compat/OldSecurity.java:24: warning: [removal] csrf() in HttpSecurity has been deprecated and marked for removal
http.csrf().disable().headers().frameOptions().sameOrigin();
^
src/compat/OldSecurity.java:24: warning: [removal] headers() in HttpSecurity has been deprecated and marked for removal
http.csrf().disable().headers().frameOptions().sameOrigin();
^
src/compat/OldSecurity.java:24: warning: [removal] frameOptions() in HeadersConfigurer has been deprecated and marked for removal
http.csrf().disable().headers().frameOptions().sameOrigin();
^
where H is a type-variable:
H extends HttpSecurityBuilder<H> declared in class HeadersConfigurer
src/compat/OldSecurity.java:28: warning: [removal] AntPathRequestMatcher in org.springframework.security.web.util.matcher has been deprecated and marked for removal
return new AntPathRequestMatcher("/api/**");
^
src/compat/OldSecurity.java:32: warning: [deprecation] DaoAuthenticationProvider() in DaoAuthenticationProvider has been deprecated
DaoAuthenticationProvider p = new DaoAuthenticationProvider();
^
src/compat/OldSecurity.java:33: warning: [deprecation] setUserDetailsService(UserDetailsService) in DaoAuthenticationProvider has been deprecated
p.setUserDetailsService(users);
^
src/compat/OldSecurity.java:38: warning: [removal] PASSWORD in AuthorizationGrantType has been deprecated and marked for removal
return AuthorizationGrantType.PASSWORD;
^
src/compat/OldSecurity.java:43: warning: [deprecation] check(Supplier<Authentication>,T) in AuthorizationManager has been deprecated
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext ctx) {
^
where T is a type-variable:
T extends Object declared in interface AuthorizationManager
12 warnings
javac exit status: 0Output: 05-old-apis-6.5.11.txt.
The same file on 7.1.2:
Spring Security 7.1.2, compiling OldSecurity.java
src/compat/OldSecurity.java:11: error: cannot find symbol
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
^
symbol: class AntPathRequestMatcher
location: package org.springframework.security.web.util.matcher
src/compat/OldSecurity.java:16: warning: [deprecation] EnableGlobalMethodSecurity in org.springframework.security.config.annotation.method.configuration has been deprecated
@EnableGlobalMethodSecurity(prePostEnabled = true)
^
src/compat/OldSecurity.java:20: error: cannot find symbol
http.authorizeRequests(a -> a.anyRequest().authenticated());
^
symbol: method authorizeRequests((a)->a.any[...]ted())
location: variable http of type HttpSecurity
src/compat/OldSecurity.java:24: error: method csrf in class HttpSecurity cannot be applied to given types;
http.csrf().disable().headers().frameOptions().sameOrigin();
^
required: Customizer<CsrfConfigurer<HttpSecurity>>
found: no arguments
reason: actual and formal argument lists differ in length
src/compat/OldSecurity.java:28: error: cannot find symbol
return new AntPathRequestMatcher("/api/**");
^
symbol: class AntPathRequestMatcher
location: class OldSecurity
src/compat/OldSecurity.java:32: error: constructor DaoAuthenticationProvider in class DaoAuthenticationProvider cannot be applied to given types;
DaoAuthenticationProvider p = new DaoAuthenticationProvider();
^
required: UserDetailsService
found: no arguments
reason: actual and formal argument lists differ in length
src/compat/OldSecurity.java:33: error: cannot find symbol
p.setUserDetailsService(users);
^
symbol: method setUserDetailsService(UserDetailsService)
location: variable p of type DaoAuthenticationProvider
src/compat/OldSecurity.java:38: error: cannot find symbol
return AuthorizationGrantType.PASSWORD;
^
symbol: variable PASSWORD
location: class AuthorizationGrantType
src/compat/OldSecurity.java:41: error: CustomManager is not abstract and does not override abstract method authorize(Supplier<? extends Authentication>,RequestAuthorizationContext) in AuthorizationManager
static class CustomManager implements AuthorizationManager<RequestAuthorizationContext> {
^
src/compat/OldSecurity.java:42: error: method does not override or implement a method from a supertype
@Override
^
9 errors
1 warning
javac exit status: 1
Output: 05-old-apis-7.1.2.txt. Nine errors from six removed APIs (the Ant matcher is counted twice, once at the import). Map them like this:
http.authorizeRequests(...)is gone: useauthorizeHttpRequests(...).http.csrf()andhttp.headers()without arguments are gone: use the lambda forms (csrf(c -> c.disable())). The chained.and()is on the removal list too:SecurityConfigurerAdapter.and()was deprecated in 6.5.AntPathRequestMatcheris gone: usePathPatternRequestMatcher.withDefaults().matcher(...).new DaoAuthenticationProvider()andsetUserDetailsService(...)are gone: pass theUserDetailsServiceto the constructor.AuthorizationGrantType.PASSWORDis gone, together with the other password-grant classes listed below. I did not look for a replacement.- A custom
AuthorizationManagerthat implementscheck(...)no longer compiles:authorize(...)is now the abstract method. This one needs care, see below.
All of these except the last have replacements that already exist in 6.5.11, so you can make the changes before upgrading. This class compiled on both lines:
Show NewSecurity.java and its compile results
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/** The replacements for OldSecurity that exist in both lines, so they can be made before upgrading. */
@EnableMethodSecurity
class NewSecurity {
void chain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(a -> a.anyRequest().authenticated());
}
void chainWithLambdas(HttpSecurity http) throws Exception {
http.csrf(c -> c.disable()).headers(h -> h.frameOptions(f -> f.sameOrigin()));
}
RequestMatcher matcher() {
return PathPatternRequestMatcher.withDefaults().matcher("/api/**");
}
DaoAuthenticationProvider provider(UserDetailsService users) {
return new DaoAuthenticationProvider(users);
}
}Source: NewSecurity.java.
Spring Security 6.5.11, compiling NewSecurity.java
javac exit status: 0Spring Security 7.1.2, compiling NewSecurity.java
javac exit status: 0Output: 06-fixed-apis-6.5.11.txt and 06-fixed-apis-7.1.2.txt.
The one change that cannot be made first: AuthorizationManager
If you wrote your own AuthorizationManager, you implemented check(Supplier<Authentication>, T) and returned an AuthorizationDecision. In 7.1.2 the method you implement is authorize, it takes Supplier<? extends Authentication> and it returns an AuthorizationResult. The javap output below shows AuthorizationManager on both lines (it also shows BCryptPasswordEncoder, which I come back to further down):
== Spring Security 6.5.11: BCryptPasswordEncoder
public java.lang.String encode(java.lang.CharSequence);
public boolean matches(java.lang.CharSequence, java.lang.String);
public boolean upgradeEncoding(java.lang.String);
== Spring Security 6.5.11: AuthorizationManager
public abstract authorization.AuthorizationDecision check(java.util.function.Supplier<core.Authentication>, T);
public default authorization.AuthorizationResult authorize(java.util.function.Supplier<core.Authentication>, T);
== Spring Security 7.1.2: BCryptPasswordEncoder
protected java.lang.String encodeNonNullPassword(java.lang.String);
protected boolean matchesNonNull(java.lang.String, java.lang.String);
protected boolean upgradeEncodingNonNull(java.lang.String);
== Spring Security 7.1.2: AuthorizationManager
public abstract authorization.AuthorizationResult authorize(java.util.function.Supplier<? extends core.Authentication>, T);
Output: 08-javap-encoder-and-manager.txt, written by inspect-jars.sh. The 7.x shape of a manager compiles on 7.1.2 and fails on 6.5.11:
Show NewManager.java and its compile results
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import java.util.function.Supplier;
/** The Security 7 shape of a custom AuthorizationManager. There is no single source that compiles on both lines. */
class NewManager implements AuthorizationManager<RequestAuthorizationContext> {
@Override
public AuthorizationResult authorize(Supplier<? extends Authentication> authentication, RequestAuthorizationContext ctx) {
return new AuthorizationDecision(authentication.get().isAuthenticated());
}
}Source: NewManager.java.
Spring Security 6.5.11, compiling NewManager.java
src/compat/NewManager.java:10: error: NewManager is not abstract and does not override abstract method check(Supplier<Authentication>,RequestAuthorizationContext) in AuthorizationManager
class NewManager implements AuthorizationManager<RequestAuthorizationContext> {
^
src/compat/NewManager.java:11: error: method does not override or implement a method from a supertype
@Override
^
2 errors
javac exit status: 1Spring Security 7.1.2, compiling NewManager.java
javac exit status: 0Output: 07-new-manager-6.5.11.txt and 07-new-manager-7.1.2.txt.
Keep the logic in a method of your own and make the thin check or authorize wrapper the only thing that changes at the upgrade. I did not find a source form that compiles on both lines, so there is no way around touching those classes in the same commit as the version bump.
A change the compiler only half reports: @EnableGlobalMethodSecurity
In the compile test above, @EnableGlobalMethodSecurity was not an error on 7.1.2, only a deprecation warning. That makes it look harmless, so I started a context with it, a bean with @PreAuthorize("hasRole('ADMIN')") and no authentication, on each line.
Show MethodSecurityProbe.java
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
/** Does the deprecated @EnableGlobalMethodSecurity still enforce @PreAuthorize? Run with no Authentication in the context. */
public class MethodSecurityProbe {
static class Vault {
@PreAuthorize("hasRole('ADMIN')")
public String secret() {
return "the secret";
}
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
static class Config {
@Bean
Vault vault() {
return new Vault();
}
}
public static void main(String[] args) {
System.out.println("Spring Security " + org.springframework.security.core.SpringSecurityCoreVersion.getVersion());
try (var ctx = new AnnotationConfigApplicationContext()) {
ctx.register(Config.class);
try {
ctx.refresh();
} catch (RuntimeException e) {
Throwable root = e;
while (root.getCause() != null) {
root = root.getCause();
}
System.out.println("context failed to start: " + root.getMessage());
return;
}
try {
System.out.println("returned: " + ctx.getBean(Vault.class).secret());
} catch (RuntimeException e) {
System.out.println("blocked: " + e.getClass().getSimpleName());
}
}
}
}Source: MethodSecurityProbe.java.
Spring Security 6.5.11
blocked: AuthenticationCredentialsNotFoundException
Spring Security 7.1.2
context failed to start: @EnableGlobalMethodSecurity requires the spring-security-access dependency on the classpath. Please add spring-security-access, or migrate to @EnableMethodSecurity which does not require it.
Spring Security 7.1.2
blocked: AuthenticationCredentialsNotFoundException
Output: 04-global-method-security-6.5.11.txt, 04-global-method-security-7.1.2.txt and 04-global-method-security-7.1.2-with-access.txt. On 6.5.11 the call is blocked. On 7.1.2 the application does not start at all, and the message says why: the annotation now needs the new spring-security-access artifact. Adding that jar brings the old behaviour back. So the annotation compiles, then fails fast at startup, which is the better of the two possible failures. The migration is to @EnableMethodSecurity, which compiled on both lines in the test above.
Request matchers: same answers, one pattern that no longer parses
The move from AntPathRequestMatcher to PathPatternRequestMatcher is the one most projects hit, and it is the one people worry about because a wrong match is a security hole. 6.5.11 contains both matchers, so I ran four patterns against ten request paths through both and compared.
Show MatcherProbe.java
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/** Runs the same patterns through the old Ant matcher (when the jar has it) and the PathPattern matcher. */
public class MatcherProbe {
static final String[] PATTERNS = {"/api/**", "/files/*.png", "/a/**/b", "/users/{id}"};
static final String[] PATHS = {"/api", "/api/", "/api/x/y", "/API/x", "/files/a.png", "/files/a.PNG", "/a/b", "/a/x/y/b", "/users/7", "/users/7/"};
public static void main(String[] args) throws Exception {
System.out.println("Spring Security " + org.springframework.security.core.SpringSecurityCoreVersion.getVersion());
Class<?> ant = null;
try {
ant = Class.forName("org.springframework.security.web.util.matcher.AntPathRequestMatcher");
} catch (ClassNotFoundException e) {
System.out.println("AntPathRequestMatcher: class not on the class path");
}
for (String pattern : PATTERNS) {
System.out.println("pattern " + pattern);
RequestMatcher oldM = ant == null ? null : (RequestMatcher) ant.getConstructor(String.class).newInstance(pattern);
RequestMatcher newM = null;
String newError = null;
try {
newM = PathPatternRequestMatcher.withDefaults().matcher(pattern);
} catch (RuntimeException e) {
newError = e.getClass().getSimpleName() + ": " + e.getMessage();
}
for (String path : PATHS) {
MockHttpServletRequest req = new MockHttpServletRequest("GET", path);
req.setServletPath(path);
req.setRequestURI(path);
String a = oldM == null ? "-" : String.valueOf(oldM.matches(req));
String b;
if (newM == null) {
b = "n/a";
} else {
try {
b = String.valueOf(newM.matches(req));
} catch (RuntimeException e) {
b = e.getClass().getSimpleName();
}
}
System.out.printf(" %-14s ant=%-6s pathPattern=%s%n", path, a, b);
}
if (newError != null) {
System.out.println(" PathPatternRequestMatcher rejects the pattern: " + newError);
}
}
}
}Source: MatcherProbe.java.
Show the 6.5.11 results for both matchers
Spring Security 6.5.11
pattern /api/**
/api ant=true pathPattern=true
/api/ ant=true pathPattern=true
/api/x/y ant=true pathPattern=true
/API/x ant=false pathPattern=false
/files/a.png ant=false pathPattern=false
/files/a.PNG ant=false pathPattern=false
/a/b ant=false pathPattern=false
/a/x/y/b ant=false pathPattern=false
/users/7 ant=false pathPattern=false
/users/7/ ant=false pathPattern=false
pattern /files/*.png
/api ant=false pathPattern=false
/api/ ant=false pathPattern=false
/api/x/y ant=false pathPattern=false
/API/x ant=false pathPattern=false
/files/a.png ant=true pathPattern=true
/files/a.PNG ant=false pathPattern=false
/a/b ant=false pathPattern=false
/a/x/y/b ant=false pathPattern=false
/users/7 ant=false pathPattern=false
/users/7/ ant=false pathPattern=false
pattern /a/**/b
/api ant=false pathPattern=n/a
/api/ ant=false pathPattern=n/a
/api/x/y ant=false pathPattern=n/a
/API/x ant=false pathPattern=n/a
/files/a.png ant=false pathPattern=n/a
/files/a.PNG ant=false pathPattern=n/a
/a/b ant=true pathPattern=n/a
/a/x/y/b ant=true pathPattern=n/a
/users/7 ant=false pathPattern=n/a
/users/7/ ant=false pathPattern=n/a
PathPatternRequestMatcher rejects the pattern: PatternParseException: {*...} or ** pattern elements should be placed at the start or end of the pattern
pattern /users/{id}
/api ant=false pathPattern=false
/api/ ant=false pathPattern=false
/api/x/y ant=false pathPattern=false
/API/x ant=false pathPattern=false
/files/a.png ant=false pathPattern=false
/files/a.PNG ant=false pathPattern=false
/a/b ant=false pathPattern=false
/a/x/y/b ant=false pathPattern=false
/users/7 ant=true pathPattern=true
/users/7/ ant=false pathPattern=falseOutput: 03-matchers-6.5.11.txt. The 7.1.2 run, 03-matchers-7.1.2.txt, gives the same pathPattern column and has no Ant matcher to compare with.
For /api/**, /files/*.png and /users/{id} the two matchers gave the same answer for every one of the ten paths, including the ones that should not match: a different case (/API/x, /files/a.PNG) and a trailing slash after a single segment (/users/7/). The only difference is /a/**/b: the Ant matcher accepts it and matches /a/b and /a/x/y/b, while the path pattern parser rejects it when the matcher is built, with PatternParseException: {*...} or ** pattern elements should be placed at the start or end of the pattern. That fails loudly at startup rather than matching wrongly.
Limits of this test. The requests were mock requests with the servlet path set to the full path, which is how a servlet mapped to/sees them. I did not test context paths,MvcRequestMatcheragainst real handler mappings, or patterns with encoded characters. If your rules are unusual, run your own paths through both matchers while you are still on 6.5.
Everything else that was removed
Those were my picks. To find the rest I compared every public class and member of nine Security jars, 6.5.11 against 7.1.2, and recorded whether 6.5 had already marked each one deprecated. Totals per artifact:
== spring-security-core: public classes removed: 59, members removed from classes that remain: 50, new classes: 104
== spring-security-config: public classes removed: 19, members removed from classes that remain: 355, new classes: 53
== spring-security-web: public classes removed: 102, members removed from classes that remain: 28, new classes: 83
== spring-security-crypto: public classes removed: 1, members removed from classes that remain: 28, new classes: 22
== spring-security-oauth2-core: public classes removed: 0, members removed from classes that remain: 16, new classes: 17
== spring-security-oauth2-client: public classes removed: 18, members removed from classes that remain: 10, new classes: 64
== spring-security-oauth2-resource-server: public classes removed: 4, members removed from classes that remain: 4, new classes: 30
== spring-security-oauth2-jose: public classes removed: 0, members removed from classes that remain: 0, new classes: 14
== spring-security-test: public classes removed: 0, members removed from classes that remain: 0, new classes: 9
TOTAL public classes missing from the old jars: 203, of which found again in spring-security-access, spring-security-webauthn: 151
Output: header lines of 02-removed-api.txt. The last line is the one to read first. Of 203 public classes missing from the old jars, 151 reappear under the same name in two new artifacts: 74 in spring-security-access (the old AccessDecisionManager, the voters, FilterSecurityInterceptor and similar) and 77 in spring-security-webauthn (including the web.webauthn.api package). They moved to another jar; they were not deleted. If you use passkeys, add the WebAuthn artifact.
That leaves 52 classes that I could not find under the same name in any of the three 7.1.2 jars I checked. Some are renamed or repackaged rather than dropped. Two I confirmed: BearerTokenAuthenticationFilter now lives in ...resource.web.authentication, and RestClientAuthorizationCodeTokenResponseClient exists next to the removed DefaultAuthorizationCodeTokenResponseClient. The rest I did not trace:
Show the 52 classes not found under the same name
class authorization.method.ExpressionAttributeAuthorizationDecision [deprecated in 6.5; gone]
class authorization.method.PrePostTemplateDefaults [deprecated in 6.5; gone]
class config.annotation.ObjectPostProcessor [deprecated in 6.5; gone]
class config.annotation.web.AuthorizeHttpRequestsDsl$get$1$WhenMappings [NOT deprecated in 6.5; gone]
class config.annotation.web.AuthorizeRequestsDsl [NOT deprecated in 6.5; gone]
class config.annotation.web.AuthorizeRequestsDsl$get$1$WhenMappings [NOT deprecated in 6.5; gone]
class config.annotation.web.RequestMatcherFactory [deprecated in 6.5; gone]
class config.annotation.web.RequiresChannelDsl$get$1$WhenMappings [NOT deprecated in 6.5; gone]
class config.annotation.web.configurers.AbstractInterceptUrlConfigurer [deprecated in 6.5; gone]
class config.annotation.web.configurers.AbstractInterceptUrlConfigurer$AbstractInterceptUrlRegistry [deprecated in 6.5; gone]
class config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer [deprecated in 6.5; gone]
class config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer$AuthorizedUrl [NOT deprecated in 6.5; gone]
class config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer$ExpressionInterceptUrlRegistry [deprecated in 6.5; gone]
class config.annotation.web.configurers.UrlAuthorizationConfigurer [deprecated in 6.5; gone]
class config.annotation.web.configurers.UrlAuthorizationConfigurer$AuthorizedUrl [NOT deprecated in 6.5; gone]
class config.annotation.web.configurers.UrlAuthorizationConfigurer$StandardInterceptUrlRegistry [deprecated in 6.5; gone]
class config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry [deprecated in 6.5; gone]
class config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry$Constraint [NOT deprecated in 6.5; gone]
class config.annotation.web.servlet.configuration.EnableWebMvcSecurity [deprecated in 6.5; gone]
class config.annotation.web.session.SessionFixationDsl$get$1$WhenMappings [NOT deprecated in 6.5; gone]
class config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer [deprecated in 6.5; gone]
class web.PortResolver [deprecated in 6.5; gone]
class web.PortResolverImpl [deprecated in 6.5; gone]
class web.access.HandlerMappingIntrospectorRequestTransformer [deprecated in 6.5; gone]
class web.csrf.LazyCsrfTokenRepository [deprecated in 6.5; gone]
class web.servlet.util.matcher.MvcRequestMatcher [deprecated in 6.5; gone]
class web.servlet.util.matcher.MvcRequestMatcher$Builder [NOT deprecated in 6.5; gone]
class web.util.matcher.AntPathRequestMatcher [deprecated in 6.5; gone]
class web.util.matcher.RequestVariablesExtractor [deprecated in 6.5; gone]
class crypto.codec.Base64 [deprecated in 6.5; gone]
class oauth2.client.OAuth2AuthorizedClientProviderBuilder$PasswordGrantBuilder [NOT deprecated in 6.5; gone]
class oauth2.client.PasswordOAuth2AuthorizedClientProvider [deprecated in 6.5; gone]
class oauth2.client.PasswordReactiveOAuth2AuthorizedClientProvider [deprecated in 6.5; gone]
class oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder$PasswordGrantBuilder [NOT deprecated in 6.5; gone]
class oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient [deprecated in 6.5; gone]
class oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient [deprecated in 6.5; gone]
class oauth2.client.endpoint.DefaultJwtBearerTokenResponseClient [deprecated in 6.5; gone]
class oauth2.client.endpoint.DefaultPasswordTokenResponseClient [deprecated in 6.5; gone]
class oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient [deprecated in 6.5; gone]
class oauth2.client.endpoint.DefaultTokenExchangeTokenResponseClient [deprecated in 6.5; gone]
class oauth2.client.endpoint.JwtBearerGrantRequestEntityConverter [deprecated in 6.5; gone]
class oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequestEntityConverter [deprecated in 6.5; gone]
class oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequestEntityConverter [deprecated in 6.5; gone]
class oauth2.client.endpoint.OAuth2PasswordGrantRequest [deprecated in 6.5; gone]
class oauth2.client.endpoint.OAuth2PasswordGrantRequestEntityConverter [deprecated in 6.5; gone]
class oauth2.client.endpoint.OAuth2RefreshTokenGrantRequestEntityConverter [deprecated in 6.5; gone]
class oauth2.client.endpoint.TokenExchangeGrantRequestEntityConverter [deprecated in 6.5; gone]
class oauth2.client.endpoint.WebClientReactivePasswordTokenResponseClient [deprecated in 6.5; gone]
class oauth2.server.resource.BearerTokenAuthenticationToken [deprecated in 6.5; gone]
class oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector [deprecated in 6.5; gone]
class oauth2.server.resource.introspection.NimbusReactiveOpaqueTokenIntrospector [deprecated in 6.5; gone]
class oauth2.server.resource.web.BearerTokenAuthenticationFilter [deprecated in 6.5; gone]
Two cautions about the member lines. A “member removed” means the exact signature is gone: a method that moved to a superclass is counted too. The password encoders show it clearly. encode and matches appear as removed from BCryptPasswordEncoder, but the javap output in the previous section shows why: 7.1.2 declares encodeNonNullPassword and matchesNonNull there instead. Code that calls PasswordEncoder.encode is not what this list is about; subclasses of these encoders are, and I did not test one.
Members that disappeared from classes that remain, all tagged deprecated in 6.5 in the output, so the 6.5.11 compiler warns about them:
RoleHierarchyImpl: the no-argument constructor andsetHierarchy(String).CookieCsrfTokenRepository:setCookieHttpOnly,setCookieDomain,setSecureandsetCookieMaxAge.LoginUrlAuthenticationEntryPointandHttpSessionRequestCache:setPortResolver, sincePortResolveritself is gone.JwtIssuerAuthenticationManagerResolver: the constructors that take issuer strings.OidcUserService.setAccessibleScopesandAuthorizationFilter.setShouldFilterAllDispatcherTypes.- The
setTemplateDefaults(PrePostTemplateDefaults)methods on the pre/post authorization managers and interceptors.
The complete list, including the members of spring-security-config (mostly the no-argument DSL methods of HttpSecurity and the Headers and Authorize DSLs), is the file 02-removed-api.txt.
The practical reading: anything tagged deprecated in 6.5 is something you can fix now, on 6.5.11, with the compiler’s help. Anything tagged NOT deprecated in 6.5 disappeared without that warning, so search your code for it.
Going deeper
- api_diff.py takes two version strings at the top, so you can compare any pair of releases, for example 7.1.2 against 7.2.0 once it ships.
- Internal packages (anything with
.internal.in the name) are left out on purpose; they were never API.
The upgrade, in order
Show the upgrade checklist
- On 6.5.11, compile with deprecation warnings on and fix everything tagged
marked for removal:authorizeHttpRequests, lambda DSL calls,PathPatternRequestMatcher, theDaoAuthenticationProviderconstructor and@EnableMethodSecurityall work on 6.5.11 already. - Run your request-matching rules through both matchers and look for patterns with
**in the middle. - Remove uses of the password grant.
- Bump Boot and Security together, and add
spring-security-accessorspring-security-webauthnonly if you really use what moved there. - Rewrite custom
AuthorizationManagerclasses toauthorize(...)in the same commit as the bump. - Search for the classes tagged
NOT deprecated in 6.5in the removal list. - Start the application and the test suite. Startup failures, like the one for
@EnableGlobalMethodSecurity, are the cheap ones.
Frequently asked questions
Does Spring Security 7 require a newer Java than 6.5?
No. The class files in the 7.1.2 jars and in the 6.5.11 jars are both Java 17 (class file version 61).
Does @EnableGlobalMethodSecurity still work in Spring Security 7?
Only with the new spring-security-access artifact on the class path. In the test here, 7.1.2 without it refused to start the application context, and with it the call was blocked as in 6.5.11. The replacement is @EnableMethodSecurity.
What replaces AntPathRequestMatcher in Spring Security 7?
PathPatternRequestMatcher, for example PathPatternRequestMatcher.withDefaults().matcher("/api/**"). It exists in 6.5.11 too. In the test here it gave the same answers as the Ant matcher except for a pattern with ** in the middle, which it refuses to parse.
Run it yourself
Clone the repository, go into security-7-migration and run ./scripts/run-all.sh (JDK 17 or newer, Maven 3.9, Python 3, internet access for Maven Central). It rewrites everything in output/. Build file: pom.xml.
Conclusion
For a project that already builds without deprecation warnings on 6.5, the move to Spring Security 7 is mostly a version bump. For the rest, the work is mechanical and the compiler finds it: the DSL calls, the Ant matcher, the provider constructor. Three things deserve your attention because they do not announce themselves: custom AuthorizationManager classes, the pattern syntax of the new matcher, and the artifacts that were split out.
See also
- Spring Security hasPermission(): PermissionEvaluator and @EnableMethodSecurity (Why Every Check Is Denied)
- Multi-Factor Authentication in Spring Security 7 with @EnableMultiFactorAuthentication
- Password Hashing in Java: BCrypt vs Argon2 vs PBKDF2 with Spring Security 7
Further reading
- Spring Security migration guide
- Companion code: security-7-migration module
No Comments yet!