Validation and Errors

Back

Loading concept...

🛡️ Validation & Errors in Spring: The Bouncer at Your Data Club

Imagine you’re throwing a super fancy party. You hire a bouncer to make sure only guests with proper invitations get in. That bouncer? That’s validation in Spring!


🎯 What You’ll Learn

  • Validation Fundamentals – How Spring checks if data is “good enough” to enter
  • Custom Validators – Creating your own special rules
  • Exception Handling – What happens when bad data tries to sneak in

🚪 Part 1: Validation Fundamentals

The Story of the Picky Restaurant

Think of a fancy restaurant that only accepts reservations with:

  • A name (can’t be empty!)
  • A phone number (must be real!)
  • Number of guests (between 1 and 20)

Spring validation works the same way! Before data enters your app, Spring checks if it follows the rules.

Meet the Annotation Guards 🏷️

Spring uses special annotations (little tags) to set rules:

public class Reservation {

    @NotBlank(message = "Name is required!")
    private String guestName;

    @Email(message = "Please use a real email!")
    private String email;

    @Min(value = 1, message = "At least 1 guest!")
    @Max(value = 20, message = "Max 20 guests!")
    private int numberOfGuests;
}

🎨 Common Validation Annotations

Annotation What It Checks Example
@NotNull Not null “Something must exist here”
@NotBlank Not empty, not just spaces “Write something real!”
@Size Length between min and max Password: 8-20 characters
@Email Valid email format must have @ and domain
@Min / @Max Number limits Age: 0 to 150
@Pattern Matches a pattern Phone: only digits

How to Activate the Bouncer 🎬

In your controller, add @Valid before the data:

@PostMapping("/reserve")
public String makeReservation(
        @Valid @RequestBody Reservation res,
        BindingResult result) {

    if (result.hasErrors()) {
        // Uh oh! Something's wrong!
        return "Please fix your info!";
    }
    // All good! Process the reservation
    return "Table reserved!";
}
graph TD A["📥 User Sends Data"] --> B{🛡️ @Valid Check} B -->|✅ All Good| C["Process Request"] B -->|❌ Has Errors| D["BindingResult Catches Them"] D --> E["Return Error Messages"]

The BindingResult Detective 🔍

BindingResult is like a notepad that catches ALL the problems:

if (result.hasErrors()) {
    for (FieldError error : result.getFieldErrors()) {
        System.out.println(
            error.getField() + ": " +
            error.getDefaultMessage()
        );
    }
}

Output might be:

guestName: Name is required!
email: Please use a real email!

🔧 Part 2: Custom Validators

When Default Rules Aren’t Enough

Imagine you run a club that only allows people whose name starts with “VIP_”. The built-in rules can’t check that!

Time to create your own bouncer!

Step 1: Create the Annotation 🏷️

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = VIPValidator.class)
public @interface VIPName {

    String message() default
        "Name must start with VIP_";

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

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

Step 2: Create the Validator Logic 🧠

public class VIPValidator
    implements ConstraintValidator<VIPName, String> {

    @Override
    public boolean isValid(
            String value,
            ConstraintValidatorContext ctx) {

        if (value == null) {
            return false;
        }
        return value.startsWith("VIP_");
    }
}

Step 3: Use It! 🚀

public class ClubMember {

    @VIPName
    private String memberName;

    // Now only "VIP_John" works!
    // "John" gets rejected!
}
graph TD A["📝 Create @Annotation"] --> B["🧠 Write Validator Class"] B --> C["🔗 Link with @Constraint"] C --> D["✨ Use on Fields!"]

Real Example: Age Checker 👶➡️👴

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AdultValidator.class)
public @interface Adult {
    String message() default
        "Must be 18 or older!";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload()
        default {};
}

public class AdultValidator
    implements ConstraintValidator<Adult, Integer> {

    @Override
    public boolean isValid(
            Integer age,
            ConstraintValidatorContext ctx) {
        return age != null && age >= 18;
    }
}

🚨 Part 3: Exception Handling

When Things Go Wrong

Even with a bouncer, sometimes things break. Maybe:

  • The database is down 💔
  • Someone found a loophole 🕳️
  • An unexpected error happened 💥

Spring has special ways to catch these problems!

The @ExceptionHandler Hero 🦸

Put this in your controller to catch specific errors:

@RestController
public class ReservationController {

    @PostMapping("/reserve")
    public String reserve(@Valid @RequestBody
            Reservation res) {
        // ... your logic
    }

    @ExceptionHandler(
        MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>>
            handleValidationErrors(
            MethodArgumentNotValidException ex) {

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

        ex.getBindingResult()
          .getFieldErrors()
          .forEach(error ->
            errors.put(
                error.getField(),
                error.getDefaultMessage()
            ));

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

The @ControllerAdvice Guardian 🌍

Want to catch errors across ALL controllers? Use @ControllerAdvice:

@ControllerAdvice
public class GlobalErrorHandler {

    @ExceptionHandler(
        MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>>
            handleValidation(
            MethodArgumentNotValidException ex) {

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

        ex.getBindingResult()
          .getFieldErrors()
          .forEach(e ->
            errors.put(
                e.getField(),
                e.getDefaultMessage()
            ));

        return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(errors);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleAll(
            Exception ex) {
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body("Oops! Something went wrong!");
    }
}
graph TD A["❌ Error Occurs"] --> B{Where to Handle?} B -->|Single Controller| C["@ExceptionHandler"] B -->|All Controllers| D["@ControllerAdvice"] C --> E["📤 Return Nice Error"] D --> E

HTTP Status Codes You’ll Use 📊

Code Meaning When to Use
400 Bad Request Validation failed
404 Not Found Item doesn’t exist
500 Server Error Something broke!

Custom Exception Example 🎯

// 1. Create your exception
public class ReservationNotFoundException
        extends RuntimeException {

    public ReservationNotFoundException(Long id) {
        super("Reservation " + id + " not found!");
    }
}

// 2. Handle it globally
@ControllerAdvice
public class GlobalHandler {

    @ExceptionHandler(
        ReservationNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Map<String, String> handleNotFound(
            ReservationNotFoundException ex) {

        return Map.of("error", ex.getMessage());
    }
}

// 3. Throw it when needed
@GetMapping("/reservation/{id}")
public Reservation get(@PathVariable Long id) {
    return repo.findById(id)
        .orElseThrow(() ->
            new ReservationNotFoundException(id));
}

🎉 Putting It All Together

Here’s how validation and error handling work as a team:

graph TD A["📥 Request Arrives"] --> B{🛡️ Valid?} B -->|✅ Yes| C["Process Data"] B -->|❌ No| D["MethodArgumentNotValidException"] D --> E["@ExceptionHandler / @ControllerAdvice"] E --> F["📤 Send Error Response"] C -->|Error During Process| G["Other Exception"] G --> E

🌟 Key Takeaways

  1. Validation = Your Bouncer 🚪

    • Use @Valid + annotations to check incoming data
    • BindingResult collects all the problems
  2. Custom Validators = Your Special Rules 🔧

    • Create annotation + validator class
    • Perfect for business-specific rules
  3. Exception Handling = Your Safety Net 🥅

    • @ExceptionHandler for single controller
    • @ControllerAdvice for all controllers
    • Always return friendly error messages!

🚀 Quick Code Summary

// ✅ Validation
@Valid @RequestBody MyDto dto

// ✅ Custom Validator
@Constraint(validatedBy = MyValidator.class)
public @interface MyRule { }

// ✅ Exception Handler
@ExceptionHandler(MyException.class)
public ResponseEntity<?> handle(MyException e)

// ✅ Global Handler
@ControllerAdvice
public class GlobalHandler { }

Now you’re ready to guard your Spring app like a pro! 🎊

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.