skip to content

What do setAllowedFields, setDisallowedFields and setRequiredFields do on a DataBinder, and why do they matter for security?

level: seniorimportance: must knowfreq 50%

answer

  1. allow-list vs deny-list; disallowed wins
  2. mass assignment / over-posting defense
  3. empty allowed = unrestricted (not deny-all)
  4. required → FieldError code 'required'
  5. getSuppressedFields(); set in @InitBinder

basics

~10 s

setAllowedFields restricts which property names may be bound (an allow-list); setDisallowedFields blocks specific names; setRequiredFields marks names that must be present or a required error is recorded. Allow-lists prevent mass-assignment attacks.

solid answer

~40 s

On DataBinder/WebDataBinder, setAllowedFields(String...) defines an allow-list of property names that are permitted to bind — anything else is skipped and recorded via getBindingResult().getSuppressedFields(). setDisallowedFields(String...) is a deny-list; if both are set, disallowed wins. Both support simple patterns like 'user.*', '*Id', '*name*' and are case-insensitive by default. setRequiredFields(String...) marks names that MUST be supplied; if a required value is absent or empty, the binder records a 'required' FieldError via its BindingErrorProcessor. The security point is mass assignment (over-posting): if you bind an untrusted request straight onto a persistent entity, an attacker can set fields you never exposed on the form — e.g. role or isAdmin. An allow-list of exactly the editable fields, or better a dedicated DTO, prevents that. In MVC you set these in an @InitBinder method.

code

java · 21 lines
java
@Controller
public class ProfileController {

    @InitBinder("user")
    protected void initBinder(WebDataBinder binder) {
        // Only these may be bound; a malicious role=ADMIN is dropped and
        // recorded in bindingResult.getSuppressedFields():
        binder.setAllowedFields("firstName", "lastName", "email");
        binder.setRequiredFields("email");
    }

    @PostMapping("/profile")
    public String update(@ModelAttribute("user") User user, BindingResult result) {
        if (result.hasErrors()) {
            // e.g. missing 'email' -> FieldError with code "required"
            return "profileForm";
        }
        service.update(user);
        return "redirect:/profile";
    }
}

go deeper

for a junior

Know allowedFields limits which fields can be set and required marks mandatory ones.

for a middle

Explain deny-list precedence, patterns, and where required errors show up.

for a senior

Articulate the mass-assignment threat and defend with DTOs plus @InitBinder allow-lists; know suppressedFields and the empty-list semantics.

for a principal

Set org-wide policy: DTO-per-endpoint, @ControllerAdvice global disallow of dangerous paths, fail-safe allow-lists, and separation of binding control from validation.

## The three controls All live on `DataBinder` (and its web subclass `WebDataBinder`): ### setAllowedFields(String... allowed) — allow-list Only the named properties may be bound. Any inbound value whose name isn't allowed is **not bound** and is captured in `bindingResult.getSuppressedFields()` (so you can audit what was dropped). An empty/null allow-list means *no restriction* (everything allowed) — a common misconception is that empty means 'nothing allowed'. ### setDisallowedFields(String... disallowed) — deny-list The named properties are explicitly rejected. **Precedence:** if a field is in both lists, disallowed wins. As of recent Spring, some binding paths default to disallowing dangerous property paths (e.g. `class.*` / classloader access) to blunt known attacks. ### setRequiredFields(String... required) Names that MUST appear in the incoming `PropertyValues` with a non-empty value. Missing ones cause the `BindingErrorProcessor` (default `DefaultBindingErrorProcessor`) to register a `FieldError` with error code **"required"**, which you resolve to a message via the standard code resolution. ## Pattern matching Allowed/disallowed names support Spring's simple `PathMatcher`-style patterns: - `"user.*"` — all direct sub-properties of user. - `"*Id"` — any name ending in Id. - `"*name*"` — any name containing name. Matching is **case-insensitive** by default (there is a switch for case sensitivity). Nested paths are matched against the full path. ## The security problem: mass assignment / over-posting Data binding matches *whatever the client sends* to properties on the target. If the target is a persistent entity (`@Entity User`) with fields like `role`, `enabled`, `accountBalance`, and you do: ```java @PostMapping("/profile") public String update(@ModelAttribute User user) { userRepo.save(user); ... } ``` an attacker can POST `role=ADMIN&enabled=true` even though the form never showed those fields — this is **mass assignment** (a.k.a. over-posting / autobinding vulnerability). ### Mitigations, best first 1. **Bind to a purpose-built DTO/command object** exposing only editable fields, then map to the entity in code. This is the cleanest defense — the object has no dangerous properties to set. 2. **Allow-list** the editable fields via `setAllowedFields("firstName","lastName","email")` in `@InitBinder`. 3. Deny-list sensitive fields (weaker: easy to forget one; allow-list fails safe, deny-list fails open). ## Where to configure (MVC) ```java @InitBinder protected void initBinder(WebDataBinder binder) { binder.setAllowedFields("firstName", "lastName", "email"); binder.setRequiredFields("email"); } ``` Applied per controller (or globally via a `@ControllerAdvice` `@InitBinder`). ## Interaction with error handling - Suppressed (not-allowed) fields → `getSuppressedFields()`, **no error** by default; they're just silently ignored (optionally logged). - Missing required fields → recorded `FieldError` (code `required`), surfaced in the `BindingResult` alongside type mismatches. ## Gotchas - Empty allowed list = unrestricted, not 'deny all'. - Deny-list precedence over allow-list. - Allow-lists don't remove the need for validation (`@Valid`) — they control *which* fields bind, not whether values are *valid*.

  • If a field is listed in both setAllowedFields and setDisallowedFields, what happens?
    It is disallowed — the deny-list takes precedence over the allow-list, so the field is not bound.
  • Is an allow-list better or worse than binding to a dedicated DTO?
    A DTO is generally better: it has no sensitive properties to over-post in the first place, so it fails safe by construction. An allow-list is a good second line, but you must remember to keep it in sync; a DTO removes the whole attack surface.
  • Does setAllowedFields validate values?
    No. It only controls which property names are permitted to bind. Value validity (e.g. email format, ranges) is separate and handled by @Valid / a Validator.

saying these in an interview costs you the question

  • Thinking an empty allowedFields list means 'bind nothing' (it means unrestricted)
  • Binding untrusted requests directly onto @Entity objects with no allow-list or DTO (mass assignment)
  • Believing allow-list wins over deny-list
  • Confusing field allow-listing with value validation

context