When writing validation in Spring Boot, @Valid is usually the first thing you reach for. But once you start handling slightly more complex input checks in real-world projects, you’ll quickly run into requirements like “I want different required fields for create vs. update” or “I want method arguments in the Service layer validated automatically, not just in the Controller.”
That’s where Spring’s @Validated annotation comes in. It builds on @Valid while making group validation and method-level validation straightforward to work with.
What Is @Validated?
@Validated is a Spring-provided validation annotation (org.springframework.validation.annotation.Validated). Like @Valid, its role is to trigger validation on a target — but @Validated has one key distinguishing feature: it lets you specify validation groups.
Additionally, when you place @Validated on a class in Spring, it enables automatic validation of that Bean’s method arguments and return values (method validation).
Differences Between @Valid and @Validated
@Valid and @Validated are similar, but each has its strengths.
@Valid(Jakarta standard)- Simple — used to “trigger” validation on a DTO
- Well-suited for validating
@RequestBodyin a Controller, or recursively validating nested objects
@Validated(Spring-provided)- Supports group validation (e.g., different rules for create vs. update)
- Well-suited for validating method arguments and return values in the Service layer
When in doubt, use this mental model:
- “I want to validate a DTO normally in a Controller” → start with
@Valid - “I want to switch validation rules depending on the use case” →
@Validated - “I want validation enforced at Service layer method boundaries” → put
@Validatedon the class
Using Group Validation to Separate Create and Update Rules
Consider a case where you want different required fields for “create user” vs. “update user.”
Define Interfaces for Groups
Groups are just markers, so empty interfaces are fine.
public interface OnCreate {}
public interface OnUpdate {}
Use the groups Attribute in Your DTO to Split Constraints
The groups attribute lets you control which constraints are active in which scenario.
public class UserRequest {
@NotBlank(message = "名前は必須です", groups = {OnCreate.class, OnUpdate.class})
@Size(min = 2, max = 20, message = "名前は2〜20文字で入力してください", groups = {OnCreate.class, OnUpdate.class})
private String name;
@NotBlank(message = "メールアドレスは新規登録時に必須です", groups = OnCreate.class)
@Email(message = "メールアドレスの形式が正しくありません", groups = {OnCreate.class, OnUpdate.class})
private String email;
// getter/setter
}
The key here is that @Validated acts as the switch that determines which group to validate with.
Specify the Group in Your Controller with @Validated
Use OnCreate for the create endpoint and OnUpdate for the update endpoint.
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public String create(@RequestBody @Validated(OnCreate.class) UserRequest request) {
return "created";
}
@PutMapping("/{id}")
public String update(@PathVariable Long id, @RequestBody @Validated(OnUpdate.class) UserRequest request) {
return "updated";
}
}
Since @Valid does not support group specification, this switching behavior is squarely in @Validated’s domain.
Enabling Method Validation in the Service Layer
Relying solely on Controller-level validation means checks can be missed when a Service is called through other paths — batch jobs, event handlers, another Controller, etc. This is where method validation shines.
Add @Validated to the Class
@Service
@Validated
public class UserService {
public void register(@NotBlank(message = "名前は必須です") String name,
@Email(message = "メール形式が不正です") String email) {
// registration logic
}
}
With this in place, an exception is thrown whenever invalid values are passed to register(). Because the check happens at the method boundary, safety is preserved no matter how many callers you add.
It Also Works with DTO Arguments
@Service
@Validated
public class UserService {
public void register(@Valid UserRequest request) {
// validated according to the constraint annotations in the DTO
}
}
This can be a bit confusing, but the mental model is: @Validated on the class is the switch that enables method validation, while @Valid on the parameter is the signal to recursively validate the DTO’s fields.
Return Values Can Be Validated Too
You can also constrain return values — useful when you want to enforce a contract like “this method must never return blank.”
@Service
@Validated
public class TokenService {
public @NotBlank(message = "トークンが空です") String issueToken(@NotBlank String userId) {
return "token";
}
}
Exception Types and How to Handle Them
With @Validated, the exception thrown depends on where validation failed. The two you’ll most commonly encounter are:
| Where validation failed | Common exception | Typical scenario |
|---|---|---|
DTO validation via @RequestBody | MethodArgumentNotValidException | JSON → DTO validation (with either @Validated or @Valid) |
| Method validation | ConstraintViolationException | Service method arguments/return values, @RequestParam or @PathVariable constraints, etc. |
Handling Both with a ControllerAdvice
Here’s a minimal example that catches errors and returns them as messages. Adjust the response format to fit your project.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
var errors = ex.getBindingResult().getFieldErrors().stream()
.map(err -> err.getField() + ": " + err.getDefaultMessage())
.toList();
return Map.of(
"type", "validation_error",
"errors", errors
);
}
@ExceptionHandler(ConstraintViolationException.class)
public Map<String, Object> handleConstraintViolation(ConstraintViolationException ex) {
var errors = ex.getConstraintViolations().stream()
.map(v -> v.getPropertyPath() + ": " + v.getMessage())
.toList();
return Map.of(
"type", "constraint_violation",
"errors", errors
);
}
}
Common Pitfalls with @Validated
Method Validation Only Fires When Called Through a Spring-Managed Bean
Method validation works by intercepting calls via Spring’s proxy mechanism. This means calling this.register(...) from within the same class may bypass validation entirely.
- OK: Controller → Service (Spring-managed Bean) call
- Watch out: Service method A → Service method B on the same instance (self-invocation)
The safe design pattern is to ensure the “boundary you want validated” (your public API) is always called from outside the Service.
When You Specify a Group, Only Constraints in That Group Are Active
When validating with @Validated(OnCreate.class), constraints that have no groups specified (the default group) may not run. This is useful when you intentionally want to separate concerns, but it’s an easy place to get tripped up with a “why isn’t @NotBlank firing?” moment.
Once you start using groups, it’s best to adopt a consistent policy of adding groups to all constraints in your DTOs to avoid confusion.
Controlling Validation Order with Group Sequence
When you want heavier validation to run only after simpler checks pass, @GroupSequence is the right tool.
public interface BasicChecks {}
public interface BusinessChecks {}
@GroupSequence({BasicChecks.class, BusinessChecks.class})
public interface OrderedChecks {}
Validating in this sequence means that if BasicChecks fails, subsequent groups are skipped.
This keeps error messages clean and avoids unnecessary processing.
Standardizing Your Response Format
When @Validated is used in both Controllers and Services, the differing exception types can cause inconsistent response formats.
For API operations, adopting the following conventions keeps things manageable:
- Unify the response format regardless of exception type
- Use
codeanderrorsas fixed keys - Include
path(field name) andmessage(description) insideerrors
This alone significantly reduces implementation overhead for frontend teams and other service integrations.
Summary
@Validated is the annotation to reach for when you want to take validation in Spring Boot to the next level. In particular, group validation for switching rules between create and update operations, and method validation for enforcing checks at Service layer boundaries, are both highly effective in real-world projects.
A good approach is to start by introducing group switching in your Controllers, then extend @Validated to the Service layer as needed — building toward a robust design without overcomplicating things all at once.
Try mixing @Valid and @Validated in a way that fits your project’s scale and operational needs.