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.