Tutorials Logic, IN info@tutorialslogic.com

Hibernate Validation @NotNull, @Size, @Email

Validation Lifecycle

Hibernate Validation focuses on data quality before invalid state reaches persistence. The unique problem is Bean Validation constraints, custom validators, groups, lifecycle timing, and error messages.

This page should teach validation gates, not Hibernate setup. Request DTOs, entities, and database constraints each protect a different layer.

Validation can run on incoming request DTOs, before entity insert/update, or through an explicit Validator call. The timing determines which errors users see and which rules protect persistence.

  • Use @Valid on request DTOs.
  • Use entity constraints for domain persistence rules.
  • Keep database constraints as the final guard.

Built-in and Custom Constraints

Built-in annotations cover common rules. Custom constraints express rules that belong to the domain, such as strong passwords, SKU format, or age eligibility.

  • Use @NotBlank for required text.
  • Use @Email for email shape.
  • Create ConstraintValidator for domain-specific checks.

Groups and Error Design

Create and update workflows may require different rules. Validation groups handle these differences, but too many groups make models hard to reason about.

  • Use groups for draft vs publish or create vs update.
  • Return field-level messages.
  • Do not expose raw exception internals to users.

Bean Validation (Jakarta Validation)

Hibernate Validator is the reference implementation of Jakarta Bean Validation (formerly javax.validation). It allows you to declare constraints directly on entity fields using annotations. Spring Boot includes it automatically via spring-boot-starter-validation.

Common Validation Annotations on Entity

Common Validation Annotations on Entity
package com.example.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;

@Entity
@Table(name = "users")
public class User {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull(message = "Name is required")
    @NotBlank(message = "Name cannot be blank")
    @Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")
    private String name;

    @NotNull(message = "Email is required")
    @Email(message = "Email must be a valid email address")
    @Column(unique = true)
    private String email;

    @NotNull(message = "Password is required")
    @Size(min = 8, message = "Password must be at least 8 characters")
    @Pattern(regexp = "^(?=.*[A-Z])(?=.*[0-9]).+$",
             message = "Password must contain at least one uppercase letter and one digit")
    private String password;

    @Min(value = 18, message = "Age must be at least 18")
    @Max(value = 120, message = "Age must be at most 120")
    private Integer age;

    @DecimalMin(value = "0.0", inclusive = false, message = "Salary must be positive")
    @DecimalMax(value = "999999.99", message = "Salary exceeds maximum")
    @Digits(integer = 6, fraction = 2, message = "Invalid salary format")
    private Double salary;

    @NotNull
    @Positive(message = "Score must be positive")
    private Integer score;

    @Past(message = "Birth date must be in the past")
    private java.time.LocalDate birthDate;

    @Future(message = "Expiry date must be in the future")
    private java.time.LocalDate expiryDate;

    @AssertTrue(message = "Terms must be accepted")
    private boolean termsAccepted;

    // getters/setters...
}

Custom Constraint Annotation

Custom Validation Constraint

Custom Validation Constraint
package com.example.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;

// Step 1: Define the annotation
@Documented
@Constraint(validatedBy = UsernameValidator.class) // Link to validator class
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidUsername {

    String message() default "Username must be 3-20 chars, alphanumeric and underscores only";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

// Usage on entity field:
// @ValidUsername
// private String username;

Custom Constraint Annotation - Java Example

Custom Constraint Annotation - Java Example
package com.example.validation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

// Step 2: Implement the validator logic
public class UsernameValidator
        implements ConstraintValidator<ValidUsername, String> {

    private static final String USERNAME_PATTERN = "^[a-zA-Z0-9_]{3,20}$";

    @Override
    public void initialize(ValidUsername annotation) {
        // Called once before validation - can read annotation attributes here
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) return true; // Let @NotNull handle null check

        if (!value.matches(USERNAME_PATTERN)) {
            // Optionally customize the error message
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(
                    "Username '" + value + "' is invalid. Use 3-20 alphanumeric chars.")
                   .addConstraintViolation();
            return false;
        }
        return true;
    }
}

@Valid in Spring MVC Controllers

@Valid in REST Controller with Error Handling

@Valid in REST Controller with Error Handling
package com.example.controller;

import com.example.model.User;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.validation.FieldError;
import java.util.*;

@RestController
@RequestMapping("/api/users")
public class UserController {

    // @Valid triggers Bean Validation on the @RequestBody
    // If validation fails, Spring throws MethodArgumentNotValidException
    @PostMapping
    public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
        // If we reach here, validation passed
        return ResponseEntity.status(201).body(userService.save(user));
    }

    // Global exception handler for validation errors
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationErrors(
            MethodArgumentNotValidException ex) {

        Map<String, String> errors = new LinkedHashMap<>();

        // Collect all field errors
        ex.getBindingResult().getAllErrors().forEach(error -> {
            String field   = ((FieldError) error).getField();
            String message = error.getDefaultMessage();
            errors.put(field, message);
        });

        return ResponseEntity.badRequest().body(errors);
    }
}

// Example error response for invalid input:
// {
//   "name": "Name must be between 2 and 50 characters",
//   "email": "Email must be a valid email address",
//   "age": "Age must be at least 18"
// }

Registration DTO Validation

Registration DTO Validation
public class RegistrationRequest {
    @NotBlank @Size(min = 3, max = 40)
    private String username;

    @Email @NotBlank
    private String email;

    @StrongPassword
    private String password;
}
Before you move on

Hibernate Validation @NotNull, @Size, @Email Mastery Check

5 checks
  • Validation can run on incoming request DTOs, before entity insert/update, or through an explicit Validator call.
  • The timing determines which errors users see and which rules protect persistence.
  • Custom constraints express rules that belong to the domain, such as strong passwords, SKU format, or age eligibility.
  • Create and update workflows may require different rules.
  • Validation groups handle these differences, but too many groups make models hard to reason about.

Hibernate Questions Learners Ask

Usually, yes. A request DTO exposes only the fields that clients may change, while the entity keeps persistence details such as IDs and relationships private.

@NotNull is application-level validation and can be skipped by another service, bulk SQL, disabled validation, or direct database access. A database NOT NULL constraint protects every write path. Use both when the rule is fundamental: validation gives earlier, friendlier feedback, while the database guarantees integrity.

Groups help when the same model has different rules for create, update, publish, or administrative operations.

Next Step
Next Practice

Finish the concept here, then reinforce it with hands-on coding, interview prep, or a tool that matches the topic.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.