org.springframework.boot:spring-boot-starter-validation:2.3.3.RELEASE org.springframework.boot:spring-boot-starter-web:2.3.3.RELEASE org.hibernate.validator:hibernate-validator:6.1.5.Final javax.validation:validation-api:2.0.1.Final

Description

When I try to validate a number on a requestbody and the client sends null, it is casted automatically and I receive a pojo with value 0 instead of returning an error.

Problems that arise

It's not possible to validate that an actual number is sent. It's not possible to distinguish between null and 0.

Example

Controller/Dto:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

@RestController()
@RequestMapping("test")
public class JavaController {

    @PostMapping(path = "validation")
    public String test(@Valid @RequestBody OrderDto order) {
        return "Java Test works!";
    }

}

class OrderDto {
    @NotNull long rewardId;
}

JSON request:

{ "rewardId": null }

What happens: null is automatically converted into 0 and validation doesn't throw an exception. Therefore the validation is not working in this case.

My interpretation what happens

Validation occurs after the request was converted into the target structure (OrderDto). Because rewardId is not Optional it assigns 0 as value. Validation only runs after convertion and therefore can't properly validate the content.

Workarounds and why I don't like them

One could use either:

class OrderDto1 {
    @NotNull Long rewardId; // Mind the big L, object can be null
}
class OrderDto2 {
    @Min(1) long rewardId; // Only if id can't be smaller 1 in every case
}

Dto1: Now I have to deal with null values after validation even so validation makes sure it can't be null. The type should provide information for later use and not help with the earlier validation. This is especially annoying in Kotlin where you have a lot of Type safety and it helps a lot if the dto is already providing non null values. (in Kotlin Long instead of Long?) Dto2: 0 could be a valid input and we still want the type not to be nullable.

Can anyone provide me with an update regarding this issue?

Other sources I read regarding this issue: Stackoverflow how to handle invalid number in javax validation

Comment From: snicoll

I am afraid that @NotNull long rewardId; is not correct. A primitive will always have a value, with a default value of 0 if not initialized. You can't pass null to such a type is all.

Comment From: judos

Why does Spring first store the values in the DTO and then validate them? This makes no sense. Also it is very annoying to have Types that allow null values after it was validated not to be null. Whenever we can rely on the compilers type system we should do so. This is an anti-pattern in Spring.

When I use the primitive type long and provide no value in the request, Spring will just not care and set it to value 0, makes again no sense. If Spring can't store the null value in the DTO and null was provided it should also through a validation error. And this is again not happening.