Affects: 5.3.27 and later versions
In spring-framework 5.3.27 ObjectUtils.nullSafeConciseToString()
method was introduced (#30287), that changed the way to generate null-safe, concise string representation of a supplied object.
We are indirectly using that method from spring-context
via FieldError.toString()
, to retrieve the string representation of a field failing JSR303 bean validation.
When upgrading from 5.3.24 to 5.3.27 we have had contract tests failing because in case of empty lists that should have at least 1 element, failing validation, the value returned for them was not anymore the expected []
, but ArrayList@[the_address]
.
I'll try to summarize:
Imagine you are in FieldError
because the Spring validation wrapping stuff has produced a MethodArgumentNotValidException
, the exception handler in the application catches it, and it calls FieldError.toString()
to fill in a ProblemDetails
.
With 5.3.24:
// snippet in the ApplicationResponseExceptionHandler:
private String getFieldErrorStr(final FieldError fieldErr) {
String result = "";
if (fieldErr != null) {
final String fullErr = fieldErr.toString();
result = fullErr.substring(0, fullErr.indexOf(";"));
}
return result;
}
// calling this in FieldError:
@Override
public String toString() {
return "Field error in object '" + getObjectName() + "' on field '" + this.field +
"': rejected value [" + ObjectUtils.nullSafeToString(this.rejectedValue) + "]; " +
resolvableToString();
}
// ...which in the case of an empty collection ended up here in org.springframework.util.ObjectUtils:
public static String nullSafeToString(@Nullable Object obj) {
if (obj == null) {
return NULL_STRING;
}
if (obj instanceof String) {
return (String) obj;
}
if (obj instanceof Object[]) {
return nullSafeToString((Object[]) obj);
}
if (obj instanceof boolean[]) {
return nullSafeToString((boolean[]) obj);
}
if (obj instanceof byte[]) {
return nullSafeToString((byte[]) obj);
}
if (obj instanceof char[]) {
return nullSafeToString((char[]) obj);
}
if (obj instanceof double[]) {
return nullSafeToString((double[]) obj);
}
if (obj instanceof float[]) {
return nullSafeToString((float[]) obj);
}
if (obj instanceof int[]) {
return nullSafeToString((int[]) obj);
}
if (obj instanceof long[]) {
return nullSafeToString((long[]) obj);
}
if (obj instanceof short[]) {
return nullSafeToString((short[]) obj);
}
String str = obj.toString();
return (str != null ? str : EMPTY_STRING);
}
For something like a list implementation, when empty, all the if's failed and the call to obj.toString()
before the return statement ended in java.util.AbstractCollection.toString
(that is the case of java.util.ArrayList
), which produced the expected result:
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";
...
}
With 5.3.27 (and onwards):
// snippet in the ApplicationResponseExceptionHandler same as before
// calling this in FieldError:
@Override
public String toString() {
return "Field error in object '" + getObjectName() + "' on field '" + this.field +
"': rejected value [" + ObjectUtils.nullSafeConciseToString(this.rejectedValue) + "]; " +
resolvableToString();
}
// ...which in the case of an empty collection ended up here in org.springframework.util.ObjectUtils:
public static String nullSafeConciseToString(@Nullable Object obj) {
if (obj == null) {
return "null";
}
if (obj instanceof Class<?>) {
return ((Class<?>) obj).getName();
}
if (obj instanceof CharSequence) {
return StringUtils.truncate((CharSequence) obj);
}
Class<?> type = obj.getClass();
if (isSimpleValueType(type)) {
String str = obj.toString();
if (str != null) {
return StringUtils.truncate(str);
}
}
return type.getTypeName() + "@" + getIdentityHexString(obj);
}
As before, with an empty list, obj does not enter any of the instanceof
if's, neither the isSimpleValueType
. As a result ArrayList@12345
is returned, which is wrong and not what we expect as the value making the validation fail, which is an empty list and not something like ArrayList@12345
.
Comment From: sbrannen
Thank you for raising the issue.
Due to your feedback and feedback from others in the community, we have decided to revert the changes to FieldError#toString
in 6.0.11 and 5.3.29.
Comment From: asjp1970
You're wellcome. What version of SpringBoot will include 5.3.29 ¿Do you know? Thanks
Comment From: jhoeller
Spring Boot 2.7.14 will include this, as well as the other upcoming Spring Boot releases in July.