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