Safeguard
Application Security

A guide to input validation with Spring Boot

Spring Boot doesn't validate input by default. Here's how Bean Validation actually works, where teams get it wrong, and how missing validation leads to injection and mass assignment.

Priya Mehta
DevSecOps Engineer
6 min read

Spring Boot does not validate anything by default. Drop @RequestBody OrderRequest order into a controller method without @Valid, and Hibernate Validator never runs — a negative quantity, a 10,000-character "email" field, or a nested object with null required fields will sail straight into your service layer. This gap is not theoretical: Spring Boot 2.3.0 (May 2020) removed Bean Validation from the default spring-boot-starter-web dependency tree entirely, so teams that upgraded without re-adding spring-boot-starter-validation silently lost validation enforcement across their APIs. Combined with Spring's aggressive automatic data binding — the same binding mechanism at the center of the March 2022 Spring4Shell RCE (CVE-2022-22965) — input validation in Spring Boot sits at the intersection of correctness and exploitability. This guide covers how Bean Validation actually works, where teams get it wrong, and how to close the gaps that lead to injection, mass assignment, and deserialization bugs.

What is input validation in Spring Boot, and why does it matter for security?

Input validation in Spring Boot is primarily implemented through Jakarta Bean Validation (formerly javax.validation, standardized as JSR 380), a declarative annotation model that Hibernate Validator enforces at runtime. You annotate a DTO field with constraints like @NotBlank, @Size(max = 255), or @Pattern(regexp = ...), and Spring's @Valid or @Validated triggers validation before the value reaches your business logic. This matters for security because unvalidated input is the entry point for CWE-20 (Improper Input Validation), which underlies OWASP's A03:2021 Injection category — the third most common web application risk in the 2021 OWASP Top 10, appearing in 94% of applications tested. A field that accepts arbitrary length or content becomes the vector for SQL injection, XXE, path traversal, or resource-exhaustion denial of service. Validation doesn't replace parameterized queries or output encoding, but it's the first checkpoint that rejects malformed data before it reaches a database driver, file system call, or downstream service.

How do you implement input validation with Bean Validation annotations?

You implement it by adding spring-boot-starter-validation as an explicit dependency, annotating your DTO fields with constraint annotations, and placing @Valid on the controller parameter that receives them. As of Spring Boot 3.0 (November 2022), the underlying API moved from javax.validation.* to jakarta.validation.* as part of the Jakarta EE 9+ migration — a common source of "annotation not applying" bugs when teams mix old and new imports during upgrades. A typical DTO looks like:

public class SignupRequest {
    @NotBlank
    @Email
    private String email;

    @Size(min = 12, max = 128)
    private String password;

    @Min(13)
    private int age;
}
@PostMapping("/signup")
public ResponseEntity<?> signup(@Valid @RequestBody SignupRequest request) {
    // Only reached if all constraints pass
}

Without @Valid on the parameter, every one of these annotations is dead code — Spring silently skips validation, and the fields are enforced only if something downstream happens to check them again.

What security vulnerabilities result from missing or weak validation in Spring Boot apps?

Missing or weak validation in Spring Boot most commonly produces mass assignment, injection, and unrestricted resource consumption. Mass assignment (CWE-915) happens when a controller binds a full request body directly to a JPA entity instead of a purpose-built DTO — an attacker adds an unexpected field like "role": "ADMIN" or "isVerified": true to a signup payload, and Spring's binder happily sets it if the entity has a matching setter, because Bean Validation constrains value shape, not which fields are allowed to be set. Spring4Shell (CVE-2022-22965), disclosed March 30, 2022 and affecting Spring Framework 5.3.0–5.3.17 and 5.2.0–5.2.19 on JDK 9+, exploited this same class of issue at a deeper level: crafted request parameters manipulated the binder's ClassLoader property chain to achieve remote code execution on Tomcat deployments — a data-binding flaw, not a Bean Validation gap, but one input sanitization at the binder level would have blocked. Separately, CVE-2020-5398 showed how insufficiently validated content-type and path handling in Spring Framework enabled reflected file download attacks. The common thread: constraint annotations check format and range, but they don't restrict which fields can be bound or sanitize values used in file paths, OGNL/SpEL expressions, or raw SQL — those require explicit allow-listing, DTO projection, and parameterization on top of Bean Validation.

How do you validate nested objects, collections, and method-level parameters?

You validate nested objects by adding @Valid on the nested field itself, since Bean Validation does not cascade into child objects automatically. If OrderRequest contains a List<LineItem>, you need @Valid on that list field, and each LineItem class needs its own constraint annotations — omit the cascading @Valid and a LineItem with a negative price or a null SKU passes silently. For method parameters outside the request body — path variables, query parameters, or parameters on @Service-layer methods — Bean Validation requires @Validated at the class level (Spring's variant that enables AOP-based method validation) combined with inline constraints:

@RestController
@Validated
public class OrderController {

    @GetMapping("/orders/{id}")
    public Order getOrder(@PathVariable @Min(1) Long id) {
        // id validated before method body runs
    }
}

This class-level @Validated is easy to forget on internal service beans, which means a REST controller can be fully validated while the service method it calls — reachable from a message queue listener or scheduled job with no HTTP-layer validation at all — accepts anything.

How should you handle validation errors without leaking information?

You handle validation errors by catching MethodArgumentNotValidException (for @RequestBody) and ConstraintViolationException (for method-level validation) in a centralized @ControllerAdvice, and returning a structured, generic error response rather than the exception's default output. Spring Boot's default behavior on an unhandled validation failure returns a stack trace fragment and internal field names in the JSON error body when server.error.include-message and related properties aren't explicitly locked down — informative for an attacker probing your data model, harmful for you. A minimal secure handler:

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
    List<String> errors = ex.getBindingResult().getFieldErrors()
        .stream().map(FieldError::getField).toList();
    return ResponseEntity.badRequest().body(new ErrorResponse("VALIDATION_FAILED", errors));
}

Return field names that failed, not the values submitted or the constraint's internal message templates, and confirm server.error.include-stacktrace=never and server.error.include-exception=false are set in application.properties — both default to safer values in recent Spring Boot releases but are frequently overridden to on_param or true during debugging and left that way in production.

How Safeguard Helps

Safeguard maps input validation gaps to real exploitability rather than flagging every unvalidated field as equally urgent. Our reachability analysis traces whether an endpoint missing @Valid, a cascading validation gap on a nested DTO, or a mass-assignment-prone entity binding is actually invokable from an internet-facing route, so security teams triage the controller bound to /api/public/signup before the internal batch job nobody exposes. Griffin AI reviews the surrounding code path — binder configuration, exception handling, and downstream data flow — to confirm whether a missing constraint is a real injection or mass-assignment vector versus low-risk dead code. Safeguard also generates and ingests SBOMs to flag when you're running Spring Framework or Spring Boot versions affected by binding-related CVEs like Spring4Shell, and where a fix is mechanical — adding spring-boot-starter-validation, annotating a field, or upgrading a patched version — Safeguard opens an auto-fix PR so the remediation ships without a manual audit of every DTO in the codebase.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.