Skip to content

Commit bc5d771

Browse files
committed
Switch to JSpecify annotations
This commit updates the whole Spring Framework codebase to use JSpecify annotations instead of Spring null-safety annotations with JSR 305 semantics. JSpecify provides signficant enhancements such as properly defined specifications, a canonical dependency with no split-package issue, better tooling, better Kotlin integration and the capability to specify generic type, array and varargs element null-safety. Generic type null-safety is not defined by this commit yet and will be specified later. A key difference is that Spring null-safety annotations, following JSR 305 semantics, apply to fields, parameters and return values, while JSpecify annotations apply to type usages. That's why this commit moves nullability annotations closer to the type for fields and return values. See gh-28797
1 parent fcb8aed commit bc5d771

File tree

3,459 files changed

+14118
-22059
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

3,459 files changed

+14118
-22059
lines changed

build.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ configure([rootProject] + javaProjects) { project ->
107107
// Previously there could be a split-package issue between JSR250 and JSR305 javax.annotation packages,
108108
// but since 6.0 JSR 250 annotations such as @Resource and @PostConstruct have been replaced by their
109109
// JakartaEE equivalents in the jakarta.annotation package.
110-
//"https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/"
110+
//"https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/",
111+
"https://jspecify.dev/docs/api/"
111112
] as String[]
112113
}
113114

framework-docs/src/main/kotlin/org/springframework/docs/integration/cache/cachestoreconfigurationcaffeine/CustomCacheConfiguration.kt

+1-3
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ class CustomCacheConfiguration {
2828
// tag::snippet[]
2929
@Bean
3030
fun cacheManager(): CacheManager {
31-
return CaffeineCacheManager().apply {
32-
cacheNames = listOf("default", "books")
33-
}
31+
return CaffeineCacheManager("default", "books")
3432
}
3533
// end::snippet[]
3634
}

framework-platform/framework-platform.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ dependencies {
131131
api("org.htmlunit:htmlunit:4.6.0")
132132
api("org.javamoney:moneta:1.4.4")
133133
api("org.jruby:jruby:9.4.9.0")
134+
api("org.jspecify:jspecify:1.0.0")
134135
api("org.junit.support:testng-engine:1.0.5")
135136
api("org.mozilla:rhino:1.7.15")
136137
api("org.ogce:xpp3:1.1.6")

gradle/spring-module.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ dependencies {
1313
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
1414
jmh 'org.openjdk.jmh:jmh-generator-bytecode:1.37'
1515
jmh 'net.sf.jopt-simple:jopt-simple'
16-
errorprone 'com.uber.nullaway:nullaway:0.10.26'
17-
errorprone 'com.google.errorprone:error_prone_core:2.9.0'
16+
errorprone 'com.uber.nullaway:nullaway:0.12.2'
17+
errorprone 'com.google.errorprone:error_prone_core:2.35.1'
1818
}
1919

2020
pluginManager.withPlugin("kotlin") {

integration-tests/src/test/java/org/springframework/aop/framework/autoproxy/AdvisorAutoProxyCreatorIntegrationTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.List;
2121

2222
import jakarta.servlet.ServletException;
23+
import org.jspecify.annotations.Nullable;
2324
import org.junit.jupiter.api.Test;
2425

2526
import org.springframework.aop.support.AopUtils;
@@ -31,7 +32,6 @@
3132
import org.springframework.beans.factory.InitializingBean;
3233
import org.springframework.beans.testfixture.beans.ITestBean;
3334
import org.springframework.context.support.ClassPathXmlApplicationContext;
34-
import org.springframework.lang.Nullable;
3535
import org.springframework.transaction.NoTransactionException;
3636
import org.springframework.transaction.interceptor.TransactionInterceptor;
3737
import org.springframework.transaction.testfixture.CallCountingTransactionManager;

spring-aop/src/main/java/org/springframework/aop/AfterReturningAdvice.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import java.lang.reflect.Method;
2020

21-
import org.springframework.lang.Nullable;
21+
import org.jspecify.annotations.Nullable;
2222

2323
/**
2424
* After returning advice is invoked only on normal method return, not if an

spring-aop/src/main/java/org/springframework/aop/MethodBeforeAdvice.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import java.lang.reflect.Method;
2020

21-
import org.springframework.lang.Nullable;
21+
import org.jspecify.annotations.Nullable;
2222

2323
/**
2424
* Advice invoked before a method is invoked. Such advices cannot

spring-aop/src/main/java/org/springframework/aop/ProxyMethodInvocation.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
package org.springframework.aop;
1818

1919
import org.aopalliance.intercept.MethodInvocation;
20-
21-
import org.springframework.lang.Nullable;
20+
import org.jspecify.annotations.Nullable;
2221

2322
/**
2423
* Extension of the AOP Alliance {@link org.aopalliance.intercept.MethodInvocation}
@@ -83,7 +82,6 @@ public interface ProxyMethodInvocation extends MethodInvocation {
8382
* @return the value of the attribute, or {@code null} if not set
8483
* @see #setUserAttribute
8584
*/
86-
@Nullable
87-
Object getUserAttribute(String key);
85+
@Nullable Object getUserAttribute(String key);
8886

8987
}

spring-aop/src/main/java/org/springframework/aop/TargetClassAware.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package org.springframework.aop;
1818

19-
import org.springframework.lang.Nullable;
19+
import org.jspecify.annotations.Nullable;
2020

2121
/**
2222
* Minimal interface for exposing the target class behind a proxy.
@@ -36,7 +36,6 @@ public interface TargetClassAware {
3636
* (typically a proxy configuration or an actual proxy).
3737
* @return the target Class, or {@code null} if not known
3838
*/
39-
@Nullable
40-
Class<?> getTargetClass();
39+
@Nullable Class<?> getTargetClass();
4140

4241
}

spring-aop/src/main/java/org/springframework/aop/TargetSource.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package org.springframework.aop;
1818

19-
import org.springframework.lang.Nullable;
19+
import org.jspecify.annotations.Nullable;
2020

2121
/**
2222
* A {@code TargetSource} is used to obtain the current "target" of
@@ -42,8 +42,7 @@ public interface TargetSource extends TargetClassAware {
4242
* @return the type of targets returned by this {@link TargetSource}
4343
*/
4444
@Override
45-
@Nullable
46-
Class<?> getTargetClass();
45+
@Nullable Class<?> getTargetClass();
4746

4847
/**
4948
* Will all calls to {@link #getTarget()} return the same object?
@@ -64,8 +63,7 @@ default boolean isStatic() {
6463
* or {@code null} if there is no actual target instance
6564
* @throws Exception if the target object can't be resolved
6665
*/
67-
@Nullable
68-
Object getTarget() throws Exception;
66+
@Nullable Object getTarget() throws Exception;
6967

7068
/**
7169
* Release the given target object obtained from the

spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java

+10-19
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.aspectj.lang.ProceedingJoinPoint;
3232
import org.aspectj.weaver.tools.JoinPointMatch;
3333
import org.aspectj.weaver.tools.PointcutParameter;
34+
import org.jspecify.annotations.Nullable;
3435

3536
import org.springframework.aop.AopInvocationException;
3637
import org.springframework.aop.MethodMatcher;
@@ -42,7 +43,6 @@
4243
import org.springframework.aop.support.StaticMethodMatcher;
4344
import org.springframework.core.DefaultParameterNameDiscoverer;
4445
import org.springframework.core.ParameterNameDiscoverer;
45-
import org.springframework.lang.Nullable;
4646
import org.springframework.util.Assert;
4747
import org.springframework.util.ClassUtils;
4848
import org.springframework.util.CollectionUtils;
@@ -118,16 +118,13 @@ public static JoinPoint currentJoinPoint() {
118118
* This will be non-null if the creator of this advice object knows the argument names
119119
* and sets them explicitly.
120120
*/
121-
@Nullable
122-
private String[] argumentNames;
121+
private @Nullable String @Nullable [] argumentNames;
123122

124123
/** Non-null if after throwing advice binds the thrown value. */
125-
@Nullable
126-
private String throwingName;
124+
private @Nullable String throwingName;
127125

128126
/** Non-null if after returning advice binds the return value. */
129-
@Nullable
130-
private String returningName;
127+
private @Nullable String returningName;
131128

132129
private Class<?> discoveredReturningType = Object.class;
133130

@@ -145,13 +142,11 @@ public static JoinPoint currentJoinPoint() {
145142
*/
146143
private int joinPointStaticPartArgumentIndex = -1;
147144

148-
@Nullable
149-
private Map<String, Integer> argumentBindings;
145+
private @Nullable Map<String, Integer> argumentBindings;
150146

151147
private boolean argumentsIntrospected = false;
152148

153-
@Nullable
154-
private Type discoveredReturningGenericType;
149+
private @Nullable Type discoveredReturningGenericType;
155150
// Note: Unlike return type, no such generic information is needed for the throwing type,
156151
// since Java doesn't allow exception types to be parameterized.
157152

@@ -212,8 +207,7 @@ public final AspectInstanceFactory getAspectInstanceFactory() {
212207
/**
213208
* Return the ClassLoader for aspect instances.
214209
*/
215-
@Nullable
216-
public final ClassLoader getAspectClassLoader() {
210+
public final @Nullable ClassLoader getAspectClassLoader() {
217211
return this.aspectInstanceFactory.getAspectClassLoader();
218212
}
219213

@@ -318,8 +312,7 @@ protected Class<?> getDiscoveredReturningType() {
318312
return this.discoveredReturningType;
319313
}
320314

321-
@Nullable
322-
protected Type getDiscoveredReturningGenericType() {
315+
protected @Nullable Type getDiscoveredReturningGenericType() {
323316
return this.discoveredReturningGenericType;
324317
}
325318

@@ -657,8 +650,7 @@ protected JoinPoint getJoinPoint() {
657650
/**
658651
* Get the current join point match at the join point we are being dispatched on.
659652
*/
660-
@Nullable
661-
protected JoinPointMatch getJoinPointMatch() {
653+
protected @Nullable JoinPointMatch getJoinPointMatch() {
662654
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
663655
if (!(mi instanceof ProxyMethodInvocation pmi)) {
664656
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
@@ -672,8 +664,7 @@ protected JoinPointMatch getJoinPointMatch() {
672664
// 'last man wins' which is not what we want at all.
673665
// Using the expression is guaranteed to be safe, since 2 identical expressions
674666
// are guaranteed to bind in exactly the same way.
675-
@Nullable
676-
protected JoinPointMatch getJoinPointMatch(ProxyMethodInvocation pmi) {
667+
protected @Nullable JoinPointMatch getJoinPointMatch(ProxyMethodInvocation pmi) {
677668
String expression = this.pointcut.getExpression();
678669
return (expression != null ? (JoinPointMatch) pmi.getUserAttribute(expression) : null);
679670
}

spring-aop/src/main/java/org/springframework/aop/aspectj/AspectInstanceFactory.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616

1717
package org.springframework.aop.aspectj;
1818

19+
import org.jspecify.annotations.Nullable;
20+
1921
import org.springframework.core.Ordered;
20-
import org.springframework.lang.Nullable;
2122

2223
/**
2324
* Interface implemented to provide an instance of an AspectJ aspect.
@@ -44,7 +45,6 @@ public interface AspectInstanceFactory extends Ordered {
4445
* @return the aspect class loader (or {@code null} for the bootstrap loader)
4546
* @see org.springframework.util.ClassUtils#getDefaultClassLoader()
4647
*/
47-
@Nullable
48-
ClassLoader getAspectClassLoader();
48+
@Nullable ClassLoader getAspectClassLoader();
4949

5050
}

spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java

+8-14
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
import org.aspectj.lang.ProceedingJoinPoint;
2929
import org.aspectj.weaver.tools.PointcutParser;
3030
import org.aspectj.weaver.tools.PointcutPrimitive;
31+
import org.jspecify.annotations.Nullable;
3132

3233
import org.springframework.core.ParameterNameDiscoverer;
33-
import org.springframework.lang.Nullable;
3434
import org.springframework.util.StringUtils;
3535

3636
/**
@@ -157,22 +157,19 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
157157

158158

159159
/** The pointcut expression associated with the advice, as a simple String. */
160-
@Nullable
161-
private final String pointcutExpression;
160+
private final @Nullable String pointcutExpression;
162161

163162
private boolean raiseExceptions;
164163

165164
/** If the advice is afterReturning, and binds the return value, this is the parameter name used. */
166-
@Nullable
167-
private String returningName;
165+
private @Nullable String returningName;
168166

169167
/** If the advice is afterThrowing, and binds the thrown value, this is the parameter name used. */
170-
@Nullable
171-
private String throwingName;
168+
private @Nullable String throwingName;
172169

173170
private Class<?>[] argumentTypes = new Class<?>[0];
174171

175-
private String[] parameterNameBindings = new String[0];
172+
private @Nullable String[] parameterNameBindings = new String[0];
176173

177174
private int numberOfRemainingUnboundArguments;
178175

@@ -221,8 +218,7 @@ public void setThrowingName(@Nullable String throwingName) {
221218
* @return the parameter names
222219
*/
223220
@Override
224-
@Nullable
225-
public String[] getParameterNames(Method method) {
221+
public @Nullable String @Nullable [] getParameterNames(Method method) {
226222
this.argumentTypes = method.getParameterTypes();
227223
this.numberOfRemainingUnboundArguments = this.argumentTypes.length;
228224
this.parameterNameBindings = new String[this.numberOfRemainingUnboundArguments];
@@ -289,8 +285,7 @@ public String[] getParameterNames(Method method) {
289285
* {@link #setRaiseExceptions(boolean) raiseExceptions} has been set to {@code true}
290286
*/
291287
@Override
292-
@Nullable
293-
public String[] getParameterNames(Constructor<?> ctor) {
288+
public String @Nullable [] getParameterNames(Constructor<?> ctor) {
294289
if (this.raiseExceptions) {
295290
throw new UnsupportedOperationException("An advice method can never be a constructor");
296291
}
@@ -453,8 +448,7 @@ else if (numAnnotationSlots == 1) {
453448
/**
454449
* If the token starts meets Java identifier conventions, it's in.
455450
*/
456-
@Nullable
457-
private String maybeExtractVariableName(@Nullable String candidateToken) {
451+
private @Nullable String maybeExtractVariableName(@Nullable String candidateToken) {
458452
if (AspectJProxyUtils.isVariableName(candidateToken)) {
459453
return candidateToken;
460454
}

spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterAdvice.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121

2222
import org.aopalliance.intercept.MethodInterceptor;
2323
import org.aopalliance.intercept.MethodInvocation;
24+
import org.jspecify.annotations.Nullable;
2425

2526
import org.springframework.aop.AfterAdvice;
26-
import org.springframework.lang.Nullable;
2727

2828
/**
2929
* Spring AOP advice wrapping an AspectJ after advice method.
@@ -43,8 +43,7 @@ public AspectJAfterAdvice(
4343

4444

4545
@Override
46-
@Nullable
47-
public Object invoke(MethodInvocation mi) throws Throwable {
46+
public @Nullable Object invoke(MethodInvocation mi) throws Throwable {
4847
try {
4948
return mi.proceed();
5049
}

spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterReturningAdvice.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
import java.lang.reflect.Method;
2121
import java.lang.reflect.Type;
2222

23+
import org.jspecify.annotations.Nullable;
24+
2325
import org.springframework.aop.AfterAdvice;
2426
import org.springframework.aop.AfterReturningAdvice;
25-
import org.springframework.lang.Nullable;
2627
import org.springframework.util.ClassUtils;
2728
import org.springframework.util.TypeUtils;
2829

spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterThrowingAdvice.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121

2222
import org.aopalliance.intercept.MethodInterceptor;
2323
import org.aopalliance.intercept.MethodInvocation;
24+
import org.jspecify.annotations.Nullable;
2425

2526
import org.springframework.aop.AfterAdvice;
26-
import org.springframework.lang.Nullable;
2727

2828
/**
2929
* Spring AOP advice wrapping an AspectJ after-throwing advice method.
@@ -58,8 +58,7 @@ public void setThrowingName(String name) {
5858
}
5959

6060
@Override
61-
@Nullable
62-
public Object invoke(MethodInvocation mi) throws Throwable {
61+
public @Nullable Object invoke(MethodInvocation mi) throws Throwable {
6362
try {
6463
return mi.proceed();
6564
}

spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAopUtils.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
package org.springframework.aop.aspectj;
1818

1919
import org.aopalliance.aop.Advice;
20+
import org.jspecify.annotations.Nullable;
2021

2122
import org.springframework.aop.Advisor;
2223
import org.springframework.aop.AfterAdvice;
2324
import org.springframework.aop.BeforeAdvice;
24-
import org.springframework.lang.Nullable;
2525

2626
/**
2727
* Utility methods for dealing with AspectJ advisors.
@@ -59,8 +59,7 @@ public static boolean isAfterAdvice(Advisor anAdvisor) {
5959
* If neither the advisor nor the advice have precedence information, this method
6060
* will return {@code null}.
6161
*/
62-
@Nullable
63-
public static AspectJPrecedenceInformation getAspectJPrecedenceInformationFor(Advisor anAdvisor) {
62+
public static @Nullable AspectJPrecedenceInformation getAspectJPrecedenceInformationFor(Advisor anAdvisor) {
6463
if (anAdvisor instanceof AspectJPrecedenceInformation ajpi) {
6564
return ajpi;
6665
}

0 commit comments

Comments
 (0)