What is WebDataBinder and what can you configure with an @InitBinder method?
answer
- New WebDataBinder per request, stateful
- @InitBinder runs before binding
- registerCustomEditor / setAllowedFields / setRequiredFields / setValidator
- @ControllerAdvice = global, @Controller = local
- void return; ignores @RequestBody
basics
~20 sWebDataBinder binds request parameters onto a target object and converts their types. An @InitBinder method receives that binder before binding so you can register custom editors/converters, restrict which fields may be bound, mark required fields, or attach a Validator.
solid answer
~40 sWebDataBinder is the object that performs the request-parameter-to-property binding for @ModelAttribute (and @RequestParam/@PathVariable conversion). For each request Spring builds a fresh WebDataBinder around the target, then calls any @InitBinder methods so you can customize it before values are applied. In an @InitBinder method you can: register per-controller PropertyEditors (registerCustomEditor), register Formatters/Converters into the binding's ConversionService, restrict binding with setAllowedFields/setDisallowedFields (mass-assignment defense), declare setRequiredFields, set a field-marker/prefix, and attach a Validator via setValidator. Put @InitBinder in a @ControllerAdvice to apply it globally, or in a single @Controller to scope it locally. You can also target one attribute by giving @InitBinder("user") the model attribute name. Binding/conversion failures accumulate in the binder's BindingResult rather than throwing.
code
java · 24 lines@ControllerAdvice
public class GlobalBinderAdvice {
@InitBinder
public void configure(WebDataBinder binder) {
// never let clients bind these, on any @ModelAttribute
binder.setDisallowedFields("id", "admin", "roles");
binder.registerCustomEditor(Date.class,
new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
}
}
@Controller
class ProfileController {
// only runs for the "profile" model attribute
@InitBinder("profile")
void allowlist(WebDataBinder binder) {
binder.setAllowedFields("displayName", "bio");
}
@PostMapping("/profile")
String save(@ModelAttribute("profile") Profile p) { /* ... */ return "ok"; }
}go deeper
Know that @InitBinder lets you register a date format editor for form fields.
Enumerate the main knobs (editors, allowed/disallowed/required fields, validator) and local vs global scope.
Discuss per-request statefulness, attribute-targeted binders, and choosing allow-lists over deny-lists for over-posting.
Design a policy: global disallow baseline in ControllerAdvice plus per-controller allow-lists or dedicated DTOs, and reason about ordering and blast radius.
## WebDataBinder — the engine behind @ModelAttribute `WebDataBinder` (a subclass of `DataBinder`) is the component that takes the request's parameters (a `MutablePropertyValues`) and applies them to a target bean, converting each string to the property's declared type. It is used for `@ModelAttribute` binding and for the type conversion of `@RequestParam`, `@PathVariable`, `@RequestHeader`, etc. It is **not** used for `@RequestBody`. A **new WebDataBinder is created per request** and is stateful, so registering stateful helpers on it (like `PropertyEditor`s) is safe. ## @InitBinder — the customization hook An `@InitBinder`-annotated method in a `@Controller` or `@ControllerAdvice` is invoked **before** binding, receiving the `WebDataBinder` (and optionally the `WebRequest`, `Locale`, etc.). What you can do with it: ### 1. Register PropertyEditors (per-binding, stateful) ```java binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false)); ``` Useful for legacy or one-off string↔type conversions scoped to a controller. ### 2. Register Converters/Formatters You can add to the binding's `ConversionService` for stateless, thread-safe conversion — though for app-wide converters you'd usually register a global `FormattingConversionService` bean instead. ### 3. Restrict bindable fields (security) ```java binder.setAllowedFields("name", "email"); // whitelist binder.setDisallowedFields("admin", "roles"); // blacklist ``` This is the primary defense against **mass-assignment / over-posting**, where an attacker adds unexpected parameters (e.g. `?admin=true`) that would otherwise be bound. Allow-lists are preferred; disallow-list patterns are matched case-insensitively and support `*` wildcards. ### 4. Required fields ```java binder.setRequiredFields("email"); ``` Missing required fields produce a binding error. ### 5. Field markers and prefixes `setFieldMarkerPrefix` (default `_`) lets HTML forms reset unchecked checkboxes; `setFieldDefaultPrefix` (`!`) supplies defaults. ### 6. Attach a Validator ```java binder.setValidator(new UserValidator()); binder.addValidators(extraValidator); ``` (How/when validation *fires* is the Validation leaf's territory.) ## Scoping - **Local**: `@InitBinder` inside a `@Controller` applies to that controller only. - **Global**: `@InitBinder` inside a `@ControllerAdvice` applies to all controllers (subject to the advice's selectors). - **Attribute-targeted**: `@InitBinder("user")` runs only when the model attribute / parameter name is `user` (matched against `@ModelAttribute` names and simple parameter names). ## Gotchas - `@InitBinder` methods must return `void`. - They **do not** affect `@RequestBody` (Jackson) parsing. - A binder registered in `@ControllerAdvice` with `setAllowedFields` protects *every* `@ModelAttribute`, which can be surprising if a controller legitimately needs a broader field set — scope carefully. - Order: local `@InitBinder` runs after global advice binders for the same request.
- How do you make an @InitBinder apply only to one specific model attribute?Give the annotation the attribute name: @InitBinder("profile"). Spring runs it only when binding a @ModelAttribute (or parameter) whose name is 'profile'; other attributes use the default/other binders.
- You added setAllowedFields but a field still isn't binding — why might that be even without security in play?setAllowedFields only permits fields that pass; anything not listed is quietly ignored, which looks identical to a name mismatch. Also check that the parameter name exactly matches the property, that a setter exists, and that conversion succeeds.
saying these in an interview costs you the question
- Saying @InitBinder can customize @RequestBody/Jackson
- Thinking WebDataBinder is a singleton shared across requests
- Claiming @InitBinder methods return the binder
- Confusing setAllowedFields (bindable input) with @Valid validation