javarestjacksonopenapihibernate-validator

REST API in Java - How to support PATCH with Hibernate Validator


When writing a REST-API in Java, the PATCH method requests that a "set of changes" described in the request entity be applied to the resource identified by the Request-URI. (see: https://datatracker.ietf.org/doc/html/rfc5789)

Given following resource:

{
  productId: 7,
  name: "container",
  contactPerson: "peter",
  price: 12
}

With PATCH i should be able to set the "contactPerson" to NULL while not touching the other fields, like this:

{
  productId: 7,
  contactPerson: null
}

Within the Java Ecosystem how can i implement this in a good way, considering that the most relevant libraries here need to have full support for it:

Java Optional

Among others i tried out Java-Optional, and while it initially seemed to work it is the wrong tool for this, as an Optional should not contain a NULL-Value, but it should also be possible to set a value to NULL explicitly within a PATCH (instead of omitting it).

For Example, the following looked good initially (here for the name-field which is required = false and nullable = false):

@Schema(required = false, nullable = false)
Optional<@NotNull @Size(max = 255) String> name = Optional.empty();

Hibernate validator considered the @NotNull Annotation to be violated when the name field was missing in the request, which i found strange because the annotation is within the Optional-Generic-Declaration (not directly on the Optional)

JsonNullable

Jackson provides a JsonNullable annotation. While this is the right tool for the task and is seemingly supported for openapi, it does not seem to be supported for hibernate validator, at least i could not find something about it.

Validation

Mostly the problem seems to be during validation of Bean Validation Annotations, which are important to have for an API.

--

Is the REST-PATCH covered by those major libraries of the Java Ecosystem, and if yes, what is the recommended way to support it?


Solution

  • How about getting a key-value map in the request body, where the key matches the field name.

    @PatchMapping("/boxes/{id}")
    public Box patchBox(@PathVariable Long id, @RequestBody Map<String, Object> values) throws JsonMappingException {
        return boxService.patchBox(id, values);
    }
    

    Then the case where the field value is null will not overlap with the case where the field is not present in the request, and you can use ObjectMapper to automatically update only those fields that came in the request.

    @Override
    @Transactional
    public Box patchBox(Long id, Map<String, Object> values) throws JsonMappingException {
        Box box = entityManager.find(Box.class, id);
        objectMapper.updateValue(box, values);
        return box;
    }
    

    Of course, you can't validate the field values ​​from the request, but you automatically validate the entity whose fields are updated with these values. Ask yourself how you can validate a field value from the request if its correctness depends on the value ​​of another field that is not in the request.