From 2801c7e23e6d429ed886b69559d8361a0e49b658 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 8 Apr 2026 13:39:10 +0200 Subject: [PATCH] Avoid MessageFormat rendering for exception messages in binding errors Closes gh-36609 (cherry picked from commit 0150c4ba06d6b4e388f19e98e947a1aecb117c55) --- .../DefaultBindingErrorProcessor.java | 42 +++++++++++++++---- .../validation/DataBinderTests.java | 4 ++ 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/validation/DefaultBindingErrorProcessor.java b/spring-context/src/main/java/org/springframework/validation/DefaultBindingErrorProcessor.java index 47aa53f0465..dd1b3e18695 100644 --- a/spring-context/src/main/java/org/springframework/validation/DefaultBindingErrorProcessor.java +++ b/spring-context/src/main/java/org/springframework/validation/DefaultBindingErrorProcessor.java @@ -16,6 +16,10 @@ package org.springframework.validation; +import java.io.Serializable; + +import org.jspecify.annotations.Nullable; + import org.springframework.beans.PropertyAccessException; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.util.Assert; @@ -59,9 +63,8 @@ public class DefaultBindingErrorProcessor implements BindingErrorProcessor { String fixedField = bindingResult.getNestedPath() + missingField; String[] codes = bindingResult.resolveMessageCodes(MISSING_FIELD_ERROR_CODE, missingField); Object[] arguments = getArgumentsForBindError(bindingResult.getObjectName(), fixedField); - FieldError error = new FieldError(bindingResult.getObjectName(), fixedField, "", true, - codes, arguments, "Field '" + fixedField + "' is required"); - bindingResult.addError(error); + bindingResult.addError(new BindingFieldError( + bindingResult.getObjectName(), fixedField, "", codes, arguments)); } @Override @@ -75,10 +78,8 @@ public class DefaultBindingErrorProcessor implements BindingErrorProcessor { if (ObjectUtils.isArray(rejectedValue)) { rejectedValue = StringUtils.arrayToCommaDelimitedString(ObjectUtils.toObjectArray(rejectedValue)); } - FieldError error = new FieldError(bindingResult.getObjectName(), field, rejectedValue, true, - codes, arguments, ex.getLocalizedMessage()); - error.wrap(ex); - bindingResult.addError(error); + bindingResult.addError(new BindingFieldError( + bindingResult.getObjectName(), field, rejectedValue, codes, arguments, ex)); } /** @@ -97,4 +98,31 @@ public class DefaultBindingErrorProcessor implements BindingErrorProcessor { return new Object[] {new DefaultMessageSourceResolvable(codes, field)}; } + + /** + * Subclass of {@code FieldError} with Spring-style default message rendering. + */ + @SuppressWarnings("serial") + private static class BindingFieldError extends FieldError implements Serializable { + + public BindingFieldError(String objectName, String field, @Nullable Object rejectedValue, String[] codes, + Object[] arguments) { + + super(objectName, field, rejectedValue, true, codes, arguments, + "Field '" + field + "' is required"); + } + + public BindingFieldError(String objectName, String field, @Nullable Object rejectedValue, String[] codes, + Object[] arguments, PropertyAccessException ex) { + + super(objectName, field, rejectedValue, true, codes, arguments, ex.getLocalizedMessage()); + wrap(ex); + } + + @Override + public boolean shouldRenderDefaultMessage() { + return false; + } + } + } diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java index 23b3f38a48a..a9ec8505143 100644 --- a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java +++ b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java @@ -1854,8 +1854,12 @@ class DataBinderTests { FieldError ageError = errors.getFieldError("age"); assertThat(ageError.getCode()).isEqualTo("typeMismatch"); + assertThat(ageError.isBindingFailure()).isTrue(); + assertThat(ageError.shouldRenderDefaultMessage()).isFalse(); FieldError nameError = errors.getFieldError("name"); assertThat(nameError.getCode()).isEqualTo("badName"); + assertThat(nameError.isBindingFailure()).isFalse(); + assertThat(nameError.shouldRenderDefaultMessage()).isTrue(); } @Test