Etraz
Etraz

Reputation: 55

How to use a Custom Validation Annotation for custom Error Handling in Spring Boot

I have a custom validation annotation that verifies a fiscal code.

FiscalCode.java

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Constraint(validatedBy = FiscalCodeValidator.class)
public @interface FiscalCode {
    String message() default "";

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

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

This annotation performs multiple checks, and the isValid method returns different error messages depending on how far it has validated the string.

FiscalCodeValidator.java

public class FiscalCodeValidator implements ConstraintValidator<FiscalCode, String> {

    @Override
    public boolean isValid(String fc, ConstraintValidatorContext context) {
        String validationMessage = checkFC(fc);

        if (!validationMessage.isEmpty()) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(validationMessage).addConstraintViolation();
            return false;
        }

        return true;
    }

    private String checkFC(String fc) {
        if (...) return "err1";
        ... some logic ...
        if (...) return "err2";
        ... some logic ...
        if (...) return "err3";
        ... some logic ...
        if (...) return "errN";
        ... some logic ...
        return "";
    }
}

I would like to use it both as a parameter in GET requests at the Controller level and as a field in a request object (POST, PUT).

TestController.java

@RestController
public class TestController {

    @GetMapping("test-get")
    public ResponseEntity<String> testGet(@FiscalCode String fc) {
        return ResponseEntity.ok(fc);
    }

    @PostMapping("test-post")
    public ResponseEntity<String> testPost(@Valid @RequestBody Example example) {
        return ResponseEntity.ok(example.getFiscalCode());
    }

    @PutMapping("test-put")
    public ResponseEntity<String> testPut(@Valid @RequestBody Example example) {
        return ResponseEntity.ok(example.getFiscalCode());
    }

    @DeleteMapping("test-delete")
    public ResponseEntity<String> testDelete(@FiscalCode String fc) {
        return ResponseEntity.ok(fc);
    }
}

Example.java

@Getter
@Setter
public class Example {
    @FiscalCode
    private String fiscalCode;
}

I would like to expose a dynamic validation error to the client (based on what the validation method returns) always in the ProblemDetail shape, with the detail property containing the error text.

GlobalExceptionHandler.java

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(HandlerMethodValidationException.class)
    ResponseEntity<ProblemDetail> handleHandlerMethodValidationException(HandlerMethodValidationException ex) {
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "");
        ... some logic ...
        return new ResponseEntity<>(problemDetail, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    ResponseEntity<ProblemDetail> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "");
        ... some logic ...
        return new ResponseEntity<>(problemDetail, HttpStatus.BAD_REQUEST);
    }
}

Is it absolutely necessary to handle each specific exception, extract the information, and insert it into a ProblemDetail? Are there more decoupled methods to achieve this? Is it possible to integrate with constraint management and provide a custom provider? Is there a best practice or recommended approach?

Thanks in advance

Upvotes: 0

Views: 108

Answers (0)

Related Questions