diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java
index ea7c3350836..64e717e2f00 100644
--- a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java
+++ b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -147,7 +147,12 @@ public class HandlerMethodValidationException extends ResponseStatusException im
}
RequestBody requestBody = param.getParameterAnnotation(RequestBody.class);
if (requestBody != null) {
- visitor.requestBody(requestBody, asErrors(result));
+ if (result instanceof ParameterErrors errors) {
+ visitor.requestBody(requestBody, errors);
+ }
+ else {
+ visitor.requestBodyValidationResult(requestBody, result);
+ }
continue;
}
RequestHeader requestHeader = param.getParameterAnnotation(RequestHeader.class);
@@ -217,6 +222,20 @@ public class HandlerMethodValidationException extends ResponseStatusException im
*/
void requestBody(RequestBody requestBody, ParameterErrors errors);
+ /**
+ * An additional {@code @RequestBody} callback for validation failures
+ * for constraints on the method parameter. For example:
+ *
+ * @RequestBody List<@NotEmpty String> ids
+ *
+ * Handle results for {@code @RequestBody} method parameters.
+ * @param requestBody the annotation declared on the parameter
+ * @param result the validation result
+ * @since 7.0
+ */
+ default void requestBodyValidationResult(RequestBody requestBody, ParameterValidationResult result) {
+ }
+
/**
* Handle results for {@code @RequestHeader} method parameters.
* @param requestHeader the annotation declared on the parameter
diff --git a/spring-web/src/main/java/org/springframework/web/util/DisconnectedClientHelper.java b/spring-web/src/main/java/org/springframework/web/util/DisconnectedClientHelper.java
index a62f6312bbb..17df6ad11af 100644
--- a/spring-web/src/main/java/org/springframework/web/util/DisconnectedClientHelper.java
+++ b/spring-web/src/main/java/org/springframework/web/util/DisconnectedClientHelper.java
@@ -22,6 +22,7 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.util.Assert;
@@ -100,7 +101,11 @@ public class DisconnectedClientHelper {
* IOException "Broken pipe" or "connection reset by peer"
*
*/
- public static boolean isClientDisconnectedException(Throwable ex) {
+ public static boolean isClientDisconnectedException(@Nullable Throwable ex) {
+ if (ex == null) {
+ return false;
+ }
+
Throwable currentEx = ex;
Throwable lastEx = null;
while (currentEx != null && currentEx != lastEx) {
diff --git a/spring-web/src/test/java/org/springframework/web/method/support/HandlerMethodValidationExceptionTests.java b/spring-web/src/test/java/org/springframework/web/method/support/HandlerMethodValidationExceptionTests.java
index 492f70e4a1a..304731b2487 100644
--- a/spring-web/src/test/java/org/springframework/web/method/support/HandlerMethodValidationExceptionTests.java
+++ b/spring-web/src/test/java/org/springframework/web/method/support/HandlerMethodValidationExceptionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
@@ -67,7 +68,7 @@ class HandlerMethodValidationExceptionTests {
private final HandlerMethod handlerMethod = handlerMethod(new ValidController(),
- controller -> controller.handle(person, person, person, person, "", "", "", "", "", ""));
+ controller -> controller.handle(person, person, person, List.of(), person, "", "", "", "", "", ""));
private final TestVisitor visitor = new TestVisitor();
@@ -84,7 +85,7 @@ class HandlerMethodValidationExceptionTests {
assertThat(this.visitor.getOutput()).isEqualTo("""
@ModelAttribute: modelAttribute1, @ModelAttribute: modelAttribute2, \
- @RequestBody: requestBody, @RequestPart: requestPart, \
+ @RequestBody: requestBody, @RequestBody: requestBodyList, @RequestPart: requestPart, \
@RequestParam: requestParam1, @RequestParam: requestParam2, \
@RequestHeader: header, @PathVariable: pathVariable, \
@CookieValue: cookie, @MatrixVariable: matrixVariable""");
@@ -100,7 +101,7 @@ class HandlerMethodValidationExceptionTests {
assertThat(this.visitor.getOutput()).isEqualTo("""
Other: modelAttribute1, @ModelAttribute: modelAttribute2, \
- @RequestBody: requestBody, @RequestPart: requestPart, \
+ @RequestBody: requestBody, @RequestBody: requestBodyList, @RequestPart: requestPart, \
Other: requestParam1, @RequestParam: requestParam2, \
@RequestHeader: header, @PathVariable: pathVariable, \
@CookieValue: cookie, @MatrixVariable: matrixVariable""");
@@ -155,6 +156,7 @@ class HandlerMethodValidationExceptionTests {
@Valid Person modelAttribute1,
@Valid @ModelAttribute Person modelAttribute2,
@Valid @RequestBody Person requestBody,
+ @RequestBody List<@NotEmpty String> requestBodyList,
@Valid @RequestPart Person requestPart,
@Size(min = 5) String requestParam1,
@Size(min = 5) @RequestParam String requestParam2,
@@ -222,6 +224,11 @@ class HandlerMethodValidationExceptionTests {
handle(requestBody, errors);
}
+ @Override
+ public void requestBodyValidationResult(RequestBody requestBody, ParameterValidationResult result) {
+ handle(requestBody, result);
+ }
+
@Override
public void requestHeader(RequestHeader requestHeader, ParameterValidationResult result) {
handle(requestHeader, result);
diff --git a/spring-web/src/test/java/org/springframework/web/util/DisconnectedClientHelperTests.java b/spring-web/src/test/java/org/springframework/web/util/DisconnectedClientHelperTests.java
index 296a1920927..0d79da9ebf4 100644
--- a/spring-web/src/test/java/org/springframework/web/util/DisconnectedClientHelperTests.java
+++ b/spring-web/src/test/java/org/springframework/web/util/DisconnectedClientHelperTests.java
@@ -90,4 +90,9 @@ public class DisconnectedClientHelperTests {
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isFalse();
}
+ @Test // gh-34533
+ void nullException() {
+ assertThat(DisconnectedClientHelper.isClientDisconnectedException(null)).isFalse();
+ }
+
}