8

I'm reading through the Spring 4 API and I'm trying to understand the difference between FieldError, ObjectError and global error, all in the context of a BindingResult. I'm guessing that global errors are another name for ObjectErrors since getGlobalError() returns an ObjectError.

The context is that I am looking to simply print error messages after some form validation and want to know how I can avoid an instanceof check, as in this accepted answer. Can I just use FieldError and ignore object errors? What would I be missing if I only logged FieldErrors?

I've tried a few scenarios but don't yet see the distinction. Will look through some source in the meantime.

Community
  • 1
  • 1
riddle_me_this
  • 8,575
  • 10
  • 55
  • 80

1 Answers1

5

I'm guessing that global errors are another name for ObjectErrors since getGlobalError() returns an ObjectError.

Actually a "global error" is any ObjectError that is not an instance of a FieldError see source of getGlobalErrors()

What would I be missing if I only logged FieldErrors?

Any ObjectErrors that code registered as a "global error" e.g. by calling BindingResult.reject(errorCode, errorArgs, defaultMessage).

See also the javadoc for rejectValue(field, errorCode, errorArgs, defaultMessage). Typically errors are registered against fields of the validated/bound object (e.g. the model value whose attribute matches the modelAttribute tag of the Spring form tag) as opposed to the object itself.

Following are a couple of ways to create global errors:

  1. Assuming it's the root form object and not a nested object that's being validated via a Spring Validator implementation, you could add a "global error" (in the context of the specified bound root object) by passing null as the field name parameter of rejectValue. If the object being validated is a nested object, however, a FieldError would be registered against the nested object field. So it matters what is the nestedPath ("nested object graph") property of the target Errors object with respect to whether a general ObjectError or specific FieldError is added.

  2. Via a JSR 303 constraint annotation applied at the class level. See example in which a model object is checked for pre-existence in a datastore.

Here's an example to reference the global vs the field level errors:

public class OverflowErrorsTag extends HtmlEscapingAwareTag {

    public static final String OVERFLOW_ERRORS_VARIABLE_NAME = "overflowErrors";
    public static final String GLOBAL_ERRORS_VARIABLE_NAME = "globalErrors";

    private String name;

    /**
     * Set the name of the bean that this tag should check.
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Return the name of the bean that this tag checks.
     */
    public String getName() {
        return this.name;
    }

    @Override
    protected final int doStartTagInternal() throws ServletException, JspException {
        Errors errors = getRequestContext().getErrors(this.name, isHtmlEscape());
        Set<FieldError> subsequentErrors = Sets.newTreeSet((fe1, fe2) -> fe1.getField().compareTo(fe2.getField()));
        Set<ObjectError> globalErrors = new HashSet<>();
        if (errors != null) {
            Set<String> firstErrorFields = new HashSet<>();
            for (FieldError fieldError : errors.getFieldErrors()) {
                if (firstErrorFields.contains(fieldError.getField())) {
                    subsequentErrors.add(fieldError);
                } else {
                    firstErrorFields.add(fieldError.getField());
                }
            }
            for (ObjectError objectError : errors.getGlobalErrors()) {
                globalErrors.add(objectError);
            }
        }
        if (subsequentErrors.isEmpty() && globalErrors.isEmpty()) {
            return SKIP_BODY;
        } else {
            this.pageContext.setAttribute(OVERFLOW_ERRORS_VARIABLE_NAME, subsequentErrors, PageContext.REQUEST_SCOPE);
            this.pageContext.setAttribute(GLOBAL_ERRORS_VARIABLE_NAME, globalErrors, PageContext.REQUEST_SCOPE);
            return EVAL_BODY_INCLUDE;
        }
    }

    @Override
    public int doEndTag() {
        this.pageContext.removeAttribute(OVERFLOW_ERRORS_VARIABLE_NAME, PageContext.REQUEST_SCOPE);
        this.pageContext.removeAttribute(GLOBAL_ERRORS_VARIABLE_NAME, PageContext.REQUEST_SCOPE);
        return EVAL_PAGE;
    }

}

Then to display this tag containing both global and field errors in the view:

<spring-ext:overflowErrors name="newModelObject">
        <div class="row">
            <div class="large-12 columns">
                <div class="alert panel">
                    <c:if test="${overflowErrors.size()>0}">
                        <p>There are multiple errors with your entry.</p>
                        <c:forEach var="error" items="${overflowErrors}">
                            ${fn:toUpperCase(fn:substring(error.field, 0, 1))}${fn:toLowerCase(
                            fn:substring(error.field, 1,fn:length(error.field)))}:
                            <b><spring:message message="${error}" /></b>
                            <br/>
                        </c:forEach>
                    </c:if>
                    <c:forEach var="error" items="${globalErrors}">
                        <b><spring:message message="${error}" /></b>
                        <br/>
                    </c:forEach>
                </div>
            </div>
        </div>
    </spring-ext:overflowErrors>
Community
  • 1
  • 1