From 279258f69955a2e68ba157decaf0a26f497e92ab Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Thu, 7 Aug 2025 09:03:01 +0200 Subject: [PATCH] Add nullability annotations to module/spring-boot-webmvc See gh-46587 --- .../AbstractWebMvcEndpointHandlerMapping.java | 57 ++++++++---- ...althEndpointPathsWebMvcHandlerMapping.java | 13 ++- .../web/ControllerEndpointHandlerMapping.java | 22 +++-- .../web/WebMvcEndpointHandlerMapping.java | 5 +- .../actuate/endpoint/web/package-info.java | 3 + .../DispatcherServletHandlerMappings.java | 6 +- .../DispatcherServletMappingDescription.java | 9 +- .../DispatcherServletMappingDetails.java | 20 ++-- ...herServletsMappingDescriptionProvider.java | 3 +- .../RequestMappingConditionsDescription.java | 16 +++- .../webmvc/actuate/mappings/package-info.java | 3 + .../DispatcherServletAutoConfiguration.java | 3 + .../WebMvcAutoConfiguration.java | 48 +++++++--- .../autoconfigure/WebMvcProperties.java | 92 ++++++++++--------- .../autoconfigure/WebMvcRegistrations.java | 8 +- .../webmvc/autoconfigure/WelcomePage.java | 10 +- .../WelcomePageHandlerMapping.java | 5 +- ...elcomePageNotAcceptableHandlerMapping.java | 7 +- ...lthEndpointExtensionAutoConfiguration.java | 4 +- .../actuate/endpoint/web/package-info.java | 3 + .../actuate/web/CompositeHandlerAdapter.java | 5 +- .../CompositeHandlerExceptionResolver.java | 9 +- .../actuate/web/CompositeHandlerMapping.java | 5 +- .../actuate/web/package-info.java | 3 + .../error/AbstractErrorController.java | 10 +- .../error/DefaultErrorViewResolver.java | 10 +- .../error/ErrorMvcAutoConfiguration.java | 15 ++- .../error/ErrorViewResolver.java | 3 +- .../autoconfigure/error/package-info.java | 3 + .../webmvc/autoconfigure/package-info.java | 3 + .../webmvc/error/DefaultErrorAttributes.java | 21 +++-- .../boot/webmvc/error/ErrorAttributes.java | 4 +- .../boot/webmvc/error/package-info.java | 3 + 33 files changed, 276 insertions(+), 155 deletions(-) diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AbstractWebMvcEndpointHandlerMapping.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AbstractWebMvcEndpointHandlerMapping.java index ec15ea953db..a50fe1bfe90 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AbstractWebMvcEndpointHandlerMapping.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AbstractWebMvcEndpointHandlerMapping.java @@ -30,6 +30,7 @@ import java.util.function.Function; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Flux; import org.springframework.aot.hint.RuntimeHints; @@ -58,6 +59,7 @@ 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.http.server.ServletServerHttpRequest; import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; @@ -96,12 +98,11 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin private final EndpointMediaTypes endpointMediaTypes; - private final CorsConfiguration corsConfiguration; + private final @Nullable CorsConfiguration corsConfiguration; private final boolean shouldRegisterLinksMapping; - private final Method handleMethod = ReflectionUtils.findMethod(OperationHandler.class, "handle", - HttpServletRequest.class, Map.class); + private final Method handleMethod = getHandleMethod(); private RequestMappingInfo.BuilderConfiguration builderConfig = new RequestMappingInfo.BuilderConfiguration(); @@ -130,7 +131,7 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin */ public AbstractWebMvcEndpointHandlerMapping(EndpointMapping endpointMapping, Collection endpoints, EndpointMediaTypes endpointMediaTypes, - CorsConfiguration corsConfiguration, boolean shouldRegisterLinksMapping) { + @Nullable CorsConfiguration corsConfiguration, boolean shouldRegisterLinksMapping) { this.endpointMapping = endpointMapping; this.endpoints = endpoints; this.endpointMediaTypes = endpointMediaTypes; @@ -139,6 +140,13 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin setOrder(-100); } + private static Method getHandleMethod() { + Method method = ReflectionUtils.findMethod(OperationHandler.class, "handle", HttpServletRequest.class, + Map.class); + Assert.state(method != null, "'method' must not be null"); + return method; + } + @Override public void afterPropertiesSet() { this.builderConfig = new RequestMappingInfo.BuilderConfiguration(); @@ -219,8 +227,10 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin .options(this.builderConfig) .build(); LinksHandler linksHandler = getLinksHandler(); - registerMapping(mapping, linksHandler, ReflectionUtils.findMethod(linksHandler.getClass(), "links", - HttpServletRequest.class, HttpServletResponse.class)); + Method links = ReflectionUtils.findMethod(linksHandler.getClass(), "links", HttpServletRequest.class, + HttpServletResponse.class); + Assert.state(links != null, "'links' must not be null"); + registerMapping(mapping, linksHandler, links); } @Override @@ -229,12 +239,13 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin } @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, HttpServletRequest request) { + protected @Nullable CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) { CorsConfiguration corsConfiguration = super.getCorsConfiguration(handler, request); return (corsConfiguration != null) ? corsConfiguration : this.corsConfiguration; } @@ -245,7 +256,7 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin } @Override - protected RequestMappingInfo getMappingForMethod(Method method, Class handlerType) { + protected @Nullable RequestMappingInfo getMappingForMethod(Method method, Class handlerType) { return null; } @@ -269,7 +280,7 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin @FunctionalInterface protected interface LinksHandler { - Object links(HttpServletRequest request, HttpServletResponse response); + @Nullable Object links(HttpServletRequest request, HttpServletResponse response); } @@ -279,7 +290,7 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin @FunctionalInterface protected interface ServletWebOperation { - Object handle(HttpServletRequest request, Map body); + @Nullable Object handle(HttpServletRequest request, @Nullable Map body); } @@ -309,7 +320,8 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin } @Override - public Object handle(HttpServletRequest request, @RequestBody(required = false) Map body) { + public @Nullable Object handle(HttpServletRequest request, + @RequestBody(required = false) @Nullable Map body) { HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders(); Map arguments = getArguments(request, body); try { @@ -341,7 +353,7 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin return "Actuator web endpoint '" + this.operation.getId() + "'"; } - private Map getArguments(HttpServletRequest request, Map body) { + private Map getArguments(HttpServletRequest request, @Nullable Map body) { Map arguments = new LinkedHashMap<>(getTemplateVariables(request)); String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate() .getMatchAllRemainingPathSegmentsVariable(); @@ -386,7 +398,7 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin return (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); } - private Object handleResult(Object result, HttpMethod httpMethod) { + private @Nullable Object handleResult(@Nullable Object result, HttpMethod httpMethod) { if (result == null) { return new ResponseEntity<>( (httpMethod != HttpMethod.GET) ? HttpStatus.NO_CONTENT : HttpStatus.NOT_FOUND); @@ -396,12 +408,17 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin } MediaType contentType = (response.getContentType() != null) ? new MediaType(response.getContentType()) : null; - return ResponseEntity.status(response.getStatus()) - .contentType(contentType) - .body(convertIfNecessary(response.getBody())); + BodyBuilder builder = ResponseEntity.status(response.getStatus()); + if (contentType != null) { + builder = builder.contentType(contentType); + } + return builder.body(convertIfNecessary(response.getBody())); } - private Object convertIfNecessary(Object body) { + private @Nullable Object convertIfNecessary(@Nullable Object body) { + if (body == null) { + return null; + } for (Function converter : BODY_CONVERTERS) { body = converter.apply(body); } @@ -435,7 +452,7 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin @ResponseBody @Reflective - Object handle(HttpServletRequest request, @RequestBody(required = false) Map body) { + @Nullable Object handle(HttpServletRequest request, @RequestBody(required = false) Map body) { return this.operation.handle(request, body); } @@ -504,7 +521,7 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin 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, OperationHandler.class); } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AdditionalHealthEndpointPathsWebMvcHandlerMapping.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AdditionalHealthEndpointPathsWebMvcHandlerMapping.java index 114217fe125..cada9c97bd4 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AdditionalHealthEndpointPathsWebMvcHandlerMapping.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AdditionalHealthEndpointPathsWebMvcHandlerMapping.java @@ -20,7 +20,10 @@ import java.util.Collection; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + 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; @@ -37,18 +40,18 @@ import org.springframework.web.servlet.HandlerMapping; */ public class AdditionalHealthEndpointPathsWebMvcHandlerMapping extends AbstractWebMvcEndpointHandlerMapping { - private final ExposableWebEndpoint healthEndpoint; + private final @Nullable ExposableWebEndpoint healthEndpoint; private final Set groups; - public AdditionalHealthEndpointPathsWebMvcHandlerMapping(ExposableWebEndpoint healthEndpoint, + public AdditionalHealthEndpointPathsWebMvcHandlerMapping(@Nullable ExposableWebEndpoint healthEndpoint, Set groups) { - super(new EndpointMapping(""), asList(healthEndpoint), null, false); + super(new EndpointMapping(""), asList(healthEndpoint), new EndpointMediaTypes(), false); this.healthEndpoint = healthEndpoint; this.groups = groups; } - private static Collection asList(ExposableWebEndpoint healthEndpoint) { + private static Collection asList(@Nullable ExposableWebEndpoint healthEndpoint) { return (healthEndpoint != null) ? Collections.singletonList(healthEndpoint) : Collections.emptyList(); } @@ -73,7 +76,7 @@ public class AdditionalHealthEndpointPathsWebMvcHandlerMapping extends AbstractW @Override protected LinksHandler getLinksHandler() { - return null; + return (request, response) -> null; } } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/ControllerEndpointHandlerMapping.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/ControllerEndpointHandlerMapping.java index f39aaa8f100..a0dc512f835 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/ControllerEndpointHandlerMapping.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/ControllerEndpointHandlerMapping.java @@ -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; @@ -34,9 +36,11 @@ import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.util.pattern.PathPattern; +import org.springframework.web.util.pattern.PathPatternParser; /** * {@link HandlerMapping} that exposes @@ -58,7 +62,7 @@ public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMappi private final EndpointMapping endpointMapping; - private final CorsConfiguration corsConfiguration; + private final @Nullable CorsConfiguration corsConfiguration; private final Map handlers; @@ -72,7 +76,7 @@ public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMappi * @param corsConfiguration the CORS configuration for the endpoints or {@code null} */ public ControllerEndpointHandlerMapping(EndpointMapping endpointMapping, - Collection endpoints, CorsConfiguration corsConfiguration) { + Collection endpoints, @Nullable CorsConfiguration corsConfiguration) { this(endpointMapping, endpoints, corsConfiguration, (endpointId, defaultAccess) -> Access.NONE); } @@ -85,7 +89,7 @@ public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMappi * @param endpointAccessResolver resolver for endpoint access */ public ControllerEndpointHandlerMapping(EndpointMapping endpointMapping, - Collection endpoints, CorsConfiguration corsConfiguration, + Collection endpoints, @Nullable CorsConfiguration corsConfiguration, EndpointAccessResolver endpointAccessResolver) { Assert.notNull(endpointMapping, "'endpointMapping' must not be null"); Assert.notNull(endpoints, "'endpoints' must not be null"); @@ -110,6 +114,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; @@ -138,9 +143,13 @@ public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMappi private RequestMappingInfo withEndpointMappedPatterns(ExposableControllerEndpoint endpoint, RequestMappingInfo mapping) { - Set patterns = mapping.getPathPatternsCondition().getPatterns(); + PathPatternsRequestCondition condition = mapping.getPathPatternsCondition(); + Assert.state(condition != null, "'condition' must not be null"); + Set patterns = condition.getPatterns(); if (patterns.isEmpty()) { - patterns = Collections.singleton(getPatternParser().parse("")); + PathPatternParser parser = getPatternParser(); + Assert.state(parser != null, "'parser' must not be null"); + patterns = Collections.singleton(parser.parse("")); } String[] endpointMappedPatterns = patterns.stream() .map((pattern) -> getEndpointMappedPattern(endpoint, pattern)) @@ -158,7 +167,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; } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/WebMvcEndpointHandlerMapping.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/WebMvcEndpointHandlerMapping.java index 10b48b506c3..af5d14798a4 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/WebMvcEndpointHandlerMapping.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/WebMvcEndpointHandlerMapping.java @@ -22,6 +22,7 @@ import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.aot.hint.BindingReflectionHintsRegistrar; import org.springframework.aot.hint.RuntimeHints; @@ -64,7 +65,7 @@ public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerM * @param shouldRegisterLinksMapping whether the links endpoint should be registered */ public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping, Collection endpoints, - EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration, + EndpointMediaTypes endpointMediaTypes, @Nullable CorsConfiguration corsConfiguration, EndpointLinksResolver linksResolver, boolean shouldRegisterLinksMapping) { super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, shouldRegisterLinksMapping); this.linksResolver = linksResolver; @@ -104,7 +105,7 @@ public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerM 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, WebMvcLinksHandler.class); this.bindingRegistrar.registerReflectionHints(hints.reflection(), Link.class); } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/package-info.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/package-info.java index 8c780cafcb4..f132772690b 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/package-info.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/package-info.java @@ -17,4 +17,7 @@ /** * Spring MVC support for actuator endpoints. */ +@NullMarked package org.springframework.boot.webmvc.actuate.endpoint.web; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletHandlerMappings.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletHandlerMappings.java index b9c343a2507..75b4cec5280 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletHandlerMappings.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletHandlerMappings.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Optional; import java.util.stream.Stream; +import io.undertow.servlet.api.DeploymentManager; import jakarta.servlet.ServletException; import org.apache.catalina.Container; import org.apache.catalina.Context; @@ -133,7 +134,10 @@ final class DispatcherServletHandlerMappings { void initializeServlet(String name) { try { - this.webServer.getDeploymentManager().getDeployment().getServlets().getManagedServlet(name).forceInit(); + DeploymentManager deploymentManager = this.webServer.getDeploymentManager(); + if (deploymentManager != null) { + deploymentManager.getDeployment().getServlets().getManagedServlet(name).forceInit(); + } } catch (ServletException ex) { // Continue diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletMappingDescription.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletMappingDescription.java index 8ad05a65e62..f4448adbf95 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletMappingDescription.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletMappingDescription.java @@ -16,6 +16,8 @@ package org.springframework.boot.webmvc.actuate.mappings; +import org.jspecify.annotations.Nullable; + import org.springframework.web.servlet.DispatcherServlet; /** @@ -30,9 +32,10 @@ public class DispatcherServletMappingDescription { private final String predicate; - private final DispatcherServletMappingDetails details; + private final @Nullable DispatcherServletMappingDetails details; - DispatcherServletMappingDescription(String predicate, String handler, DispatcherServletMappingDetails details) { + DispatcherServletMappingDescription(String predicate, String handler, + @Nullable DispatcherServletMappingDetails details) { this.handler = handler; this.predicate = predicate; this.details = details; @@ -46,7 +49,7 @@ public class DispatcherServletMappingDescription { return this.predicate; } - public DispatcherServletMappingDetails getDetails() { + public @Nullable DispatcherServletMappingDetails getDetails() { return this.details; } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletMappingDetails.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletMappingDetails.java index 930ae0eb226..282ca2030d7 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletMappingDetails.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletMappingDetails.java @@ -16,6 +16,8 @@ package org.springframework.boot.webmvc.actuate.mappings; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.actuate.web.mappings.HandlerMethodDescription; import org.springframework.web.servlet.DispatcherServlet; @@ -28,33 +30,33 @@ import org.springframework.web.servlet.DispatcherServlet; */ public class DispatcherServletMappingDetails { - 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; } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletsMappingDescriptionProvider.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletsMappingDescriptionProvider.java index fd839c018b8..aaacf57b520 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletsMappingDescriptionProvider.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/DispatcherServletsMappingDescriptionProvider.java @@ -28,6 +28,7 @@ import java.util.function.Function; import java.util.stream.Stream; import jakarta.servlet.Servlet; +import org.jspecify.annotations.Nullable; import org.springframework.aot.hint.BindingReflectionHintsRegistrar; import org.springframework.aot.hint.RuntimeHints; @@ -272,7 +273,7 @@ public class DispatcherServletsMappingDescriptionProvider 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(), DispatcherServletMappingDescription.class); } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/RequestMappingConditionsDescription.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/RequestMappingConditionsDescription.java index 38384973eda..2135b6ff2fe 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/RequestMappingConditionsDescription.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/RequestMappingConditionsDescription.java @@ -19,9 +19,13 @@ package org.springframework.boot.webmvc.actuate.mappings; import java.util.List; import java.util.Set; +import org.jspecify.annotations.Nullable; + +import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.condition.MediaTypeExpression; import org.springframework.web.servlet.mvc.condition.NameValueExpression; +import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; /** @@ -73,8 +77,12 @@ public class RequestMappingConditionsDescription { private Set extractPathPatterns(RequestMappingInfo requestMapping) { org.springframework.web.servlet.mvc.condition.PatternsRequestCondition patternsCondition = requestMapping .getPatternsCondition(); - return (patternsCondition != null) ? patternsCondition.getPatterns() - : requestMapping.getPathPatternsCondition().getPatternValues(); + if (patternsCondition != null) { + return patternsCondition.getPatterns(); + } + PathPatternsRequestCondition condition = requestMapping.getPathPatternsCondition(); + Assert.state(condition != null, "'condition' must not be null"); + return condition.getPatternValues(); } public List getConsumes() { @@ -132,7 +140,7 @@ public class RequestMappingConditionsDescription { private final String name; - private final Object value; + private final @Nullable Object value; private final boolean negated; @@ -146,7 +154,7 @@ public class RequestMappingConditionsDescription { return this.name; } - public Object getValue() { + public @Nullable Object getValue() { return this.value; } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/package-info.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/package-info.java index bfd9d130d2d..6b1c95c7720 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/package-info.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/mappings/package-info.java @@ -17,4 +17,7 @@ /** * Actuator request mappings support for Spring MVC. */ +@NullMarked package org.springframework.boot.webmvc.actuate.mappings; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration.java index 7470ab169b7..172869c3165 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration.java @@ -47,6 +47,7 @@ import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.Assert; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.servlet.DispatcherServlet; @@ -132,6 +133,7 @@ public final class DispatcherServletAutoConfiguration { public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConditionMessage.Builder message = ConditionMessage.forCondition("Default DispatcherServlet"); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + Assert.state(beanFactory != null, "'beanFactory' must not be null"); List dispatchServletBeans = Arrays .asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false)); if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) { @@ -158,6 +160,7 @@ public final class DispatcherServletAutoConfiguration { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + Assert.state(beanFactory != null, "'beanFactory' must not be null"); ConditionOutcome outcome = checkDefaultDispatcherName(beanFactory); if (!outcome.isMatch()) { return outcome; diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java index 484557675b8..87f328d8044 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.boot.webmvc.autoconfigure; import java.time.Duration; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.Consumer; @@ -25,6 +26,7 @@ import jakarta.servlet.Servlet; import jakarta.servlet.ServletContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; @@ -74,9 +76,12 @@ import org.springframework.core.io.ResourceLoader; 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.MediaType; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.lang.Contract; import org.springframework.util.AntPathMatcher; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.validation.DefaultMessageCodesResolver; @@ -201,9 +206,9 @@ public final class WebMvcAutoConfiguration { private final ObjectProvider> servletRegistrations; - private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; + private final @Nullable ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; - private ServletContext servletContext; + private @Nullable ServletContext servletContext; private final ObjectProvider apiVersionResolvers; @@ -328,7 +333,7 @@ public final class WebMvcAutoConfiguration { } @Override - public MessageCodesResolver getMessageCodesResolver() { + public @Nullable MessageCodesResolver getMessageCodesResolver() { if (this.mvcProperties.getMessageCodesResolverFormat() != null) { DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver(); resolver.setMessageCodeFormatter(this.mvcProperties.getMessageCodesResolverFormat()); @@ -370,13 +375,20 @@ public final class WebMvcAutoConfiguration { } ResourceHandlerRegistration registration = registry.addResourceHandler(pattern); customizer.accept(registration); - registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod())); - registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl()); + Integer cachePeriod = getSeconds(this.resourceProperties.getCache().getPeriod()); + if (cachePeriod != null) { + registration.setCachePeriod(cachePeriod); + } + CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); + if (cacheControl != null) { + registration.setCacheControl(cacheControl); + } registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified()); customizeResourceHandlerRegistration(registration); } - private Integer getSeconds(Duration cachePeriod) { + @Contract("!null -> !null") + private @Nullable Integer getSeconds(@Nullable Duration cachePeriod) { return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null; } @@ -433,8 +445,9 @@ public final class WebMvcAutoConfiguration { private final ListableBeanFactory beanFactory; - private final WebMvcRegistrations mvcRegistrations; + private final @Nullable WebMvcRegistrations mvcRegistrations; + @SuppressWarnings("NullAway.Init") private ResourceLoader resourceLoader; EnableWebMvcConfiguration(WebMvcProperties mvcProperties, WebProperties webProperties, @@ -491,11 +504,13 @@ public final class WebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME) public LocaleResolver localeResolver() { + Locale locale = this.webProperties.getLocale(); if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) { - return new FixedLocaleResolver(this.webProperties.getLocale()); + Assert.state(locale != null, "'locale' must not be null"); + return new FixedLocaleResolver(locale); } AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); - localeResolver.setDefaultLocale(this.webProperties.getLocale()); + localeResolver.setDefaultLocale(locale); return localeResolver; } @@ -513,7 +528,7 @@ public final class WebMvcAutoConfiguration { return super.viewNameTranslator(); } - private Resource getIndexHtmlResource() { + private @Nullable Resource getIndexHtmlResource() { for (String location : this.resourceProperties.getStaticLocations()) { Resource indexHtml = getIndexHtmlResource(location); if (indexHtml != null) { @@ -527,11 +542,11 @@ public final class WebMvcAutoConfiguration { return null; } - private Resource getIndexHtmlResource(String location) { + private @Nullable Resource getIndexHtmlResource(String location) { return getIndexHtmlResource(this.resourceLoader.getResource(location)); } - private Resource getIndexHtmlResource(Resource location) { + private @Nullable Resource getIndexHtmlResource(Resource location) { try { Resource resource = location.createRelative("index.html"); if (resource.exists() && (resource.getURL() != null)) { @@ -564,7 +579,9 @@ public final class WebMvcAutoConfiguration { getClass().getClassLoader())) { return super.mvcValidator(); } - return ValidatorAdapter.get(getApplicationContext(), getValidator()); + ApplicationContext applicationContext = getApplicationContext(); + Assert.state(applicationContext != null, "'applicationContext' must not be null"); + return ValidatorAdapter.get(applicationContext, getValidator()); } @Override @@ -620,7 +637,7 @@ public final class WebMvcAutoConfiguration { @Override @ConditionalOnMissingBean(name = "mvcApiVersionStrategy") - public ApiVersionStrategy mvcApiVersionStrategy() { + public @Nullable ApiVersionStrategy mvcApiVersionStrategy() { return super.mvcApiVersionStrategy(); } @@ -642,7 +659,7 @@ public final class WebMvcAutoConfiguration { interface WelcomePageHandlerMappingFactory { T create(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, - Resource indexHtmlResource, String staticPathPattern); + @Nullable Resource indexHtmlResource, String staticPathPattern); } @@ -682,6 +699,7 @@ public final class WebMvcAutoConfiguration { 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()) { diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcProperties.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcProperties.java index 49d8e2dcfe4..f4bac736926 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcProperties.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcProperties.java @@ -22,6 +22,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; @@ -45,7 +47,7 @@ public class WebMvcProperties { /** * Formatting strategy for message codes. For instance, 'PREFIX_ERROR_CODE'. */ - private DefaultMessageCodesResolver.Format messageCodesResolverFormat; + private DefaultMessageCodesResolver.@Nullable Format messageCodesResolverFormat; private final Format format = new Format(); @@ -100,11 +102,11 @@ public class WebMvcProperties { private final Apiversion apiversion = new Apiversion(); - public DefaultMessageCodesResolver.Format getMessageCodesResolverFormat() { + public DefaultMessageCodesResolver.@Nullable Format getMessageCodesResolverFormat() { return this.messageCodesResolverFormat; } - public void setMessageCodesResolverFormat(DefaultMessageCodesResolver.Format messageCodesResolverFormat) { + public void setMessageCodesResolverFormat(DefaultMessageCodesResolver.@Nullable Format messageCodesResolverFormat) { this.messageCodesResolverFormat = messageCodesResolverFormat; } @@ -202,13 +204,13 @@ public class WebMvcProperties { * Amount of time before asynchronous request handling times out. If this value is * not set, the default timeout of the underlying implementation is used. */ - private Duration requestTimeout; + private @Nullable Duration requestTimeout; - public Duration getRequestTimeout() { + public @Nullable Duration getRequestTimeout() { return this.requestTimeout; } - public void setRequestTimeout(Duration requestTimeout) { + public void setRequestTimeout(@Nullable Duration requestTimeout) { this.requestTimeout = requestTimeout; } @@ -282,26 +284,26 @@ public class WebMvcProperties { /** * Spring MVC view prefix. */ - private String prefix; + private @Nullable String prefix; /** * Spring MVC view suffix. */ - private String suffix; + private @Nullable String suffix; - public String getPrefix() { + public @Nullable String getPrefix() { return this.prefix; } - public void setPrefix(String prefix) { + public void setPrefix(@Nullable String prefix) { this.prefix = prefix; } - public String getSuffix() { + public @Nullable String getSuffix() { return this.suffix; } - public void setSuffix(String suffix) { + public void setSuffix(@Nullable String suffix) { this.suffix = suffix; } @@ -318,7 +320,7 @@ public class WebMvcProperties { /** * Query parameter name to use when "favor-parameter" is enabled. */ - private String parameterName; + private @Nullable String parameterName; /** * Map file extensions to media types for content negotiation. For instance, yml @@ -340,11 +342,11 @@ public class WebMvcProperties { this.favorParameter = favorParameter; } - public String getParameterName() { + public @Nullable String getParameterName() { return this.parameterName; } - public void setParameterName(String parameterName) { + public void setParameterName(@Nullable String parameterName) { this.parameterName = parameterName; } @@ -389,41 +391,41 @@ public class WebMvcProperties { * 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; } @@ -477,58 +479,58 @@ public class WebMvcProperties { /** * 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 supported; + private @Nullable List 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 getSupported() { + public @Nullable List getSupported() { return this.supported; } - public void setSupported(List supported) { + public void setSupported(@Nullable List 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; } @@ -541,44 +543,44 @@ public class WebMvcProperties { /** * 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 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; } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcRegistrations.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcRegistrations.java index 4bde71dae0c..919f92597b3 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcRegistrations.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcRegistrations.java @@ -16,6 +16,8 @@ package org.springframework.boot.webmvc.autoconfigure; +import org.jspecify.annotations.Nullable; + import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; @@ -44,7 +46,7 @@ public interface WebMvcRegistrations { * processed by the MVC configuration. * @return the custom {@link RequestMappingHandlerMapping} instance */ - default RequestMappingHandlerMapping getRequestMappingHandlerMapping() { + default @Nullable RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return null; } @@ -53,7 +55,7 @@ public interface WebMvcRegistrations { * processed by the MVC configuration. * @return the custom {@link RequestMappingHandlerAdapter} instance */ - default RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() { + default @Nullable RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() { return null; } @@ -62,7 +64,7 @@ public interface WebMvcRegistrations { * processed by the MVC configuration. * @return the custom {@link ExceptionHandlerExceptionResolver} instance */ - default ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver() { + default @Nullable ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver() { return null; } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WelcomePage.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WelcomePage.java index b648bc29a32..1187e642d05 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WelcomePage.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WelcomePage.java @@ -16,6 +16,8 @@ package org.springframework.boot.webmvc.autoconfigure; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; @@ -32,11 +34,11 @@ final class WelcomePage { */ static final WelcomePage UNRESOLVED = new WelcomePage(null, false); - private final String viewName; + private final @Nullable String viewName; private final boolean templated; - private WelcomePage(String viewName, boolean templated) { + private WelcomePage(@Nullable String viewName, boolean templated) { this.viewName = viewName; this.templated = templated; } @@ -45,7 +47,7 @@ final class WelcomePage { * Return the view name of the welcome page. * @return the view name */ - String getViewName() { + @Nullable String getViewName() { return this.viewName; } @@ -66,7 +68,7 @@ final class WelcomePage { * @return a resolved {@link WelcomePage} instance or {@link #UNRESOLVED} */ static WelcomePage resolve(TemplateAvailabilityProviders templateAvailabilityProviders, - ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern) { + ApplicationContext applicationContext, @Nullable Resource indexHtmlResource, String staticPathPattern) { if (indexHtmlResource != null && "/**".equals(staticPathPattern)) { return new WelcomePage("forward:index.html", false); } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WelcomePageHandlerMapping.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WelcomePageHandlerMapping.java index 8be5fac7f34..44b685c4383 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WelcomePageHandlerMapping.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WelcomePageHandlerMapping.java @@ -22,6 +22,7 @@ import java.util.List; import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; import org.springframework.context.ApplicationContext; @@ -51,7 +52,7 @@ final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping { private static final List MEDIA_TYPES_ALL = Collections.singletonList(MediaType.ALL); WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, - ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern) { + ApplicationContext applicationContext, @Nullable Resource indexHtmlResource, String staticPathPattern) { setOrder(2); WelcomePage welcomePage = WelcomePage.resolve(templateAvailabilityProviders, applicationContext, indexHtmlResource, staticPathPattern); @@ -65,7 +66,7 @@ final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping { } @Override - public Object getHandlerInternal(HttpServletRequest request) throws Exception { + public @Nullable Object getHandlerInternal(HttpServletRequest request) throws Exception { return (!isHtmlTextAccepted(request)) ? null : super.getHandlerInternal(request); } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WelcomePageNotAcceptableHandlerMapping.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WelcomePageNotAcceptableHandlerMapping.java index 5189da21c1f..5ea4e441c80 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WelcomePageNotAcceptableHandlerMapping.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WelcomePageNotAcceptableHandlerMapping.java @@ -18,6 +18,7 @@ package org.springframework.boot.webmvc.autoconfigure; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; import org.springframework.context.ApplicationContext; @@ -36,7 +37,7 @@ import org.springframework.web.servlet.mvc.Controller; class WelcomePageNotAcceptableHandlerMapping extends AbstractUrlHandlerMapping { WelcomePageNotAcceptableHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, - ApplicationContext applicationContext, Resource indexHtmlResource, String staticPathPattern) { + ApplicationContext applicationContext, @Nullable Resource indexHtmlResource, String staticPathPattern) { setOrder(LOWEST_PRECEDENCE - 10); // Before ResourceHandlerRegistry WelcomePage welcomePage = WelcomePage.resolve(templateAvailabilityProviders, applicationContext, indexHtmlResource, staticPathPattern); @@ -45,13 +46,13 @@ class WelcomePageNotAcceptableHandlerMapping extends AbstractUrlHandlerMapping { } } - private ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { + private @Nullable ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { response.setStatus(HttpStatus.NOT_ACCEPTABLE.value()); return null; } @Override - protected Object getHandlerInternal(HttpServletRequest request) throws Exception { + protected @Nullable Object getHandlerInternal(HttpServletRequest request) throws Exception { return super.getHandlerInternal(request); } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/endpoint/web/WebMvcHealthEndpointExtensionAutoConfiguration.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/endpoint/web/WebMvcHealthEndpointExtensionAutoConfiguration.java index abcccc118a3..8ce098a96fd 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/endpoint/web/WebMvcHealthEndpointExtensionAutoConfiguration.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/endpoint/web/WebMvcHealthEndpointExtensionAutoConfiguration.java @@ -18,6 +18,8 @@ package org.springframework.boot.webmvc.autoconfigure.actuate.endpoint.web; import java.util.Collection; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; @@ -56,7 +58,7 @@ public final class WebMvcHealthEndpointExtensionAutoConfiguration { groups.getAllWithAdditionalPath(WebServerNamespace.SERVER)); } - private static ExposableWebEndpoint getHealthEndpoint(WebEndpointsSupplier webEndpointsSupplier) { + private static @Nullable ExposableWebEndpoint getHealthEndpoint(WebEndpointsSupplier webEndpointsSupplier) { Collection webEndpoints = webEndpointsSupplier.getEndpoints(); return webEndpoints.stream() .filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID)) diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/endpoint/web/package-info.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/endpoint/web/package-info.java index 8ab6c3a1979..19698d0e411 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/endpoint/web/package-info.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/endpoint/web/package-info.java @@ -17,4 +17,7 @@ /** * Auto-configuration for Spring MVC actuator web endpoint support. */ +@NullMarked package org.springframework.boot.webmvc.autoconfigure.actuate.endpoint.web; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/CompositeHandlerAdapter.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/CompositeHandlerAdapter.java index 9cf64682582..6941647f54f 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/CompositeHandlerAdapter.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/CompositeHandlerAdapter.java @@ -22,6 +22,7 @@ import java.util.Optional; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.core.annotation.AnnotationAwareOrderComparator; @@ -39,7 +40,7 @@ class CompositeHandlerAdapter implements HandlerAdapter { private final ListableBeanFactory beanFactory; - private List adapters; + private @Nullable List adapters; CompositeHandlerAdapter(ListableBeanFactory beanFactory) { this.beanFactory = beanFactory; @@ -51,7 +52,7 @@ class CompositeHandlerAdapter implements HandlerAdapter { } @Override - public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) + public @Nullable ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Optional adapter = getAdapter(handler); if (adapter.isPresent()) { diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/CompositeHandlerExceptionResolver.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/CompositeHandlerExceptionResolver.java index 0901445d028..ac8a9e10b08 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/CompositeHandlerExceptionResolver.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/CompositeHandlerExceptionResolver.java @@ -21,6 +21,7 @@ import java.util.List; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.HierarchicalBeanFactory; @@ -46,11 +47,11 @@ class CompositeHandlerExceptionResolver implements HandlerExceptionResolver { @Autowired private ListableBeanFactory beanFactory; - private volatile List resolvers; + private volatile @Nullable List resolvers; @Override - public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, - Exception ex) { + public @Nullable ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, + @Nullable Object handler, Exception ex) { for (HandlerExceptionResolver resolver : getResolvers()) { ModelAndView resolved = resolver.resolveException(request, response, handler, ex); if (resolved != null) { @@ -76,7 +77,7 @@ class CompositeHandlerExceptionResolver implements HandlerExceptionResolver { return resolvers; } - private void collectResolverBeans(List resolvers, BeanFactory beanFactory) { + private void collectResolverBeans(List resolvers, @Nullable BeanFactory beanFactory) { if (beanFactory instanceof ListableBeanFactory listableBeanFactory) { resolvers.addAll(listableBeanFactory.getBeansOfType(HandlerExceptionResolver.class).values()); } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/CompositeHandlerMapping.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/CompositeHandlerMapping.java index 84dd53c9f3e..38967622f5b 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/CompositeHandlerMapping.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/CompositeHandlerMapping.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -39,10 +40,10 @@ class CompositeHandlerMapping implements HandlerMapping { @Autowired private ListableBeanFactory beanFactory; - private List mappings; + private @Nullable List mappings; @Override - public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { + public @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { for (HandlerMapping mapping : getMappings()) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/package-info.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/package-info.java index c361242cee3..f40229efc7f 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/package-info.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/package-info.java @@ -17,4 +17,7 @@ /** * Auto-configuration for Spring MVC-based actuator infrastructure. */ +@NullMarked package org.springframework.boot.webmvc.autoconfigure.actuate.web; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/AbstractErrorController.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/AbstractErrorController.java index 74a2da8a9e6..78c37c0c267 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/AbstractErrorController.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/AbstractErrorController.java @@ -23,6 +23,7 @@ import java.util.Map; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.boot.web.error.ErrorAttributeOptions; import org.springframework.boot.webmvc.error.ErrorAttributes; @@ -55,13 +56,14 @@ public abstract class AbstractErrorController implements ErrorController { this(errorAttributes, null); } - public AbstractErrorController(ErrorAttributes errorAttributes, List errorViewResolvers) { + public AbstractErrorController(ErrorAttributes errorAttributes, + @Nullable List errorViewResolvers) { Assert.notNull(errorAttributes, "'errorAttributes' must not be null"); this.errorAttributes = errorAttributes; this.errorViewResolvers = sortErrorViewResolvers(errorViewResolvers); } - private List sortErrorViewResolvers(List resolvers) { + private List sortErrorViewResolvers(@Nullable List resolvers) { List sorted = new ArrayList<>(); if (resolvers != null) { sorted.addAll(resolvers); @@ -142,8 +144,8 @@ public abstract class AbstractErrorController implements ErrorController { * @return a specific {@link ModelAndView} or {@code null} if the default should be * used */ - protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, - Map model) { + protected @Nullable ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, + HttpStatus status, Map model) { for (ErrorViewResolver resolver : this.errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/DefaultErrorViewResolver.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/DefaultErrorViewResolver.java index c59c69529fc..1cb068e08be 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/DefaultErrorViewResolver.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/DefaultErrorViewResolver.java @@ -22,6 +22,7 @@ import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider; import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; @@ -97,7 +98,8 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { } @Override - public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map model) { + public @Nullable ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, + Map model) { ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); @@ -105,7 +107,7 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { return modelAndView; } - private ModelAndView resolve(String viewName, Map model) { + private @Nullable ModelAndView resolve(String viewName, Map model) { String errorViewName = "error/" + viewName; TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); @@ -115,7 +117,7 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { return resolveResource(errorViewName, model); } - private ModelAndView resolveResource(String viewName, Map model) { + private @Nullable ModelAndView resolveResource(String viewName, Map model) { for (String location : this.resources.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); @@ -157,7 +159,7 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { } @Override - public void render(Map model, HttpServletRequest request, HttpServletResponse response) + public void render(@Nullable Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { response.setContentType(getContentType()); FileCopyUtils.copy(this.resource.getInputStream(), response.getOutputStream()); diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration.java index 888d179bd60..320137badf2 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration.java @@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.aop.framework.autoproxy.AutoProxyUtils; import org.springframework.beans.BeansException; @@ -65,6 +66,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.http.MediaType; +import org.springframework.lang.Contract; +import org.springframework.util.Assert; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.View; import org.springframework.web.servlet.view.BeanNameViewResolver; @@ -173,9 +176,11 @@ public final class ErrorMvcAutoConfiguration { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { ConditionMessage.Builder message = ConditionMessage.forCondition("ErrorTemplate Missing"); - TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders(context.getClassLoader()); + ClassLoader classLoader = context.getClassLoader(); + Assert.state(classLoader != null, "'classLoader' must not be null"); + TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders(classLoader); TemplateAvailabilityProvider provider = providers.getProvider("error", context.getEnvironment(), - context.getClassLoader(), context.getResourceLoader()); + classLoader, context.getResourceLoader()); if (provider != null) { return ConditionOutcome.noMatch(message.foundExactly("template from " + provider)); } @@ -194,8 +199,9 @@ public final class ErrorMvcAutoConfiguration { private static final Log logger = LogFactory.getLog(StaticView.class); @Override - public void render(Map model, HttpServletRequest request, HttpServletResponse response) + public void render(@Nullable Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { + Assert.state(model != null, "'model' must not be null"); if (response.isCommitted()) { String message = getMessage(model); logger.error(message); @@ -229,7 +235,8 @@ public final class ErrorMvcAutoConfiguration { response.getWriter().append(builder.toString()); } - private String htmlEscape(Object input) { + @Contract("!null -> !null") + private @Nullable String htmlEscape(@Nullable Object input) { return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null; } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/ErrorViewResolver.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/ErrorViewResolver.java index 5c5e0fd5a11..10d38a8e208 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/ErrorViewResolver.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/ErrorViewResolver.java @@ -19,6 +19,7 @@ package org.springframework.boot.webmvc.autoconfigure.error; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpStatus; import org.springframework.web.servlet.ModelAndView; @@ -39,6 +40,6 @@ public interface ErrorViewResolver { * @param model the suggested model to be used with the view * @return a resolved {@link ModelAndView} or {@code null} */ - ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map model); + @Nullable ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map model); } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/package-info.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/package-info.java index d89587dc743..2b2db85bc0d 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/package-info.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/error/package-info.java @@ -17,4 +17,7 @@ /** * Auto-configuration for Spring MVC error handling. */ +@NullMarked package org.springframework.boot.webmvc.autoconfigure.error; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/package-info.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/package-info.java index dbd8caf8c84..8332fd02a51 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/package-info.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/package-info.java @@ -17,4 +17,7 @@ /** * Auto-configuration for Spring MVC. */ +@NullMarked package org.springframework.boot.webmvc.autoconfigure; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/error/DefaultErrorAttributes.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/error/DefaultErrorAttributes.java index eebd9f18675..9804603d9b2 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/error/DefaultErrorAttributes.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/error/DefaultErrorAttributes.java @@ -26,6 +26,7 @@ import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.boot.web.error.Error; import org.springframework.boot.web.error.ErrorAttributeOptions; @@ -81,8 +82,8 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException } @Override - public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, - Exception ex) { + public @Nullable ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, + @Nullable Object handler, Exception ex) { storeErrorAttributes(request, ex); return null; } @@ -139,7 +140,8 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException addErrorMessage(errorAttributes, webRequest, error); } - private void addErrorMessage(Map errorAttributes, WebRequest webRequest, Throwable error) { + private void addErrorMessage(Map errorAttributes, WebRequest webRequest, + @Nullable Throwable error) { BindingResult bindingResult = extractBindingResult(error); if (bindingResult != null) { addMessageAndErrorsFromBindingResult(errorAttributes, bindingResult); @@ -166,7 +168,8 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException errorAttributes.put("errors", Error.wrapIfNecessary(result.getAllErrors())); } - private void addExceptionErrorMessage(Map errorAttributes, WebRequest webRequest, Throwable error) { + private void addExceptionErrorMessage(Map errorAttributes, WebRequest webRequest, + @Nullable Throwable error) { errorAttributes.put("message", getMessage(webRequest, error)); } @@ -183,7 +186,7 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException * @param error current error, if any * @return message to include in the error attributes */ - protected String getMessage(WebRequest webRequest, Throwable error) { + protected String getMessage(WebRequest webRequest, @Nullable Throwable error) { Object message = getAttribute(webRequest, RequestDispatcher.ERROR_MESSAGE); if (!ObjectUtils.isEmpty(message)) { return message.toString(); @@ -194,14 +197,14 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException return "No message available"; } - private BindingResult extractBindingResult(Throwable error) { + private @Nullable BindingResult extractBindingResult(@Nullable Throwable error) { if (error instanceof BindingResult bindingResult) { return bindingResult; } return null; } - private MethodValidationResult extractMethodValidationResult(Throwable error) { + private @Nullable MethodValidationResult extractMethodValidationResult(@Nullable Throwable error) { if (error instanceof MethodValidationResult methodValidationResult) { return methodValidationResult; } @@ -223,7 +226,7 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException } @Override - public Throwable getError(WebRequest webRequest) { + public @Nullable Throwable getError(WebRequest webRequest) { Throwable exception = getAttribute(webRequest, ERROR_INTERNAL_ATTRIBUTE); if (exception == null) { exception = getAttribute(webRequest, RequestDispatcher.ERROR_EXCEPTION); @@ -232,7 +235,7 @@ public class DefaultErrorAttributes implements ErrorAttributes, HandlerException } @SuppressWarnings("unchecked") - private T getAttribute(RequestAttributes requestAttributes, String name) { + private @Nullable T getAttribute(RequestAttributes requestAttributes, String name) { return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST); } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/error/ErrorAttributes.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/error/ErrorAttributes.java index a4ac3455abc..9a254af0da5 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/error/ErrorAttributes.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/error/ErrorAttributes.java @@ -19,6 +19,8 @@ package org.springframework.boot.webmvc.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.bind.annotation.ResponseBody; import org.springframework.web.context.request.WebRequest; @@ -52,6 +54,6 @@ public interface ErrorAttributes { * @param webRequest the source request * @return the {@link Exception} that caused the error or {@code null} */ - Throwable getError(WebRequest webRequest); + @Nullable Throwable getError(WebRequest webRequest); } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/error/package-info.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/error/package-info.java index 5803d52711d..9bb6907bec8 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/error/package-info.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/error/package-info.java @@ -17,4 +17,7 @@ /** * Spring MVC error handling infrastructure. */ +@NullMarked package org.springframework.boot.webmvc.error; + +import org.jspecify.annotations.NullMarked;