One of the main obstacles to solving this problem is the default failure of Jackson's data binding mechanism; one would have to somehow convince him to continue the analysis, and not just stumble at the first error. You also need to collect these parsing errors in order to ultimately convert them to BindingResult records. Essentially, you would have to catch, suppress, and collect exceptions, convert them to BindingResult records BindingResult then add these records to the correct @Controller method.
Part capture and suppression can be performed:
- Jackson custom deserializers that will simply delegate the default ones, but will also catch, suppress, and collect their exceptions when parsing
- using
AOP (aspectj version), you can simply intercept the default deserializers, analyze exceptions, suppress and collect them - using other means, for example, the corresponding
BeanDeserializerModifier , you can also catch, suppress and collect exceptions during parsing; this may be the easiest approach, but it requires some knowledge of Jacksonβs tuning support
Part of the collection can use the ThreadLocal variable to store all the necessary details related to exceptions. Converting to BindingResult entries and adding BindingResult to the right argument can be quite easily done by the AOP interceptor in @Controller methods (any type of AOP , including the Spring variant).
What a win
With this approach, data binding errors (in addition to validation errors) BindingResult argument BindingResult in the same way as when receiving, for example, @ModelAttribute . It will also work with several levels of embedded objects - the solution presented in this question is not suitable for this.
Solution Details (Jackson Custom Deserializers Approach)
I created a small project proving the solution (run the test class), and here I just highlight the main parts:
@Aspect @Component public class BindingErrorsHandler { @Before("@within(org.springframework.web.bind.annotation.RestController)") public void logBefore(JoinPoint joinPoint) {
With this, you will get something like this in BindingResult :
Field error in object 'level1' on field 'nr12': rejected value [1]; codes [Min.level1.nr12,Min.nr12,Min.java.lang.Integer,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [level1.nr12,nr12]; arguments []; default message [nr12],5]; default message [must be greater than or equal to 5] Field error in object 'level1' on field 'nr11': rejected value [x]; codes []; arguments []; default message [null] Field error in object 'level1' on field 'level2.level3.nr31': rejected value [xxx]; codes []; arguments []; default message [null] Field error in object 'level1' on field 'level2.nr22': rejected value [1]; codes [Min.level1.level2.nr22,Min.level2.nr22,Min.nr22,Min.java.lang.Integer,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [level1.level2.nr22,level2.nr22]; arguments []; default message [level2.nr22],5]; default message [must be greater than or equal to 5]
where the 1st line is determined by the verification error (setting 1 as the value for @Min(5) private Integer nr12; ), while the 2nd @Min(5) private Integer nr12; defined by binding (setting "x" as the value for @JsonDeserialize(using = CustomIntegerDeserializer.class) private Integer nr11; ). The third line tests binding errors with built-in objects: level1 contains level2 which contains level3 object.
Note that other approaches may simply replace the use of custom Jackson deserializers while preserving the rest of the solution ( AOP , JsonParsingFeedBack ).