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
This commit is contained in:
Sébastien Deleuze
2024-12-03 15:22:37 +01:00
parent fcb8aed03f
commit bc5d771a06
3459 changed files with 14118 additions and 22059 deletions

View File

@@ -19,6 +19,7 @@ package org.springframework.http.client;
import java.io.IOException;
import java.net.URI;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -26,7 +27,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
@@ -37,8 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class JdkClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTests {
@Nullable
private static String originalPropertyValue;
private static @Nullable String originalPropertyValue;
@BeforeAll
static void setProperty() {

View File

@@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import io.netty.buffer.PooledByteBufAllocator;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -48,7 +49,6 @@ import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
import static java.nio.charset.StandardCharsets.UTF_8;

View File

@@ -16,7 +16,7 @@
package org.springframework.http.codec.xml;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* @author Arjen Poutsma

View File

@@ -39,13 +39,13 @@ import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.lang.Nullable;
import org.springframework.web.testfixture.http.MockHttpInputMessage;
import org.springframework.web.testfixture.http.MockHttpOutputMessage;

View File

@@ -28,6 +28,7 @@ import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -35,7 +36,6 @@ import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.lang.Nullable;
import org.springframework.web.testfixture.http.MockHttpInputMessage;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -16,6 +16,7 @@
package org.springframework.http.server.reactive;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Subscriber;
@@ -103,7 +104,7 @@ class ListenerReadPublisherTests {
}
@Override
protected DataBuffer read() {
protected @Nullable DataBuffer read() {
if (this.discardCalls != 0) {
return null;
}

View File

@@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.beans.testfixture.beans.TestBean;
@@ -33,7 +34,6 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
@@ -456,7 +456,7 @@ class ErrorResponseExceptionTests {
}
private void assertDetailMessageCode(
ErrorResponse ex, @Nullable String suffix, @Nullable Object[] arguments) {
ErrorResponse ex, @Nullable String suffix, Object @Nullable [] arguments) {
assertThat(ex.getDetailMessageCode())
.isEqualTo(ErrorResponse.getDefaultDetailMessageCode(ex.getClass(), suffix));

View File

@@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@@ -35,7 +36,6 @@ import org.springframework.http.MediaType;
import org.springframework.http.codec.FormHttpMessageWriter;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.MultipartHttpMessageWriter;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
@@ -334,8 +334,7 @@ class WebExchangeDataBinderTests {
private final FilePart part;
@Nullable
private final FilePart nullablePart;
private final @Nullable FilePart nullablePart;
MultipartDataClass(FilePart part, @Nullable FilePart nullablePart) {
this.part = part;
@@ -346,8 +345,7 @@ class WebExchangeDataBinderTests {
return part;
}
@Nullable
public FilePart getNullablePart() {
public @Nullable FilePart getNullablePart() {
return nullablePart;
}
}

View File

@@ -20,6 +20,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
@@ -28,7 +29,6 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.StreamUtils;
import static java.nio.charset.StandardCharsets.UTF_8;

View File

@@ -24,6 +24,7 @@ import java.util.List;
import java.util.Map;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.http.client.ClientHttpRequestInitializer;
@@ -32,7 +33,6 @@ import org.springframework.http.client.JettyClientHttpRequestFactory;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.web.util.DefaultUriBuilderFactory;
import static org.assertj.core.api.Assertions.assertThat;
@@ -256,8 +256,7 @@ public class RestClientBuilderTests {
);
}
@Nullable
private static Object fieldValue(String name, DefaultRestClientBuilder instance) {
private static @Nullable Object fieldValue(String name, DefaultRestClientBuilder instance) {
try {
Field field = DefaultRestClientBuilder.class.getDeclaredField(name);
field.setAccessible(true);
@@ -270,8 +269,7 @@ public class RestClientBuilderTests {
}
}
@Nullable
private static Object fieldValue(String name, RestClient instance) {
private static @Nullable Object fieldValue(String name, RestClient instance) {
try {
Field field = DefaultRestClient.class.getDeclaredField(name);
field.setAccessible(true);

View File

@@ -26,6 +26,7 @@ import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.tck.TestObservationRegistry;
import io.micrometer.observation.tck.TestObservationRegistryAssert;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -39,7 +40,6 @@ import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.observation.ClientRequestObservationContext;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -234,8 +234,7 @@ class RestTemplateObservationTests {
final TestObservationRegistry observationRegistry;
@Nullable
Observation currentObservation;
@Nullable Observation currentObservation;
ObservationErrorHandler(TestObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;

View File

@@ -30,6 +30,7 @@ import io.micrometer.observation.tck.TestObservationRegistry;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -37,7 +38,6 @@ import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.CookieValue;

View File

@@ -24,13 +24,13 @@ import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;

View File

@@ -20,11 +20,11 @@ import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.testfixture.servlet.MockFilterChain;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;

View File

@@ -18,11 +18,11 @@ package org.springframework.web.jsf;
import jakarta.faces.application.NavigationHandler;
import jakarta.faces.context.FacesContext;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.StaticListableBeanFactory;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -21,6 +21,7 @@ import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -29,7 +30,6 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.lang.Nullable;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.MethodArgumentNotValidException;

View File

@@ -26,6 +26,7 @@ import java.util.function.Predicate;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
@@ -33,7 +34,6 @@ import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.method.MethodValidationResult;

View File

@@ -19,8 +19,9 @@ package org.springframework.web.method.support;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
@@ -33,8 +34,7 @@ public class StubArgumentResolver implements HandlerMethodArgumentResolver {
private final Class<?> valueType;
@Nullable
private final Object value;
private final @Nullable Object value;
private List<MethodParameter> resolvedParameters = new ArrayList<>();

View File

@@ -16,6 +16,7 @@
package org.springframework.web.service.annotation;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.aop.SpringProxy;
@@ -28,7 +29,6 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.DecoratingProxy;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -67,8 +67,7 @@ class HttpExchangeBeanRegistrationAotProcessorTests {
}
}
@Nullable
private static BeanRegistrationAotContribution createContribution(Class<?> beanClass) {
private static @Nullable BeanRegistrationAotContribution createContribution(Class<?> beanClass) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition(beanClass.getName(), new RootBeanDefinition(beanClass));
return new HttpExchangeBeanRegistrationAotProcessor()

View File

@@ -18,10 +18,10 @@ package org.springframework.web.service.invoker;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
@@ -95,8 +95,7 @@ class HttpMethodArgumentResolverTests {
}
@Nullable
private HttpMethod getActualMethod() {
private @Nullable HttpMethod getActualMethod() {
return this.client.getRequestValues().getHttpMethod();
}

View File

@@ -27,6 +27,7 @@ import java.util.Optional;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Single;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -38,7 +39,6 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;

View File

@@ -27,13 +27,13 @@ import java.util.Map;
import java.util.Optional;
import org.apache.groovy.util.Maps;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AliasFor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;

View File

@@ -16,9 +16,9 @@
package org.springframework.web.service.invoker;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;

View File

@@ -16,9 +16,9 @@
package org.springframework.web.service.invoker;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.service.annotation.GetExchange;

View File

@@ -20,12 +20,12 @@ import java.util.Optional;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Single;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.GetExchange;
@@ -168,18 +168,15 @@ class RequestBodyArgumentResolverTests {
}
@Nullable
private Object getBodyValue() {
private @Nullable Object getBodyValue() {
return getReactiveRequestValues().getBodyValue();
}
@Nullable
private Publisher<?> getPublisherBody() {
private @Nullable Publisher<?> getPublisherBody() {
return getReactiveRequestValues().getBodyPublisher();
}
@Nullable
private ParameterizedTypeReference<?> getBodyElementType() {
private @Nullable ParameterizedTypeReference<?> getBodyElementType() {
return getReactiveRequestValues().getBodyPublisherElementType();
}

View File

@@ -18,10 +18,11 @@ package org.springframework.web.service.invoker;
import java.util.Collections;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
@@ -33,14 +34,11 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class TestExchangeAdapter implements HttpExchangeAdapter {
@Nullable
private String invokedMethodName;
private @Nullable String invokedMethodName;
@Nullable
private HttpRequestValues requestValues;
private @Nullable HttpRequestValues requestValues;
@Nullable
private ParameterizedTypeReference<?> bodyType;
private @Nullable ParameterizedTypeReference<?> bodyType;
public String getInvokedMethodName() {
@@ -53,8 +51,7 @@ public class TestExchangeAdapter implements HttpExchangeAdapter {
return this.requestValues;
}
@Nullable
public ParameterizedTypeReference<?> getBodyType() {
public @Nullable ParameterizedTypeReference<?> getBodyType() {
return this.bodyType;
}

View File

@@ -18,9 +18,9 @@ package org.springframework.web.service.invoker;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;

View File

@@ -19,9 +19,9 @@ package org.springframework.web.service.invoker;
import java.net.URI;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
import org.springframework.web.service.annotation.GetExchange;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -16,10 +16,9 @@
package org.springframework.web.util;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
/**