diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java index 22b2762047e2..566935f7892c 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java @@ -115,6 +115,8 @@ public AbstractNamedValueMethodArgumentResolver(@Nullable ConfigurableBeanFactor } Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest); + boolean valueMissing = + (resolveName(resolvedName.toString(), nestedParameter, webRequest) == null); if (arg == null) { if (namedValueInfo.defaultValue != null) { arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue); @@ -130,20 +132,32 @@ else if ("".equals(arg) && namedValueInfo.defaultValue != null) { arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue); } - if (binderFactory != null && (arg != null || !hasDefaultValue)) { + + boolean skipConversion = + valueMissing && + parameter.getParameterType().isPrimitive() && + !namedValueInfo.required && + !hasDefaultValue; + + + + if (binderFactory != null && !skipConversion && (arg != null || !hasDefaultValue)) { arg = convertIfNecessary(parameter, webRequest, binderFactory, namedValueInfo, arg); - // Check for null value after conversion of incoming argument value if (arg == null) { if (namedValueInfo.defaultValue != null) { arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue); - arg = convertIfNecessary(parameter, webRequest, binderFactory, namedValueInfo, arg); } else if (namedValueInfo.required && !nestedParameter.isOptional()) { - handleMissingValueAfterConversion(resolvedName.toString(), nestedParameter, webRequest); + handleMissingValue(resolvedName.toString(), nestedParameter, webRequest); + } + else if (parameter.getParameterType().isPrimitive()) { + arg = handleNullValue(resolvedName.toString(), null, parameter.getParameterType()); } } + } + handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; @@ -290,9 +304,10 @@ else if (paramType.isPrimitive()) { throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(), namedValueInfo.name, parameter, ex.getCause()); } - catch (TypeMismatchException ex) { - throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(), - namedValueInfo.name, parameter, ex.getCause()); + catch (TypeMismatchException | IllegalArgumentException ex) { + throw new MethodArgumentTypeMismatchException(arg, ex instanceof TypeMismatchException tme ? + tme.getRequiredType() : parameterType, + namedValueInfo.name, parameter, ex); } return arg; } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java index 3a0a37444390..b7e2ecad11c4 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapterTests.java @@ -48,18 +48,14 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.SessionAttributes; +import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.AsyncRequestNotUsableException; import org.springframework.web.context.request.async.StandardServletAsyncWebRequest; import org.springframework.web.context.request.async.WebAsyncManager; import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.method.HandlerMethod; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.method.annotation.ModelMethodProcessor; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; @@ -131,6 +127,32 @@ void cacheControlWithoutSessionAttributes() throws Exception { assertThat(response.getHeader("Cache-Control")).contains("max-age"); } + @Test + void invalidBooleanRequestParamResultsInBadRequest() throws Exception { + this.handlerAdapter.afterPropertiesSet(); + + this.request.setRequestURI("/test"); + this.request.setParameter("bool", "dummy"); + + HandlerMethod handlerMethod = + handlerMethod(new BooleanParamController(), "test", boolean.class); + + assertThatThrownBy(() -> { + try { + this.handlerAdapter.handle(this.request, this.response, handlerMethod); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + }) + .hasCauseInstanceOf(MethodArgumentTypeMismatchException.class); + + } + + + + + @Test void cacheControlWithSessionAttributes() throws Exception { SessionAttributeController handler = new SessionAttributeController(); @@ -234,6 +256,23 @@ void modelAttributeAdvice() throws Exception { assertThat(mav.getModel().get("attr2")).isEqualTo("gAttr2"); } + @Test + void primitiveBooleanRequiredFalseShouldNotThrowTypeMismatch() throws Exception { + this.handlerAdapter.afterPropertiesSet(); + + this.request.setRequestURI("/test"); + + HandlerMethod handlerMethod = + handlerMethod(new PrimitiveBooleanController(), "test", boolean.class); + + this.handlerAdapter.handle(this.request, this.response, handlerMethod); + + assertThat(this.response.getContentAsString()).isEqualTo("false"); + } + + + + @Test void prototypeControllerAdvice() throws Exception { this.webAppContext.registerPrototype("maa", ModelAttributeAdvice.class); @@ -410,6 +449,26 @@ public ResponseEntity handle(@RequestParam String q) throws IOException { } + @RestController + static class BooleanParamController { + + @GetMapping("/test") + void test(@RequestParam boolean bool) { + } + } + + @RestController + static class PrimitiveBooleanController { + + @GetMapping("/test") + public String test(@RequestParam(required = false) boolean flag) { + return String.valueOf(flag); + } + } + + + + @ControllerAdvice private static class ModelAttributeAdvice {