0

I wonder how to add validation for parameter type: I have endpoint:

    @PostMapping(consumes = "application/json", path = "/v2/tw")
    @ResponseStatus(HttpStatus.CREATED)
    public TwDto add(
            Authentication auth,
            @Validated(TwDto.New.class) @RequestBody TwDto twDto,
            BindingResult bindingResult) {

TwDto has inside

    @Valid
    @PositiveOrZero
    @Pattern(regexp = "[0-9]", message = "id must be a number")
    private Long storeId;

When I set storeId = "123someString" then I get alwasy 400 error without any message.

I want to send to the client eg. 404 eror code with some message - how to do that? I tried many optiosn and I get always 400 BAD REQUEST...

Or maybe I should do custom validation, eg go through some dto fields and in try catch check if storeId is number or not and then throw some error that will be catched in ExceptionHandler?

@Pattern(regexp = "[0-9]", message = "id must be a number") doesn't work, I dont see any message that should be returned with the error

Matley
  • 1,953
  • 4
  • 35
  • 73

1 Answers1

1

If you want to maintain automatic validation you may need to change the type of storeId to String:

@Valid
@Pattern(regexp = "[0-9]*", message = "id must be a number")
private String storeId; 

and then validation will work:

public class TwDtoTest {
    @Test
    void invalid_store_test() {
        TwDto twDto = new TwDto();
        twDto.setStoreId("aaaa");
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Set<ConstraintViolation<TwDto>> violations = validator.validate(twDto);

        assertThat(violations.size()).isEqualTo(1);
        violations.forEach(violation -> assertThat(
            violation.getMessage()).isEqualTo("id must be a number")
        );
    }

    @Test
    void valid_store_test() {
        TwDto twDto = new TwDto();
        twDto.setStoreId("10");
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Set<ConstraintViolation<TwDto>> violations = validator.validate(twDto);

        assertThat(violations.size()).isEqualTo(0);
    }
}

Of course, down the line, if you need to save or use the storeId as a Long you would need to convert it: Long.parseLong(storeId)

gere
  • 1,600
  • 1
  • 12
  • 19
  • 1
    I don't want to change property type - I thought that this solution is the best for my case but it doesn't work! https://stackoverflow.com/questions/57944251/spring-boot-handle-type-mismatch-errors – Matley Mar 02 '21 at 20:29
  • If you don't want or can't change the type, than no luck, you will need to handle the exception and the validation message manually as suggested in the other comments. – gere Mar 02 '21 at 20:34
  • Hmm but I think now that manual validation won't work because it's too late - first, Spring want to deserialize JSON to object and then the exception is thrown - and I can't catch any exception in ExceptionHandler – Matley Mar 02 '21 at 20:37
  • Have you tried with a @ControllerAdvice annotated class? is a class that behave like a global ExceptionHanlder for all controllers. – gere Mar 02 '21 at 20:43
  • Of course... NO exception during deserialization from json request body to my dto object is catched in my ControllerAdvice – Matley Mar 02 '21 at 20:48
  • Does this help? https://stackoverflow.com/questions/51001546/catch-a-deserialization-exception-before-a-controlleradvice – gere Mar 02 '21 at 20:57
  • doesn't work... I can't reach ControllerAdvice :( – Matley Mar 03 '21 at 00:30