36

I have an controller that returns JSON. It takes a form, which validates itself via spring annotations. I can get FieldError list from BindingResult, but they don't contain the text that a JSP would display in the <form:errors> tag. How can I get the error text to send back in JSON?

@RequestMapping(method = RequestMethod.POST)
public @ResponseBody JSONResponse submit(@Valid AnswerForm answerForm, BindingResult result, Model model, HttpServletRequest request, HttpServletResponse response) {
    if (result.hasErrors()) {
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        JSONResponse r = new JSONResponse();
        r.setStatus(JSONResponseStatus.ERROR);
        //HOW DO I GET ERROR MESSAGES OUT OF BindingResult??? 
    } else {
        JSONResponse r = new JSONResponse();
        r.setStatus(JSONResponseStatus.OK);
        return r;
    }
}

JSONREsponse class is just a POJO

public class JSONResponse implements Serializable {
    private JSONResponseStatus status;
    private String error;
    private Map<String,String> errors;
    private Map<String,Object> data;
    
    // ...getters and setters...
}

Calling BindingResult.getAllErrors() returns an array of FieldError objects, but it does not have the actual errors.

Vy Do
  • 46,709
  • 59
  • 215
  • 313
Mike
  • 1,176
  • 3
  • 14
  • 26

6 Answers6

46

Disclaimer: I still do not use Spring-MVC 3.0

But i think the same approach used by Spring 2.5 can fullfil your needs

for (Object object : bindingResult.getAllErrors()) {
    if(object instanceof FieldError) {
        FieldError fieldError = (FieldError) object;

        System.out.println(fieldError.getCode());
    }

    if(object instanceof ObjectError) {
        ObjectError objectError = (ObjectError) object;

        System.out.println(objectError.getCode());
    }
}

I hope it can be useful to you

UPDATE

If you want to get the message provided by your resource bundle, you need a registered messageSource instance (It must be called messageSource)

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames" value="ValidationMessages"/>
</bean>

Inject your MessageSource instance inside your View

@Autowired
private MessageSource messageSource;

And to get your message, do as follows

for (Object object : bindingResult.getAllErrors()) {
    if(object instanceof FieldError) {
        FieldError fieldError = (FieldError) object;

        /**
          * Use null as second parameter if you do not use i18n (internationalization)
          */

        String message = messageSource.getMessage(fieldError, null);
    }
}

Your Validator should looks like

/**
  * Use null as fourth parameter if you do not want a default message
  */
errors.rejectValue("<FIELD_NAME_GOES_HERE>", "answerform.questionId.invalid", new Object [] {"123"}, null);
Arthur Ronald
  • 33,349
  • 20
  • 110
  • 136
  • 1
    Let's say I have following in ValidationMessages.properties: "answerform.questionId.invalid = Invalid question id: {0}." fieldError.getCode() will return "answerform.questionId.invalid", I am looking for the error itself, not the code, ex: "Invalid question id: 123" – Mike May 03 '10 at 17:03
  • Thanks this helped me... if using internationalization then just pass the locale (and args if needed) to get the translated string to place in your JSON error object: messageSource.getMessage (fieldError.getCode(), fieldError.getArguments(), myQuestionnaire.getLocale ()) – armyofda12mnkeys Jun 17 '15 at 14:50
37

I met this problem recently, and found an easier way (maybe it's the support of Spring 3)

    List<FieldError> errors = bindingResult.getFieldErrors();
    for (FieldError error : errors ) {
        System.out.println (error.getObjectName() + " - " + error.getDefaultMessage());
    }

There's no need to change/add the message source.

Hoàng Long
  • 10,746
  • 20
  • 75
  • 124
  • @J.Wincewicz: sorry for not approving your edit suggestion. The above code I what I have tested (and running) in Spring 3. Your code suggests using a new method, which I'm not 100% sure that it will work with this old version of Spring. You may post it as a separate answer or comments on this post, though. – Hoàng Long Oct 06 '17 at 03:31
14

With Java 8 Streams

bindingResult
.getFieldErrors()
.stream()
.forEach(f -> System.out.println(f.getField() + ": " + f.getDefaultMessage()));
pirs
  • 2,410
  • 2
  • 18
  • 25
Krzysiek
  • 141
  • 1
  • 2
  • 8
    The 'stream().forEach()' chain can be replaced with 'forEach()' https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html#forEach-java.util.function.Consumer- – Simon Sep 14 '18 at 09:11
2

BEAN XML:

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames">
        <list>
            <value>messages</value>
        </list>            
    </property>
</bean>

<bean id="messageAccessor" class="org.springframework.context.support.MessageSourceAccessor">
    <constructor-arg index="0" ref="messageSource"/>
</bean> 

JAVA:

for (FieldError error : errors.getFieldErrors()) {
    logger.debug(messageAccessor.getMessage(error));
}

NOTE: Calling Errors.getDefaultMessage() will not necessarily return the same message that is generated from the code + args. The defaultMessage is a separate value defined when calling the Errors.rejectValue() method. See Errors.rejectValue() API Here

splashout
  • 537
  • 5
  • 11
2

WebMvcConfigurerAdapter:

@Bean(name = "messageSourceAccessor")
public org.springframework.context.support.MessageSourceAccessor messageSourceAccessor() {
    return new MessageSourceAccessor( messageSource());
}

Controller:

@Autowired
@Qualifier("messageSourceAccessor")
private MessageSourceAccessor           messageSourceAccessor;
...

StringBuilder sb = new StringBuilder();
for (ObjectError error : result.getAllErrors()) {
    if ( error instanceof FieldError) {
        FieldError fe = (FieldError) error;
        sb.append( fe.getField());
        sb.append( ": ");
    }
    sb.append( messageSourceAccessor.getMessage( error));
    sb.append( "<br />");
}
1

To obtain a Map of all the errors associated with each field:

@Autowired
private MessageSource messageSource;

Map<String, List<String>> errorMap = bindingResult.getFieldErrors().stream()
    .collect(Collectors.groupingBy(FieldError::getField, 
    Collectors.mapping(e -> messageSource.getMessage(e, LocaleContextHolder.getLocale()), 
                        Collectors.toList())));
//In this example, each field/key is mapped to a 
//List of all the errors associated with that field

To obtain a Map with the errors associated with each field merged together as a single String:

@Autowired
private MessageSource messageSource;

Map<String, String> errorMap = bindingResult.getFieldErrors().stream()
        .collect(Collectors
                .toMap(FieldError::getField, 
                    e -> messageSource.getMessage(e, LocaleContextHolder.getLocale()), 
                    (a,b)->a + "<br/>" + b));
//In this example, multiple errors for the same field are separated with <br/>, 
//the line break element, to be easily displayed on the front end
Unmitigated
  • 76,500
  • 11
  • 62
  • 80