Best Practices for Data Validation in Spring Boot — A Beginner-Friendly Guide
When you're building a Spring Boot application, one thing you will quickly realize is that not every request coming from the client can be…
Best Practices for Data Validation in Spring Boot — A Beginner-Friendly Guide
When you're building a Spring Boot application, one thing you will quickly realize is that not every request coming from the client can be trusted.
A user might send:
{
"username": "",
"email": "hello",
"age": -5
}
Your application should not blindly accept this data.
That's where validation comes in.
In this article, we'll understand:
- Why validation is necessary
@Validvs@Validate- Important validation annotations
- Where validation should be placed
@NotNullvs@Column(nullable = false)- Commonly confused annotations
- A practical Spring Boot example
- Validation best practices for beginners

1. Why Do We Need Validation?
Imagine you have a registration API:
POST /users
The client sends:
{
"username": "",
"email": "abc",
"age": -10
}
Without validation, your application might continue processing this request and eventually save invalid data to the database.
Validation allows us to reject invalid input before it reaches our business logic or database.
For example:
@NotBlank
private String username;
@Email
private String email;
@Min(18)
private int age;
Now Spring Boot can automatically check whether the incoming data satisfies these rules.
Think of validation as a gate
Client Request
↓
Validation
↓
┌────┴────┐
Valid Invalid
↓ ↓
Service Error
↓
Database
This is much better than manually writing:
if (username == null || username.isEmpty()) {
// error
}
if (age < 18) {
// error
}
for every request.
2. Spring Boot Validation: The Basic Idea
Spring Boot uses Jakarta Bean Validation for declarative validation.
In a typical Spring Boot project, you add:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Then you can use annotations such as:
@NotBlank
@Email
@Size
@Min
@Max
@NotNull
@Positive
These annotations describe what is valid.
But there is an important distinction:
*@NotBlankand@Validand@Validatedhelp determine when validation is triggered.*
This distinction is one of the most important things to understand.
3. @Valid vs @Validated
This is probably the biggest source of confusion for beginners.
They look similar, but they are not exactly the same.
@Valid
@Valid comes from Jakarta:
import jakarta.validation.Valid;
It is commonly used in controllers to tell Spring:
"Validate this object using the constraints declared inside it."
Example:
@PostMapping
public ResponseEntity<String> createUser(
@Valid @RequestBody UserDto userDto) {
return ResponseEntity.ok("User is valid");
}
Suppose our DTO is:
public class UserDto {
@NotBlank
private String username;
@Email
@NotBlank
private String email;
@Min(18)
private int age;
}
When the request arrives, @Valid tells Spring to check these annotations.
Without @Valid
public ResponseEntity<String> createUser(
@RequestBody UserDto userDto)
The constraints inside UserDto will not automatically be triggered for this request body.
With @Valid
public ResponseEntity<String> createUser(
@Valid @RequestBody UserDto userDto)
Spring validates the DTO before entering your controller method.
4. What is @Validated?
@Validated is a Spring-specific annotation:
import org.springframework.validation.annotation.Validated;
It is particularly useful for method-level validation and validation groups.
For example:
@Service
@Validated
public class UserService {
public void updateEmail(
@NotNull Long id,
@Email @NotBlank String newEmail) {
// business logic
}
}
Here, @Validated on the service class enables method parameter validation.
If someone calls:
updateEmail(null, "wrong-email");
the validation mechanism can detect the invalid arguments.
5. So What Is the Actual Difference?
A simple way to remember it:
@Valid@ValidatedJakarta Bean Validation annotationSpring annotationCommonly used with request DTOsCommonly used for method validationCascades validation into an objectCan activate method validationDoes not select validation groupsSupports validation groupsVery common in controllersCommonly used at service/class level
Beginner rule
For normal request DTO validation:
@Valid @RequestBody UserDto dto
For method parameter validation in a Spring-managed service:
@Service
@Validated
public class UserService {
public void method(@NotNull Long id) {
}
}
Don't think of them as competitors.
They solve different parts of the validation problem.
6. Important Validation Annotations
Let's divide them into categories.
String Validation
@NotNull
Checks that the value is not null.
@NotNull
private String username;
But this is valid:
username = ""
because an empty string is not null.
@NotEmpty
Checks that the value is not null and has at least one element/character.
@NotEmpty
private String username;
This rejects:
null
""
But it does not reject a string containing only whitespace:
" "
@NotBlank
This is usually the better choice for required text fields.
@NotBlank
private String username;
It rejects:
null
""
" "
Easy way to remember
@NotNull
↓
must exist
@NotEmpty
↓
must contain something
@NotBlank
↓
must contain meaningful text
For most required String fields, @NotBlank is a great default.
7. @Size
Used to specify length or collection-size limits.
@Size(min = 3, max = 20)
private String username;
This means:
3 ≤ username length ≤ 20
Important:
@Size does not mean "required."
For example:
@Size(min = 3, max = 20)
private String username;
does not reject null.
If the field is mandatory, you may need:
@NotBlank
@Size(min = 3, max = 20)
private String username;
8. @Email
Used to validate email format.
@Email
private String email;
One important detail:
@Email by itself does not mean the field is required.
Therefore, if the email must exist:
@NotBlank
@Email
private String email;
This is a very common combination.
9. @Pattern
Used when you need a custom regular expression.
For example:
@Pattern(
regexp = "^[0-9]{10}$",
message = "Phone number must contain 10 digits"
)
private String phoneNumber;
This can be useful for:
- phone numbers
- PIN codes
- usernames
- custom IDs
- specific formats
Don't use @Pattern when a standard annotation already expresses the rule clearly.
10. Numeric Validation
@Min
@Min(18)
private int age;
Means:
age >= 18
@Max
@Max(100)
private int age;
Means:
age <= 100
@Positive
@Positive
private int quantity;
Means:
quantity > 0
@PositiveOrZero
@PositiveOrZero
private int quantity;
Means:
quantity >= 0
Similarly:
@Negative
@NegativeOrZero
can be used for negative numbers.
11. A Common Confusion: @Min vs @Positive
Consider:
@Min(1)
private int quantity;
and:
@Positive
private int quantity;
For many integer use cases, both effectively enforce:
quantity >= 1
But they communicate slightly different intentions.
@Positive says:
The value must be positive.
@Min(5) says:
The value must be at least 5.
So use the annotation that expresses your business rule most clearly.
12. Date Validation
For date/time fields, you can use:
@Past
The date must be in the past.
@PastOrPresent
The date can be in the past or present.
@Future
The date must be in the future.
@FutureOrPresent
The date can be today or in the future.
For example:
@Past
private LocalDate dateOfBirth;
A person's date of birth should normally be in the past.
13. Boolean Validation
You can also validate boolean values.
@AssertTrue
private boolean termsAccepted;
This can be useful when the user must explicitly accept something:
@AssertTrue(message = "You must accept the terms")
private boolean termsAccepted;
14. Where Should Validation Be Placed?
This is where beginners often put everything inside the Entity.
A better mental model is:
Controller
↓
DTO validation
↓
Service
↓
Business validation
↓
Repository
↓
Database constraints
Different layers have different responsibilities.
15. Controller Layer: Validate Incoming Requests
Suppose we have:
public class UserDto {
@NotBlank(message = "Username is required")
private String username;
@NotBlank(message = "Email is required")
@Email(message = "Invalid email")
private String email;
@Min(value = 18, message = "Age must be at least 18")
private int age;
}
Our controller:
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public ResponseEntity<String> createUser(
@Valid @RequestBody UserDto userDto) {
return ResponseEntity.ok("User created");
}
}
The flow is:
HTTP Request
↓
@RequestBody
↓
@Valid
↓
UserDto constraints
↓
Valid? ─── No ──→ Validation Error
│
Yes
↓
Controller logic
This is why DTO validation is generally a good practice.
16. Why Not Put Everything in the Entity?
You might see:
@Entity
public class User {
@NotBlank
private String username;
@Email
private String email;
}
This can work, but using entities directly as API request objects can create unnecessary coupling between:
API contract
↕
Database model
A DTO allows you to keep these responsibilities separate.
For example:
UserRequestDto
↓
Controller
↓
UserService
↓
User Entity
↓
Repository
The DTO represents what the API accepts.
The Entity represents how your application persists the data.
17. Entity Validation: @NotNull vs @Column(nullable = false)
This is another extremely important distinction.
Consider:
@NotNull
@Column(nullable = false)
private String email;
They may look like they do the same thing.
They don't.
@NotNull
This is a Bean Validation constraint.
It says:
"This Java value must not be null when validation is triggered."
Example:
@NotNull
private String email;
Validation happens at the application/validation layer.
@Column(nullable = false)
This is a JPA mapping configuration.
@Column(nullable = false)
private String email;
It communicates that the corresponding database column should not allow NULL values, particularly when schema generation is involved.
So:
@NotNull
↓
Application validation
@Column(nullable = false)
↓
Database/schema constraint
Best practice?
For important mandatory fields, you may want both:
@NotNull
@Column(nullable = false)
private String email;
Why?
Because application validation and database constraints provide different layers of protection.
18. Another Important Confusion: @NotNull on Primitive Types
Consider:
@NotNull
private int age;
This doesn’t make much sense because a primitive int can never be null.
It always has a value.
Instead, if you need to distinguish between:
age not provided
and:
age = 0
use:
@NotNull
private Integer age;
This is because Integer can be:
null
18
25
while int cannot be null.
This small difference becomes very important when designing DTOs.
19. Validation Does Not Replace Business Logic
This is another important concept.
Validation:
@Min(18)
private Integer age;
can tell us:
age must be at least 18.
But suppose our business rule is:
A user can only purchase this product if they have sufficient balance.
That’s not simply a Bean Validation constraint.
It belongs in the service/business layer.
For example:
if (user.getBalance() < product.getPrice()) {
throw new InsufficientBalanceException();
}
So think:
Validation
↓
"Is this input structurally valid?"
Business Logic
↓
"Is this operation allowed?"
20. A Practical Example
Let’s put everything together.
DTO
public class UserRequest {
@NotBlank(message = "Username is required")
@Size(min = 3, max = 20,
message = "Username must be between 3 and 20 characters")
private String username;
@NotBlank(message = "Email is required")
@Email(message = "Invalid email format")
private String email;
@NotNull(message = "Age is required")
@Min(value = 18, message = "Age must be at least 18")
private Integer age;
@AssertTrue(message = "You must accept the terms")
private boolean termsAccepted;
// getters and setters
}
Controller
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public ResponseEntity<String> createUser(
@Valid @RequestBody UserRequest request) {
return ResponseEntity.ok("User created successfully");
}
}
Now this request:
{
"username": "",
"email": "hello",
"age": 15,
"termsAccepted": false
}
will fail validation.
The service doesn’t have to manually check every field.
21. What Happens When Validation Fails?
Suppose we send:
{
"username": "",
"email": "wrong",
"age": 15
}
Spring detects the violations and typically raises an exception such as:
MethodArgumentNotValidException
for a validated request body.
In a real production application, you usually don’t want to return a huge framework-generated error response.
Instead, you can create a global exception handler using:
@RestControllerAdvice
and return a clean response such as:
{
"username": "Username is required",
"email": "Invalid email format",
"age": "Age must be at least 18"
}
This makes your API much easier for frontend developers and API consumers to use.
22. Quick Cheat Sheet
Required String
@NotBlank
private String name;
Required object/value
@NotNull
private Integer age;
@NotBlank
@Email
private String email;
String length
@Size(min = 3, max = 20)
private String username;
Number range
@Min(18)
@Max(100)
private Integer age;
Positive number
@Positive
private Integer quantity;
Custom format
@Pattern(regexp = "...")
private String value;
Past date
@Past
private LocalDate dateOfBirth;
Future date
@Future
private LocalDate appointmentDate;
Must be true
@AssertTrue
private boolean accepted;
Trigger DTO validation
@Valid
@RequestBody UserRequest request
Method parameter validation
@Service
@Validated
public class UserService {
public void method(@NotNull Long id) {
}
}
23. The Most Important Things to Remember
If you’re learning Spring Boot, don’t try to memorize every annotation immediately.
First understand these:
1. @NotBlank
For required strings.
2. @NotNull
For required objects/values.
3. @Email
For email format.
Usually:
@NotBlank
@Email
for a required email.
4. @Size
For length/size.
5. @Min, @Max
For numeric ranges.
6. @Valid
Usually used when you want Spring to validate an object’s fields, especially a request DTO.
7. @Validated
Useful for Spring method validation and validation groups.
8. @NotNull ≠ @Column(nullable = false)
One is application-level Bean Validation; the other is a database/JPA mapping constraint.
Final Mental Model
Don’t think of validation as:
“Which annotation should I memorize?”
Think of it as layers:
CLIENT
↓
┌─────────────────┐
│ CONTROLLER │
│ │
│ @Valid DTO │
│ @NotBlank │
│ @Email │
│ @Size │
└────────┬────────┘
↓
┌─────────────────┐
│ SERVICE │
│ │
│ @Validated │
│ Method checks │
│ Business rules │
└────────┬────────┘
↓
┌─────────────────┐
│ REPOSITORY │
└────────┬────────┘
↓
┌─────────────────┐
│ DATABASE │
│ │
│ NOT NULL │
│ UNIQUE │
│ FK constraints │
└─────────────────┘
The key idea is:
Validation annotations define the rules.
@Valid/@Validatedhelp trigger validation. Database constraints provide another line of defense. Business rules belong in the service layer.
Once you understand this separation, Spring Boot validation becomes much less confusing — and you’ll start seeing why production applications don’t simply put every validation annotation on the Entity and call it done.
메타데이터
- post_id
- 01e2879d07df
- slug
- best-practices-for-data-validation-in-spring-boot-a-beginner-friendly-guide-01e2879d07df
- url
- https://medium.com/@tanishasuyal43/best-practices-for-data-validation-in-spring-boot-a-beginner-friendly-guide-01e2879d07df
- canonical_url
- https://medium.com/@tanishasuyal43/best-practices-for-data-validation-in-spring-boot-a-beginner-friendly-guide-01e2879d07df
- author_url
- https://medium.com/@tanishasuyal43
- status
- ok
- fetched_at
- 2026-09-08 02:17:38