diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java index 82784d15c1f..1f17bf8b138 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java @@ -553,7 +553,7 @@ public class MethodValidationAdapter implements MethodValidator { @SuppressWarnings("serial") - private static class ViolationMessageSourceResolvable extends DefaultMessageSourceResolvable { + private class ViolationMessageSourceResolvable extends DefaultMessageSourceResolvable { private final transient ConstraintViolation violation; @@ -567,6 +567,11 @@ public class MethodValidationAdapter implements MethodValidator { public ConstraintViolation getViolation() { return this.violation; } + + @Override + public boolean shouldRenderDefaultMessage() { + return validatorAdapter.get().requiresMessageFormat(this.violation); + } } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java index d14b5251814..8c6e7982c70 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java @@ -18,6 +18,7 @@ package org.springframework.validation.beanvalidation; import java.io.Serializable; import java.util.ArrayList; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -64,7 +65,7 @@ import org.springframework.validation.SmartValidator; */ public class SpringValidatorAdapter implements SmartValidator, jakarta.validation.Validator { - private static final Set internalAnnotationAttributes = Set.of("message", "groups", "payload"); + private static final Set INTERNAL_ANNOTATION_ATTRIBUTES = Set.of("message", "groups", "payload"); @Nullable @@ -145,10 +146,21 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio */ @SuppressWarnings("serial") protected void processConstraintViolations(Set> violations, Errors errors) { + // Pre-identify binding failures (using getAllErrors to avoid intermediate list) + Set failedFields = new HashSet<>(); + if (errors.hasErrors()) { + for (ObjectError error : errors.getAllErrors()) { + if (error instanceof FieldError fieldError && fieldError.isBindingFailure()) { + failedFields.add(fieldError.getField()); + } + } + } + + // Turn the ConstraintViolations into Object/FieldErrors, + // but only for fields where binding has been successful. for (ConstraintViolation violation : violations) { String field = determineField(violation); - FieldError fieldError = errors.getFieldError(field); - if (fieldError == null || !fieldError.isBindingFailure()) { + if (!failedFields.contains(field)) { try { ConstraintDescriptor cd = violation.getConstraintDescriptor(); String errorCode = determineErrorCode(cd); @@ -159,14 +171,14 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio String nestedField = bindingResult.getNestedPath() + field; if (nestedField.isEmpty()) { String[] errorCodes = bindingResult.resolveMessageCodes(errorCode); - ObjectError error = new ViolationObjectError( + ObjectError error = new ValidationObjectError( errors.getObjectName(), errorCodes, errorArgs, violation, this); bindingResult.addError(error); } else { Object rejectedValue = getRejectedValue(field, violation, bindingResult); String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field); - FieldError error = new ViolationFieldError(errors.getObjectName(), nestedField, + FieldError error = new ValidationFieldError(errors.getObjectName(), nestedField, rejectedValue, errorCodes, errorArgs, violation, this); bindingResult.addError(error); } @@ -261,7 +273,7 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio // Using a TreeMap for alphabetical ordering of attribute names Map attributesToExpose = new TreeMap<>(); descriptor.getAttributes().forEach((attributeName, attributeValue) -> { - if (!internalAnnotationAttributes.contains(attributeName)) { + if (!INTERNAL_ANNOTATION_ATTRIBUTES.contains(attributeName)) { if (attributeValue instanceof String str) { attributeValue = new ResolvableAttribute(str); } @@ -442,7 +454,7 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio * Subclass of {@code ObjectError} with Spring-style default message rendering. */ @SuppressWarnings("serial") - private static class ViolationObjectError extends ObjectError implements Serializable { + private static class ValidationObjectError extends ObjectError implements Serializable { @Nullable private transient SpringValidatorAdapter adapter; @@ -450,7 +462,7 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio @Nullable private transient ConstraintViolation violation; - public ViolationObjectError(String objectName, String[] codes, Object[] arguments, + public ValidationObjectError(String objectName, String[] codes, Object[] arguments, ConstraintViolation violation, SpringValidatorAdapter adapter) { super(objectName, codes, arguments, violation.getMessage()); @@ -472,7 +484,7 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio * Subclass of {@code FieldError} with Spring-style default message rendering. */ @SuppressWarnings("serial") - private static class ViolationFieldError extends FieldError implements Serializable { + private static class ValidationFieldError extends FieldError implements Serializable { @Nullable private transient SpringValidatorAdapter adapter; @@ -480,7 +492,7 @@ public class SpringValidatorAdapter implements SmartValidator, jakarta.validatio @Nullable private transient ConstraintViolation violation; - public ViolationFieldError(String objectName, String field, @Nullable Object rejectedValue, String[] codes, + public ValidationFieldError(String objectName, String field, @Nullable Object rejectedValue, String[] codes, Object[] arguments, ConstraintViolation violation, SpringValidatorAdapter adapter) { super(objectName, field, rejectedValue, false, codes, arguments, violation.getMessage());