Spring WebMVC allows validating method parameters of controller methods - for example like this:
@PostMapping(value = "/attachements", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> addAttachement(@Valid @RequestBody EmailAttachement attachement) {
...
}
Inside the EmailAttachement type, we defined a byte[] field with a validation constraint of max. 5 MB:
public class EmailAttachement {
@Size(max=5_000_000)
private byte[] attachement;
Calling the /attachements endpoint with an slightly to large payload (6mb) leads to an ValidationException and then to an OutOfMemoryError during Exceptionhandling in HandlerExceptionResolver (application running with -Xmx256m'.
java.lang.OutOfMemoryError: Java heap space
at java.base/java.lang.reflect.Array.newArray(Native Method) ~[na:na]
at java.base/java.lang.reflect.Array.newInstance(Array.java:76) ~[na:na]
at java.base/java.util.Arrays.copyOf(Arrays.java:3510) ~[na:na]
at java.base/java.util.Arrays.copyOf(Arrays.java:3478) ~[na:na]
at java.base/java.util.StringJoiner.add(StringJoiner.java:191) ~[na:na]
at org.springframework.util.ObjectUtils.nullSafeToString(ObjectUtils.java:711) ~[spring-core-6.2.11.jar:6.2.11]
at org.springframework.util.ObjectUtils.nullSafeToString(ObjectUtils.java:621) ~[spring-core-6.2.11.jar:6.2.11]
at org.springframework.validation.FieldError.toString(FieldError.java:129) ~[spring-context-6.2.11.jar:6.2.11]
at java.base/java.lang.String.valueOf(String.java:4530) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na]
at org.springframework.web.bind.MethodArgumentNotValidException.getMessage(MethodArgumentNotValidException.java:103) ~[spring-web-6.2.11.jar:6.2.11]
at org.springframework.web.util.DisconnectedClientHelper.isClientDisconnectedException(DisconnectedClientHelper.java:125) ~[spring-web-6.2.11.jar:6.2.11]
at org.springframework.web.util.DisconnectedClientHelper.checkAndLogClientDisconnectedException(DisconnectedClientHelper.java:82) ~[spring-web-6.2.11.jar:6.2.11]
at org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:183) ~[spring-webmvc-6.2.11.jar:6.2.11]
at org.springframework.web.servlet.handler.HandlerExceptionResolverComposite.resolveException(HandlerExceptionResolverComposite.java:80) ~[spring-webmvc-6.2.11.jar:6.2.11]
at org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1360) ~[spring-webmvc-6.2.11.jar:6.2.11]
Reason
During Exception handling, the DisconnectedClientHelper is invoked to check for 'broken pipe' or 'connection reset by peer'. To do this, DisconnectedClientHelper tries to check the message of the occured exception - which is a MethodArgumentNotValidException.
MethodArgumentNotValidException in turn format the message with a string representation of all validation errors - in this case a FieldError.
The toString() method of FieldError uses Spring's ObjectUtils to stringify the field content. byte-arrays are formatted by ObjectUtils with a StringJoiner, adding the numerical Value of each byte as String to the StringJoiner.
So each byte to be fomatted results in a short String with 1-3 characters - requiring 48 bytes of memory. Because StringJoiner collects all Strings in an internal String-Array until the toString() method creates the resulting String, formatting an 6mb byte-array needs about 288mb temporary memory.
Possible Enhancemens * Do not use StringJoiner for byte Arrays. * format byte[] as hex string instead of comma-seperated list of decimals * limit FieldError.toString() output
Sample Application to produce this error:
https://github.com/LVM-IT/spring-web-validation-oom
Comment From: tvahrst
concerning the label 'in: web' : the title of this issue (WebMVC...) may be misleading.
I am not sure whether this is an issue of spring-web (i.e. MethodArgumentNotValidException#getMessage appending all errors to the message)
or an issue of spring-context (i.e. org.springframework.validation.FieldError#toString using ObjectUtils)
or an issue of spring-core (i.e. ObjectUtils using StringJoiner to format byte arrays - which is the root cause of the memory exhaustion )
Comment From: rstoyanchev
It's definitely not Spring MVC specific. We're evaluating how to address, and will update the issue accordingly.