Add nullability annotations to module/spring-boot-webflux

See gh-46587
This commit is contained in:
Moritz Halbritter
2025-08-07 10:48:44 +02:00
parent 840fc57d7a
commit 3dd8bf98bc
28 changed files with 214 additions and 112 deletions
@@ -26,6 +26,7 @@ import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -56,11 +57,13 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.ResponseEntity.BodyBuilder;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@@ -96,13 +99,11 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
private final EndpointMediaTypes endpointMediaTypes;
private final CorsConfiguration corsConfiguration;
private final @Nullable CorsConfiguration corsConfiguration;
private final Method handleWriteMethod = ReflectionUtils.findMethod(WriteOperationHandler.class, "handle",
ServerWebExchange.class, Map.class);
private final Method handleWriteMethod = getHandleWriteMethod();
private final Method handleReadMethod = ReflectionUtils.findMethod(ReadOperationHandler.class, "handle",
ServerWebExchange.class);
private final Method handleReadMethod = getHandleReadMethod();
private final boolean shouldRegisterLinksMapping;
@@ -117,7 +118,7 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
*/
public AbstractWebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<ExposableWebEndpoint> endpoints, EndpointMediaTypes endpointMediaTypes,
CorsConfiguration corsConfiguration, boolean shouldRegisterLinksMapping) {
@Nullable CorsConfiguration corsConfiguration, boolean shouldRegisterLinksMapping) {
this.endpointMapping = endpointMapping;
this.endpoints = endpoints;
this.endpointMediaTypes = endpointMediaTypes;
@@ -126,6 +127,19 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
setOrder(-100);
}
private static Method getHandleWriteMethod() {
Method method = ReflectionUtils.findMethod(WriteOperationHandler.class, "handle", ServerWebExchange.class,
Map.class);
Assert.state(method != null, "'method' must not be null");
return method;
}
private static Method getHandleReadMethod() {
Method method = ReflectionUtils.findMethod(ReadOperationHandler.class, "handle", ServerWebExchange.class);
Assert.state(method != null, "'method' must not be null");
return method;
}
@Override
protected void initHandlerMethods() {
for (ExposableWebEndpoint endpoint : this.endpoints) {
@@ -204,8 +218,9 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
.produces(produces)
.build();
LinksHandler linksHandler = getLinksHandler();
registerMapping(mapping, linksHandler,
ReflectionUtils.findMethod(linksHandler.getClass(), "links", ServerWebExchange.class));
Method linksMethod = ReflectionUtils.findMethod(linksHandler.getClass(), "links", ServerWebExchange.class);
Assert.state(linksMethod != null, "'linksMethod' must not be null");
registerMapping(mapping, linksHandler, linksMethod);
}
@Override
@@ -214,12 +229,13 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
}
@Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mapping) {
protected @Nullable CorsConfiguration initCorsConfiguration(Object handler, Method method,
RequestMappingInfo mapping) {
return this.corsConfiguration;
}
@Override
protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) {
protected @Nullable CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) {
CorsConfiguration corsConfiguration = super.getCorsConfiguration(handler, exchange);
return (corsConfiguration != null) ? corsConfiguration : this.corsConfiguration;
}
@@ -230,7 +246,7 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
}
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
protected @Nullable RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
return null;
}
@@ -277,7 +293,7 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
}
@Override
public Object invoke(InvocationContext context) {
public @Nullable Object invoke(InvocationContext context) {
try {
return this.invoker.invoke(context);
}
@@ -304,7 +320,7 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
@FunctionalInterface
protected interface ReactiveWebOperation {
Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange, Map<String, String> body);
Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange, @Nullable Map<String, String> body);
}
@@ -355,7 +371,7 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
}
@Override
public Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange, Map<String, String> body) {
public Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange, @Nullable Map<String, String> body) {
Map<String, Object> arguments = getArguments(exchange, body);
OperationArgumentResolver serverNamespaceArgumentResolver = OperationArgumentResolver
.of(WebServerNamespace.class, () -> WebServerNamespace
@@ -369,7 +385,7 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
exchange.getRequest().getMethod()));
}
private Map<String, Object> getArguments(ServerWebExchange exchange, Map<String, String> body) {
private Map<String, Object> getArguments(ServerWebExchange exchange, @Nullable Map<String, String> body) {
Map<String, Object> arguments = new LinkedHashMap<>(getTemplateVariables(exchange));
String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate()
.getMatchAllRemainingPathSegmentsVariable();
@@ -387,6 +403,7 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
private Object getRemainingPathSegments(ServerWebExchange exchange) {
PathPattern pathPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
Assert.state(pathPattern != null, "'pathPattern' must not be null");
if (pathPattern.hasPatternSyntax()) {
String remainingSegments = pathPattern
.extractPathWithinPattern(exchange.getRequest().getPath().pathWithinApplication())
@@ -407,10 +424,12 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
}
private Map<String, String> getTemplateVariables(ServerWebExchange exchange) {
return exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
Map<String, String> result = exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
Assert.state(result != null, "'result' must not be null");
return result;
}
private Mono<ResponseEntity<Object>> handleResult(Publisher<?> result, HttpMethod httpMethod) {
private Mono<ResponseEntity<Object>> handleResult(@Nullable Publisher<?> result, HttpMethod httpMethod) {
if (result instanceof Flux) {
result = ((Flux<?>) result).collectList();
}
@@ -428,9 +447,11 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
}
MediaType contentType = (webEndpointResponse.getContentType() != null)
? new MediaType(webEndpointResponse.getContentType()) : null;
return ResponseEntity.status(webEndpointResponse.getStatus())
.contentType(contentType)
.body(webEndpointResponse.getBody());
BodyBuilder builder = ResponseEntity.status(webEndpointResponse.getStatus());
if (contentType != null) {
builder = builder.contentType(contentType);
}
return builder.body(webEndpointResponse.getBody());
}
@Override
@@ -512,18 +533,18 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
private static final String ROLE_PREFIX = "ROLE_";
private final Authentication authentication;
private final @Nullable Authentication authentication;
ReactiveSecurityContext(Authentication authentication) {
ReactiveSecurityContext(@Nullable Authentication authentication) {
this.authentication = authentication;
}
private Authentication getAuthentication() {
private @Nullable Authentication getAuthentication() {
return this.authentication;
}
@Override
public Principal getPrincipal() {
public @Nullable Principal getPrincipal() {
return this.authentication;
}
@@ -531,7 +552,7 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
public boolean isUserInRole(String role) {
String authority = (!role.startsWith(ROLE_PREFIX)) ? ROLE_PREFIX + role : role;
AuthorizationResult result = AuthorityAuthorizationManager.hasAuthority(authority)
.authorize(this::getAuthentication, null);
.authorize(this::getAuthentication, new Object());
return result != null && result.isGranted();
}
@@ -542,7 +563,7 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
private final ReflectiveRuntimeHintsRegistrar reflectiveRegistrar = new ReflectiveRuntimeHintsRegistrar();
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
this.reflectiveRegistrar.registerRuntimeHints(hints, WriteOperationHandler.class,
ReadOperationHandler.class);
}
@@ -20,7 +20,11 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
@@ -42,19 +46,19 @@ public class AdditionalHealthEndpointPathsWebFluxHandlerMapping extends Abstract
private final EndpointMapping endpointMapping;
private final ExposableWebEndpoint healthEndpoint;
private final @Nullable ExposableWebEndpoint healthEndpoint;
private final Set<HealthEndpointGroup> groups;
public AdditionalHealthEndpointPathsWebFluxHandlerMapping(EndpointMapping endpointMapping,
ExposableWebEndpoint healthEndpoint, Set<HealthEndpointGroup> groups) {
super(endpointMapping, asList(healthEndpoint), null, null, false);
@Nullable ExposableWebEndpoint healthEndpoint, Set<HealthEndpointGroup> groups) {
super(endpointMapping, asList(healthEndpoint), new EndpointMediaTypes(), null, false);
this.endpointMapping = endpointMapping;
this.groups = groups;
this.healthEndpoint = healthEndpoint;
}
private static Collection<ExposableWebEndpoint> asList(ExposableWebEndpoint healthEndpoint) {
private static Collection<ExposableWebEndpoint> asList(@Nullable ExposableWebEndpoint healthEndpoint) {
return (healthEndpoint != null) ? Collections.singletonList(healthEndpoint) : Collections.emptyList();
}
@@ -90,7 +94,7 @@ public class AdditionalHealthEndpointPathsWebFluxHandlerMapping extends Abstract
@Override
protected LinksHandler getLinksHandler() {
return null;
return (exchange) -> Mono.empty();
}
}
@@ -25,6 +25,8 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.endpoint.Access;
import org.springframework.boot.actuate.endpoint.EndpointAccessResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
@@ -58,7 +60,7 @@ public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMappi
private final EndpointMapping endpointMapping;
private final CorsConfiguration corsConfiguration;
private final @Nullable CorsConfiguration corsConfiguration;
private final Map<Object, ExposableControllerEndpoint> handlers;
@@ -72,7 +74,7 @@ public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMappi
* @param corsConfiguration the CORS configuration for the endpoints or {@code null}
*/
public ControllerEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<ExposableControllerEndpoint> endpoints, CorsConfiguration corsConfiguration) {
Collection<ExposableControllerEndpoint> endpoints, @Nullable CorsConfiguration corsConfiguration) {
this(endpointMapping, endpoints, corsConfiguration, (endpointId, defaultAccess) -> Access.NONE);
}
@@ -85,7 +87,7 @@ public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMappi
* @param endpointAccessResolver resolver for endpoint access
*/
public ControllerEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<ExposableControllerEndpoint> endpoints, CorsConfiguration corsConfiguration,
Collection<ExposableControllerEndpoint> endpoints, @Nullable CorsConfiguration corsConfiguration,
EndpointAccessResolver endpointAccessResolver) {
Assert.notNull(endpointMapping, "'endpointMapping' must not be null");
Assert.notNull(endpoints, "'endpoints' must not be null");
@@ -110,6 +112,7 @@ public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMappi
@Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
ExposableControllerEndpoint endpoint = this.handlers.get(handler);
Assert.state(endpoint != null, "'endpoint' must not be null");
Access access = this.accessResolver.accessFor(endpoint.getEndpointId(), endpoint.getDefaultAccess());
if (access == Access.NONE) {
return;
@@ -157,7 +160,8 @@ public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMappi
}
@Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mapping) {
protected @Nullable CorsConfiguration initCorsConfiguration(Object handler, Method method,
RequestMappingInfo mapping) {
return this.corsConfiguration;
}
@@ -20,6 +20,8 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
@@ -65,7 +67,7 @@ public class WebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandle
* @param shouldRegisterLinksMapping whether the links endpoint should be registered
*/
public WebFluxEndpointHandlerMapping(EndpointMapping endpointMapping, Collection<ExposableWebEndpoint> endpoints,
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
EndpointMediaTypes endpointMediaTypes, @Nullable CorsConfiguration corsConfiguration,
EndpointLinksResolver linksResolver, boolean shouldRegisterLinksMapping) {
super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, shouldRegisterLinksMapping);
this.linksResolver = linksResolver;
@@ -107,7 +109,7 @@ public class WebFluxEndpointHandlerMapping extends AbstractWebFluxEndpointHandle
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
this.reflectiveRegistrar.registerRuntimeHints(hints, WebFluxLinksHandler.class);
this.bindingRegistrar.registerReflectionHints(hints.reflection(), Link.class);
}
@@ -17,4 +17,7 @@
/**
* Spring WebFlux support for actuator endpoints.
*/
@NullMarked
package org.springframework.boot.webflux.actuate.endpoint.web;
import org.jspecify.annotations.NullMarked;
@@ -19,6 +19,7 @@ package org.springframework.boot.webflux.actuate.exchanges;
import java.security.Principal;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.web.exchanges.HttpExchange;
@@ -98,20 +99,20 @@ public class HttpExchangesWebFilter implements WebFilter, Ordered {
*/
private static class PrincipalAndSession {
private final Principal principal;
private final @Nullable Principal principal;
private final WebSession session;
private final @Nullable WebSession session;
PrincipalAndSession(Object[] zipped) {
this.principal = (zipped[0] != NONE) ? (Principal) zipped[0] : null;
this.session = (zipped[1] != NONE) ? (WebSession) zipped[1] : null;
}
Principal getPrincipal() {
@Nullable Principal getPrincipal() {
return this.principal;
}
String getSessionId() {
@Nullable String getSessionId() {
return (this.session != null && this.session.isStarted()) ? this.session.getId() : null;
}
@@ -24,6 +24,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.web.exchanges.RecordableHttpRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -41,7 +43,7 @@ class RecordableServerHttpRequest implements RecordableHttpRequest {
private final URI uri;
private final String remoteAddress;
private final @Nullable String remoteAddress;
RecordableServerHttpRequest(ServerHttpRequest request) {
this.method = request.getMethod().name();
@@ -50,7 +52,7 @@ class RecordableServerHttpRequest implements RecordableHttpRequest {
this.remoteAddress = getRemoteAddress(request);
}
private static String getRemoteAddress(ServerHttpRequest request) {
private static @Nullable String getRemoteAddress(ServerHttpRequest request) {
InetSocketAddress remoteAddress = request.getRemoteAddress();
InetAddress address = (remoteAddress != null) ? remoteAddress.getAddress() : null;
return (address != null) ? address.toString() : null;
@@ -74,7 +76,7 @@ class RecordableServerHttpRequest implements RecordableHttpRequest {
}
@Override
public String getRemoteAddress() {
public @Nullable String getRemoteAddress() {
return this.remoteAddress;
}
@@ -19,4 +19,7 @@
*
* @see org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository
*/
@NullMarked
package org.springframework.boot.webflux.actuate.exchanges;
import org.jspecify.annotations.NullMarked;
@@ -16,6 +16,8 @@
package org.springframework.boot.webflux.actuate.mappings;
import org.jspecify.annotations.Nullable;
import org.springframework.web.reactive.DispatcherHandler;
/**
@@ -30,9 +32,10 @@ public class DispatcherHandlerMappingDescription {
private final String handler;
private final DispatcherHandlerMappingDetails details;
private final @Nullable DispatcherHandlerMappingDetails details;
DispatcherHandlerMappingDescription(String predicate, String handler, DispatcherHandlerMappingDetails details) {
DispatcherHandlerMappingDescription(String predicate, String handler,
@Nullable DispatcherHandlerMappingDetails details) {
this.predicate = predicate;
this.handler = handler;
this.details = details;
@@ -46,7 +49,7 @@ public class DispatcherHandlerMappingDescription {
return this.predicate;
}
public DispatcherHandlerMappingDetails getDetails() {
public @Nullable DispatcherHandlerMappingDetails getDetails() {
return this.details;
}
@@ -16,6 +16,8 @@
package org.springframework.boot.webflux.actuate.mappings;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.web.mappings.HandlerMethodDescription;
import org.springframework.web.reactive.DispatcherHandler;
@@ -27,33 +29,33 @@ import org.springframework.web.reactive.DispatcherHandler;
*/
public class DispatcherHandlerMappingDetails {
private HandlerMethodDescription handlerMethod;
private @Nullable HandlerMethodDescription handlerMethod;
private HandlerFunctionDescription handlerFunction;
private @Nullable HandlerFunctionDescription handlerFunction;
private RequestMappingConditionsDescription requestMappingConditions;
private @Nullable RequestMappingConditionsDescription requestMappingConditions;
public HandlerMethodDescription getHandlerMethod() {
public @Nullable HandlerMethodDescription getHandlerMethod() {
return this.handlerMethod;
}
void setHandlerMethod(HandlerMethodDescription handlerMethod) {
void setHandlerMethod(@Nullable HandlerMethodDescription handlerMethod) {
this.handlerMethod = handlerMethod;
}
public HandlerFunctionDescription getHandlerFunction() {
public @Nullable HandlerFunctionDescription getHandlerFunction() {
return this.handlerFunction;
}
void setHandlerFunction(HandlerFunctionDescription handlerFunction) {
void setHandlerFunction(@Nullable HandlerFunctionDescription handlerFunction) {
this.handlerFunction = handlerFunction;
}
public RequestMappingConditionsDescription getRequestMappingConditions() {
public @Nullable RequestMappingConditionsDescription getRequestMappingConditions() {
return this.requestMappingConditions;
}
void setRequestMappingConditions(RequestMappingConditionsDescription requestMappingConditions) {
void setRequestMappingConditions(@Nullable RequestMappingConditionsDescription requestMappingConditions) {
this.requestMappingConditions = requestMappingConditions;
}
@@ -18,6 +18,7 @@ package org.springframework.boot.webflux.actuate.mappings;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -25,6 +26,7 @@ import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
@@ -78,7 +80,11 @@ public class DispatcherHandlersMappingDescriptionProvider implements MappingDesc
}
private List<DispatcherHandlerMappingDescription> describeMappings(DispatcherHandler dispatcherHandler) {
return dispatcherHandler.getHandlerMappings().stream().flatMap(this::describe).toList();
List<HandlerMapping> handlerMappings = dispatcherHandler.getHandlerMappings();
if (handlerMappings == null) {
return Collections.emptyList();
}
return handlerMappings.stream().flatMap(this::describe).toList();
}
@SuppressWarnings("unchecked")
@@ -202,7 +208,7 @@ public class DispatcherHandlersMappingDescriptionProvider implements MappingDesc
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
this.bindingRegistrar.registerReflectionHints(hints.reflection(),
DispatcherHandlerMappingDescription.class);
}
@@ -21,6 +21,8 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.reactive.result.condition.MediaTypeExpression;
import org.springframework.web.reactive.result.condition.NameValueExpression;
@@ -131,7 +133,7 @@ public class RequestMappingConditionsDescription {
private final String name;
private final Object value;
private final @Nullable Object value;
private final boolean negated;
@@ -145,7 +147,7 @@ public class RequestMappingConditionsDescription {
return this.name;
}
public Object getValue() {
public @Nullable Object getValue() {
return this.value;
}
@@ -17,4 +17,7 @@
/**
* Actuator reactive request mappings support.
*/
@NullMarked
package org.springframework.boot.webflux.actuate.mappings;
import org.jspecify.annotations.NullMarked;
@@ -19,6 +19,8 @@ package org.springframework.boot.webflux.autoconfigure;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader;
import org.springframework.http.codec.multipart.PartEventHttpMessageReader;
@@ -64,7 +66,7 @@ public class ReactiveMultipartProperties {
* directory named 'spring-multipart' created under the system temporary directory.
* Ignored when using the PartEvent streaming support.
*/
private String fileStorageDirectory;
private @Nullable String fileStorageDirectory;
/**
* Character set used to decode headers.
@@ -103,11 +105,11 @@ public class ReactiveMultipartProperties {
this.maxParts = maxParts;
}
public String getFileStorageDirectory() {
public @Nullable String getFileStorageDirectory() {
return this.fileStorageDirectory;
}
public void setFileStorageDirectory(String fileStorageDirectory) {
public void setFileStorageDirectory(@Nullable String fileStorageDirectory) {
this.fileStorageDirectory = fileStorageDirectory;
}
@@ -17,6 +17,7 @@
package org.springframework.boot.webflux.autoconfigure;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.util.Assert;
import org.springframework.web.reactive.config.ResourceChainRegistration;
import org.springframework.web.reactive.config.ResourceHandlerRegistration;
import org.springframework.web.reactive.resource.EncodedResourceResolver;
@@ -58,6 +59,7 @@ class ResourceChainResourceHandlerRegistrationCustomizer implements ResourceHand
if (properties.getFixed().isEnabled()) {
String version = properties.getFixed().getVersion();
String[] paths = properties.getFixed().getPaths();
Assert.state(version != null, "'version' must not be null");
resolver.addFixedVersionStrategy(version, paths);
}
if (properties.getContent().isEnabled()) {
@@ -18,6 +18,7 @@ package org.springframework.boot.webflux.autoconfigure;
import java.time.Duration;
import java.util.List;
import java.util.Locale;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -65,7 +66,9 @@ import org.springframework.core.env.Environment;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.CacheControl;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.validation.Validator;
import org.springframework.web.accept.ApiVersionParser;
@@ -137,7 +140,7 @@ public final class WebFluxAutoConfiguration {
static class WelcomePageConfiguration {
@Bean
RouterFunctionMapping welcomePageRouterFunctionMapping(ApplicationContext applicationContext,
@Nullable RouterFunctionMapping welcomePageRouterFunctionMapping(ApplicationContext applicationContext,
WebFluxProperties webFluxProperties, WebProperties webProperties) {
String[] staticLocations = webProperties.getResources().getStaticLocations();
WelcomePageRouterFunctionFactory factory = new WelcomePageRouterFunctionFactory(
@@ -258,7 +261,10 @@ public final class WebFluxAutoConfiguration {
if (cachePeriod != null && cacheControl.getMaxAge() == null) {
cacheControl.setMaxAge(cachePeriod);
}
registration.setCacheControl(cacheControl.toHttpCacheControl());
CacheControl httpCacheControl = cacheControl.toHttpCacheControl();
if (httpCacheControl != null) {
registration.setCacheControl(httpCacheControl);
}
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
}
@@ -310,7 +316,7 @@ public final class WebFluxAutoConfiguration {
private final ServerProperties serverProperties;
private final WebFluxRegistrations webFluxRegistrations;
private final @Nullable WebFluxRegistrations webFluxRegistrations;
EnableWebFluxConfiguration(WebFluxProperties webFluxProperties, WebProperties webProperties,
ServerProperties serverProperties, ObjectProvider<WebFluxRegistrations> webFluxRegistrations) {
@@ -340,7 +346,9 @@ public final class WebFluxAutoConfiguration {
getClass().getClassLoader())) {
return super.webFluxValidator();
}
return ValidatorAdapter.get(getApplicationContext(), getValidator());
ApplicationContext applicationContext = getApplicationContext();
Assert.state(applicationContext != null, "'applicationContext' must not be null");
return ValidatorAdapter.get(applicationContext, getValidator());
}
@Override
@@ -369,11 +377,13 @@ public final class WebFluxAutoConfiguration {
@Override
@ConditionalOnMissingBean(name = WebHttpHandlerBuilder.LOCALE_CONTEXT_RESOLVER_BEAN_NAME)
public LocaleContextResolver localeContextResolver() {
Locale locale = this.webProperties.getLocale();
if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
return new FixedLocaleContextResolver(this.webProperties.getLocale());
Assert.state(locale != null, "'locale' must not be null");
return new FixedLocaleContextResolver(locale);
}
AcceptHeaderLocaleContextResolver localeContextResolver = new AcceptHeaderLocaleContextResolver();
localeContextResolver.setDefaultLocale(this.webProperties.getLocale());
localeContextResolver.setDefaultLocale(locale);
return localeContextResolver;
}
@@ -20,6 +20,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.Name;
import org.springframework.http.MediaType;
@@ -38,7 +40,7 @@ public class WebFluxProperties {
/**
* Base path for all web handlers.
*/
private String basePath;
private @Nullable String basePath;
private final Format format = new Format();
@@ -56,15 +58,15 @@ public class WebFluxProperties {
*/
private String webjarsPathPattern = "/webjars/**";
public String getBasePath() {
public @Nullable String getBasePath() {
return this.basePath;
}
public void setBasePath(String basePath) {
public void setBasePath(@Nullable String basePath) {
this.basePath = cleanBasePath(basePath);
}
private String cleanBasePath(String basePath) {
private @Nullable String cleanBasePath(@Nullable String basePath) {
String candidate = null;
if (StringUtils.hasLength(basePath)) {
candidate = basePath.strip();
@@ -114,41 +116,41 @@ public class WebFluxProperties {
* Date format to use, for example 'dd/MM/yyyy'. Used for formatting of
* java.util.Date and java.time.LocalDate.
*/
private String date;
private @Nullable String date;
/**
* Time format to use, for example 'HH:mm:ss'. Used for formatting of java.time's
* LocalTime and OffsetTime.
*/
private String time;
private @Nullable String time;
/**
* Date-time format to use, for example 'yyyy-MM-dd HH:mm:ss'. Used for formatting
* of java.time's LocalDateTime, OffsetDateTime, and ZonedDateTime.
*/
private String dateTime;
private @Nullable String dateTime;
public String getDate() {
public @Nullable String getDate() {
return this.date;
}
public void setDate(String date) {
public void setDate(@Nullable String date) {
this.date = date;
}
public String getTime() {
public @Nullable String getTime() {
return this.time;
}
public void setTime(String time) {
public void setTime(@Nullable String time) {
this.time = time;
}
public String getDateTime() {
public @Nullable String getDateTime() {
return this.dateTime;
}
public void setDateTime(String dateTime) {
public void setDateTime(@Nullable String dateTime) {
this.dateTime = dateTime;
}
@@ -176,58 +178,58 @@ public class WebFluxProperties {
/**
* Whether the API version is required with each request.
*/
private Boolean required;
private @Nullable Boolean required;
/**
* Default version that should be used for each request.
*/
@Name("default")
private String defaultVersion;
private @Nullable String defaultVersion;
/**
* Supported versions.
*/
private List<String> supported;
private @Nullable List<String> supported;
/**
* Whether supported versions should be detected from controllers.
*/
private Boolean detectSupported;
private @Nullable Boolean detectSupported;
/**
* How version details should be inserted into requests.
*/
private final Use use = new Use();
public Boolean getRequired() {
public @Nullable Boolean getRequired() {
return this.required;
}
public void setRequired(Boolean required) {
public void setRequired(@Nullable Boolean required) {
this.required = required;
}
public String getDefaultVersion() {
public @Nullable String getDefaultVersion() {
return this.defaultVersion;
}
public void setDefaultVersion(String defaultVersion) {
public void setDefaultVersion(@Nullable String defaultVersion) {
this.defaultVersion = defaultVersion;
}
public List<String> getSupported() {
public @Nullable List<String> getSupported() {
return this.supported;
}
public void setSupported(List<String> supported) {
public void setSupported(@Nullable List<String> supported) {
this.supported = supported;
}
public Boolean getDetectSupported() {
public @Nullable Boolean getDetectSupported() {
return this.detectSupported;
}
public void setDetectSupported(Boolean detectSupported) {
public void setDetectSupported(@Nullable Boolean detectSupported) {
this.detectSupported = detectSupported;
}
@@ -240,44 +242,44 @@ public class WebFluxProperties {
/**
* Use the HTTP header with the given name to obtain the version.
*/
private String header;
private @Nullable String header;
/**
* Use the query parameter with the given name to obtain the version.
*/
private String queryParameter;
private @Nullable String queryParameter;
/**
* Use the path segment at the given index to obtain the version.
*/
private Integer pathSegment;
private @Nullable Integer pathSegment;
/**
* Use the media type parameter with the given name to obtain the version.
*/
private Map<MediaType, String> mediaTypeParameter = new LinkedHashMap<>();
public String getHeader() {
public @Nullable String getHeader() {
return this.header;
}
public void setHeader(String header) {
public void setHeader(@Nullable String header) {
this.header = header;
}
public String getQueryParameter() {
public @Nullable String getQueryParameter() {
return this.queryParameter;
}
public void setQueryParameter(String queryParameter) {
public void setQueryParameter(@Nullable String queryParameter) {
this.queryParameter = queryParameter;
}
public Integer getPathSegment() {
public @Nullable Integer getPathSegment() {
return this.pathSegment;
}
public void setPathSegment(Integer pathSegment) {
public void setPathSegment(@Nullable Integer pathSegment) {
this.pathSegment = pathSegment;
}
@@ -16,6 +16,8 @@
package org.springframework.boot.webflux.autoconfigure;
import org.jspecify.annotations.Nullable;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
@@ -38,7 +40,7 @@ public interface WebFluxRegistrations {
* processed by the WebFlux configuration.
* @return the custom {@link RequestMappingHandlerMapping} instance
*/
default RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
default @Nullable RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return null;
}
@@ -47,7 +49,7 @@ public interface WebFluxRegistrations {
* processed by the WebFlux configuration.
* @return the custom {@link RequestMappingHandlerAdapter} instance
*/
default RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
default @Nullable RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return null;
}
@@ -76,6 +76,11 @@ public final class WebSessionIdResolverAutoConfiguration {
map.from(cookie::getSecure).to(builder::secure);
map.from(cookie::getMaxAge).to(builder::maxAge);
map.from(cookie::getPartitioned).to(builder::partitioned);
setSameSite(builder, map, cookie);
}
@SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability
private void setSameSite(ResponseCookieBuilder builder, PropertyMapper map, Cookie cookie) {
map.from(cookie::getSameSite).as(SameSite::attributeValue).to(builder::sameSite);
}
@@ -18,6 +18,8 @@ package org.springframework.boot.webflux.autoconfigure;
import java.util.Arrays;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
@@ -41,7 +43,7 @@ final class WelcomePageRouterFunctionFactory {
private final String staticPathPattern;
private final Resource welcomePage;
private final @Nullable Resource welcomePage;
private final boolean welcomePageTemplateExists;
@@ -52,7 +54,7 @@ final class WelcomePageRouterFunctionFactory {
this.welcomePageTemplateExists = welcomeTemplateExists(templateAvailabilityProviders, applicationContext);
}
private Resource getWelcomePage(ResourceLoader resourceLoader, String[] staticLocations) {
private @Nullable Resource getWelcomePage(ResourceLoader resourceLoader, String[] staticLocations) {
return Arrays.stream(staticLocations)
.map((location) -> getIndexHtml(resourceLoader, location))
.filter(this::isReadable)
@@ -78,7 +80,7 @@ final class WelcomePageRouterFunctionFactory {
return templateAvailabilityProviders.getProvider("index", applicationContext) != null;
}
RouterFunction<ServerResponse> createRouterFunction() {
@Nullable RouterFunction<ServerResponse> createRouterFunction() {
if (this.welcomePage != null && "/**".equals(this.staticPathPattern)) {
return RouterFunctions.route(GET("/").and(accept(MediaType.TEXT_HTML)),
(req) -> ServerResponse.ok().contentType(MediaType.TEXT_HTML).bodyValue(this.welcomePage));
@@ -17,4 +17,7 @@
/**
* Auto-configuration for Spring WebFlux actuator web endpoint support.
*/
@NullMarked
package org.springframework.boot.webflux.autoconfigure.actuate.endpoint.web;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* Auto-configuration for WebFlux-based actuator web concerns.
*/
@NullMarked
package org.springframework.boot.webflux.autoconfigure.actuate.web;
import org.jspecify.annotations.NullMarked;
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.InitializingBean;
@@ -135,7 +136,7 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
* @param request the source request
* @return the error
*/
protected Throwable getError(ServerRequest request) {
protected @Nullable Throwable getError(ServerRequest request) {
return this.errorAttributes.getError(request);
}
@@ -208,7 +209,7 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
return this.templateAvailabilityProviders.getProvider(viewName, this.applicationContext) != null;
}
private Resource resolveResource(String viewName) {
private @Nullable Resource resolveResource(String viewName) {
for (String location : this.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
@@ -261,7 +262,7 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
return responseBody.bodyValue(builder.toString());
}
private String htmlEscape(Object input) {
private @Nullable String htmlEscape(@Nullable Object input) {
return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null;
}
@@ -17,4 +17,7 @@
/**
* Auto-configuration for Spring WebFlux error handling.
*/
@NullMarked
package org.springframework.boot.webflux.autoconfigure.error;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* Auto-configuration for reactive web servers and Spring WebFlux.
*/
@NullMarked
package org.springframework.boot.webflux.autoconfigure;
import org.jspecify.annotations.NullMarked;
@@ -19,6 +19,8 @@ package org.springframework.boot.webflux.error;
import java.util.Collections;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
@@ -51,7 +53,7 @@ public interface ErrorAttributes {
* @param request the source ServerRequest
* @return the {@link Exception} that caused the error or {@code null}
*/
Throwable getError(ServerRequest request);
@Nullable Throwable getError(ServerRequest request);
/**
* Store the given error information in the current {@link ServerWebExchange}.
@@ -17,4 +17,7 @@
/**
* Spring WebFlux error handling infrastructure.
*/
@NullMarked
package org.springframework.boot.webflux.error;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* Spring Boot specific {@link org.springframework.web.server.WebFilter} implementations.
*/
@NullMarked
package org.springframework.boot.webflux.filter;
import org.jspecify.annotations.NullMarked;