What is MessageSourceResolvable, and how does it connect Spring's validation errors (FieldError/ObjectError) to MessageSource?
answer
- codes[] + args[] + defaultMessage
- tries codes in order, first hit wins
- FieldError/ObjectError extend DefaultMessageSourceResolvable
- NotNull.user.email → NotNull.email → NotNull.String → NotNull
- args can be resolvables (recursive)
basics
~20 sMessageSourceResolvable is an object carrying candidate codes, arguments, and a default message. MessageSource tries the codes in order and returns the first that resolves. Validation errors (FieldError, ObjectError) implement it, so getMessage(error, locale) produces localized error text.
solid answer
~40 sMessageSourceResolvable is an interface bundling everything needed to resolve a message: getCodes() (candidate codes, most specific first), getArguments() (substitution args), and getDefaultMessage(). MessageSource.getMessage(MessageSourceResolvable, Locale) walks the codes and returns the first one that resolves, falling back to the default message. DefaultMessageSourceResolvable is the base implementation, and Spring's validation ObjectError/FieldError extend it. When a validator or Bean Validation constraint fails, Spring generates several codes from most to least specific — e.g. for a @NotNull on User.email: NotNull.user.email, NotNull.email, NotNull.java.lang.String, NotNull — so you can override the message at any granularity in messages.properties. The field name and value become arguments, and the rejected field is often passed as a resolvable argument itself so its label is localized too. This is why <spring:message> / getMessage over a FieldError yields fully localized validation output.
code
java · 23 lines// messages.properties
// NotBlank.user.email=Please enter your email address.
// NotBlank=The {0} must not be blank.
public class User {
@NotBlank private String email; // getters/setters omitted
}
@PostMapping("/users")
public String create(@Valid @ModelAttribute User user,
BindingResult binding,
Locale locale,
MessageSource messages) {
if (binding.hasErrors()) {
for (FieldError error : binding.getFieldErrors()) {
// FieldError IS a MessageSourceResolvable: codes tried most-specific first
String text = messages.getMessage(error, locale);
// -> "Please enter your email address."
log.info(text);
}
}
return "form";
}go deeper
Aware validation messages come from message codes in properties files.
Knows FieldError resolves via MessageSource and you can key by field.
Explains the codes/args/default triple, first-match resolution, and the generated code hierarchy.
Standardizes MessageCodesResolver strategy and code conventions so validation messages stay consistent and translatable across teams.
**The interface.** `org.springframework.context.MessageSourceResolvable` decouples 'what to resolve' from the `MessageSource` doing the resolving. It has three methods: - `String[] getCodes()` — candidate message codes, ordered **most specific first**. - `Object[] getArguments()` — arguments for `MessageFormat` substitution (an argument may itself be a `MessageSourceResolvable`, so it gets resolved recursively). - `String getDefaultMessage()` — text returned if none of the codes resolve. **Resolution algorithm.** `getMessage(MessageSourceResolvable resolvable, Locale locale)` iterates the codes in order and returns the message for the **first code that resolves**. If none resolve, it returns the `defaultMessage`; if that's also null it throws `NoSuchMessageException` (unless `useCodeAsDefaultMessage` is set). This 'try several codes, fall back' pattern is the whole point — it enables layered overrides. **DefaultMessageSourceResolvable.** The standard implementation holding codes/args/default. You can construct one directly, but its real importance is as the **superclass of validation errors**. **Validation integration.** Spring's `org.springframework.validation.ObjectError` and its subclass `FieldError` **extend** `DefaultMessageSourceResolvable`. When binding/validation fails (either classic `Validator` + `Errors`, or Bean Validation `@Valid` via `SpringValidatorAdapter`), Spring populates each error with a graded list of codes produced by `DefaultMessageCodesResolver`. Example — `@NotNull` on `User.email` (a `String`) yields, most-specific to least: ``` NotNull.user.email NotNull.email NotNull.java.lang.String NotNull ``` You put a key for whichever level you want to control in `messages.properties`: ``` NotNull.email=Email is required. NotNull=The {0} field must not be null. ``` The **arguments** typically include a `DefaultMessageSourceResolvable` for the field name (codes like `user.email`, `email`) so the label itself is localizable, followed by any constraint attributes. That's why rendering a `FieldError` through `MessageSource` (or `<spring:message message=...>` / `th:errors`) produces fully translated messages. **Bean Validation interplay.** With Jakarta Bean Validation, the constraint's own `message` (e.g. `{jakarta.validation.constraints.NotNull.message}`) is one source, but when Spring's `LocalValidatorFactoryBean` is wired as the `Validator`, Spring can resolve messages through the `MessageSource` too, and the generated codes above let you override per-field. **When to use it directly.** Rarely you construct a `DefaultMessageSourceResolvable` to pass a pre-built code/args/default triple to a component that only knows how to call `getMessage(resolvable, locale)` — decoupling message identity from the resolution site. **Gotchas.** (1) Code order matters — most specific must come first. (2) An argument that is itself a resolvable gets resolved, so passing a raw `String` field name vs a resolvable changes whether it's localized. (3) The exact generated codes depend on the `MessageCodesResolver` strategy.
- For a @Size violation on Order.items, what codes does Spring generate and in what order?Most specific first: Size.order.items, Size.items, Size.<type-of-items> (e.g. Size.java.util.List), then Size. You override any level in messages.properties; MessageSource returns the first that resolves.
- Why is the field name often passed as a MessageSourceResolvable argument instead of a plain String?So the label itself is localizable. When an argument implements MessageSourceResolvable, MessageSource resolves it recursively (using codes like user.email/email), letting {0} render a translated field label rather than the raw property name.
saying these in an interview costs you the question
- Thinking getMessage(resolvable) tries all codes and concatenates (it returns the first that resolves)
- Not knowing FieldError/ObjectError are MessageSourceResolvables
- Believing code order is least-specific first