How do BindingResult and the Errors interface represent binding and validation outcomes, and how do you read them?
answer
- BindingResult extends Errors
- ObjectError (reject) vs FieldError (rejectValue)
- typeMismatch auto-recorded, keeps rejected value
- codes + args + default message → MessageSource
- MVC: param must follow the model attribute
basics
~10 sBindingResult (a sub-interface of Errors) is the container that holds everything that went wrong during binding and validation: global ObjectErrors and per-field FieldErrors. You query it with hasErrors(), getFieldErrors(), getFieldError(name) and rejectValue().
solid answer
~40 sorg.springframework.validation.Errors is Spring's abstraction for a bound object's errors and its target; BindingResult extends it and adds binding-specific detail (access to the target, the PropertyEditor/ConversionService used, suppressed fields). Errors distinguishes global errors (ObjectError, via reject(code)) from field errors (FieldError, via rejectValue(field, code)). Binding failures like a String→int mismatch are auto-recorded as FieldErrors with the code 'typeMismatch' and the rejected value preserved for redisplay. You read it with hasErrors(), hasFieldErrors(), getFieldErrors(), getGlobalErrors(), getFieldError(name), getErrorCount(). Each error carries codes, arguments and a default message, resolved to text via a MessageSource. In Spring MVC you declare a BindingResult parameter immediately after the @ModelAttribute it refers to, and check it before proceeding. Validators receive Errors and call rejectValue to add their own.
code
java · 20 lines@PostMapping("/people")
public String submit(@ModelAttribute("person") Person person,
BindingResult result) { // MUST come right after person
// A non-numeric 'age' is already recorded as a 'typeMismatch' FieldError.
// Add a cross-field / global error:
if (person.getPassword() != null
&& !person.getPassword().equals(person.getConfirm())) {
result.reject("password.mismatch", "Passwords do not match");
}
// Add a field error:
if (person.getName() == null || person.getName().isBlank()) {
result.rejectValue("name", "name.required", "Name is required");
}
if (result.hasErrors()) {
FieldError age = result.getFieldError("age"); // may be typeMismatch
return "personForm";
}
service.save(person);
return "redirect:/people";
}go deeper
Know it collects what went wrong and you check hasErrors().
Distinguish global vs field errors and know typeMismatch is auto-added; read getFieldErrors().
Explain the Errors↔BindingResult relationship, message-code resolution, the MVC parameter-ordering rule, and shared accumulation with validators.
Design consistent error contracts (codes, i18n via MessageSource, API error mapping) and reason about where binding vs validation errors originate in the pipeline.
## Two interfaces, one hierarchy - `org.springframework.validation.Errors` — the general contract for storing and querying errors bound to a *target object* and a *object name*. It's what a `Validator` receives. - `org.springframework.validation.BindingResult` — **extends `Errors`** and is the richer, binding-aware view: it also exposes the target object, the model map key, the `PropertyEditorRegistry`/type info used during binding, and `getSuppressedFields()`. The default implementation is `BeanPropertyBindingResult`. `DataBinder.getBindingResult()` returns the `BindingResult`; the same object is what a Spring MVC handler receives as a method parameter. ## Two kinds of error - **Global / object errors** → `ObjectError`, added with `errors.reject(String errorCode)` / `reject(code, defaultMessage)`. Not tied to a field (e.g. "password and confirmation don't match"). - **Field errors** → `FieldError` (a subclass of `ObjectError`), added with `errors.rejectValue(String field, String errorCode)`. A `FieldError` also records the **rejected value** and whether it was a binding failure — so a form can redisplay exactly what the user typed even though it couldn't be set on the (typed) property. ## Auto-recorded binding failures When binding itself fails — e.g. `"abc"` into an `int age` — the binder records a `FieldError` automatically, conventionally with error code **`typeMismatch`**, preserving the offending input. You don't call `rejectValue` yourself for these; they appear from the bind step. (The *conversion* that failed is a separate SPI concern; here we care that the *failure* surfaces as a FieldError.) ## Reading it ```java if (result.hasErrors()) { ... } result.getErrorCount(); List<FieldError> fes = result.getFieldErrors(); FieldError fe = result.getFieldError("age"); List<ObjectError> ges = result.getGlobalErrors(); result.hasFieldErrors("age"); ``` ## Error codes and messages Each error holds an **array of codes** (most specific → least specific), optional **arguments**, and a **default message**. Spring's `DefaultMessageCodesResolver` expands a rejected field into codes like `typeMismatch.person.age`, `typeMismatch.age`, `typeMismatch.int`, `typeMismatch`. A `MessageSource` (e.g. `messages.properties`) resolves these to human text — enabling i18n. In MVC, `<form:errors>` / Thymeleaf `th:errors` render them. ## Spring MVC parameter ordering gotcha The `BindingResult` parameter **must immediately follow** the `@ModelAttribute`/`@Valid` object it describes: ```java public String submit(@Valid @ModelAttribute Person p, BindingResult result) { ... } ``` If you put another parameter between them, Spring won't associate the result with that object and will instead **throw** on a bind/validation error rather than hand you the result. ## Validation vs binding — same container A `Validator.validate(target, Errors)` uses the *same* `Errors` object, so binding errors (typeMismatch) and validation errors (e.g. `@NotNull`, or programmatic `rejectValue`) accumulate together. (JSR-380 `@Valid` constraint checking is a separate leaf, but its violations land in this same `BindingResult`.) ## Useful helpers `ValidationUtils.rejectIfEmpty(errors, "name", "name.required")` is a shortcut for common field checks inside a Validator. ## When you use it Every Spring MVC form handler; custom `Validator`s; anywhere you drive `DataBinder` directly and need to know what failed without try/catch around each field.
- What is the difference between reject() and rejectValue() on Errors?reject() adds a global ObjectError not tied to any field (e.g. a cross-field rule). rejectValue() adds a FieldError for a named property and records the rejected value so forms can redisplay the bad input.
- Why must the BindingResult parameter come immediately after the model attribute in an MVC handler?Spring pairs each errors holder with the object right before it. If another parameter intervenes, the result isn't associated, and Spring throws a BindException on error instead of populating your BindingResult.
- Where does a String→int conversion failure show up?As an automatically added FieldError with the code 'typeMismatch' on that field, with the original String preserved as the rejected value.
saying these in an interview costs you the question
- Confusing ObjectError (global) with FieldError (field-scoped)
- Placing another parameter between @Valid object and BindingResult
- Thinking BindingResult holds only validation errors, not binding/typeMismatch errors
- Assuming error codes are the final message text rather than keys resolved via MessageSource