Validating Path & Query Params
Request bodies aren’t the only untrusted input — query parameters and path variables need validation too. These are simple scalars rather than objects, so they’re validated differently: you put constraints directly on the parameters and opt the controller into method-level validation with Spring’s @Validated. This page also covers validating @ConfigurationProperties.
@Validated on the controller class
Method-parameter validation is off by default. Enable it by annotating the class with Spring’s @Validated (from org.springframework.validation.annotation). Then put constraint annotations directly on the @RequestParam / @PathVariable arguments.
import jakarta.validation.constraints.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/products")
@Validated // enables param-level validation
public class ProductController {
@GetMapping
public List<Product> list(
@RequestParam @Min(0) int page,
@RequestParam @Min(1) @Max(100) int size,
@RequestParam(required = false) @Size(max = 50) String search) {
return service.find(page, size, search);
}
@GetMapping("/{id}")
public Product one(@PathVariable @Positive Long id) {
return service.findById(id);
}
}
Warning: Without
@Validatedon the class, the constraints on@RequestParam/@PathVariableare silently ignored. This is the single most common reason param validation “doesn’t work.” The annotation must be on the class (or a@Configuration-registered bean), not the method.
ConstraintViolationException
When a parameter constraint fails, Spring throws ConstraintViolationException (from jakarta.validation) — a different exception than the MethodArgumentNotValidException used for request bodies.
curl "http://localhost:8080/api/products?page=-1&size=999"
By default this surfaces as 500 Internal Server Error, which is wrong — bad client input should be a 400. Map it explicitly in a @RestControllerAdvice:
import jakarta.validation.ConstraintViolationException;
@RestControllerAdvice
public class ParamValidationHandler {
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> onConstraintViolation(ConstraintViolationException ex) {
Map<String, String> errors = new HashMap<>();
ex.getConstraintViolations().forEach(v -> {
String field = v.getPropertyPath().toString(); // e.g. "list.size"
errors.put(field, v.getMessage());
});
return errors;
}
}
Output:
{
"list.page": "must be greater than or equal to 0",
"list.size": "must be less than or equal to 100"
}
Note the property path includes the method name (list.size) rather than a clean field name. Trim it if you want friendlier output. The full handling strategy lives in Handling Validation Errors.
Body vs param validation — at a glance
| Aspect | @RequestBody | @RequestParam / @PathVariable |
|---|---|---|
| Trigger annotation | @Valid on the parameter | @Validated on the class |
| Where constraints go | on DTO fields | directly on the method parameter |
| Exception on failure | MethodArgumentNotValidException | ConstraintViolationException |
| Default status | 400 | 500 (until you map it) |
| Error detail | BindingResult / FieldError | Set<ConstraintViolation> |
Validating @ConfigurationProperties
Validation isn’t limited to web requests. You can validate externalized configuration at startup so the application fails fast on a bad application.yml. Annotate the properties class with @Validated and add constraints to its fields.
import jakarta.validation.constraints.*;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties(prefix = "app.mail")
@Validated
public record MailProperties(
@NotBlank String host,
@Min(1) @Max(65535) int port,
@Email String fromAddress) {}
app:
mail:
host: smtp.example.com
port: 587
fromAddress: [email protected]
If a value is missing or invalid, the application context fails to start with a clear message:
***************************
APPLICATION FAILED TO START
***************************
Binding to target ... failed:
Property: app.mail.port
Value: "70000"
Reason: must be less than or equal to 65535
Tip: Failing fast at startup is far safer than discovering a misconfiguration at runtime. Pair validated
@ConfigurationPropertieswith profiles to enforce per-environment configuration contracts. See also Type-safe Configuration Properties.
Validating @RequestHeader and other params
The same rules apply to any method parameter resolved by Spring MVC. With @Validated on the class you can constrain @RequestHeader, @CookieValue, and @MatrixVariable:
@GetMapping("/feed")
public Feed feed(@RequestHeader("X-Api-Version") @Pattern(regexp = "v[12]") String version) {
return service.feed(version);
}