Spring Boot starter providing NullableValue<T> — a wrapper that distinguishes three states of a JSON field:
| State | Meaning | Example JSON |
|---|---|---|
| undefined | Field is absent in JSON | {"name": "John"} |
| null | Field is present with null value |
{"email": null} |
| value | Field is present with actual value | {"age": 30} |
This is especially useful for PATCH requests in REST APIs where you need to differentiate between "don't change this field" (undefined) and "set this field to null" (null).
- Java 21+
- Spring Boot 3.x / 4.x
- Jackson 3.x (
tools.jackson) - Jakarta Validation API 3.1+
First, add the JitPack repository to your build configuration:
Gradle:
repositories {
maven { url 'https://jitpack.io' }
}Maven:
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>Gradle:
dependencies {
implementation 'io.github.rav316:nullable-value-spring-boot-starter:1.0.1'
}Maven:
<dependency>
<groupId>io.github.rav316</groupId>
<artifactId>nullable-value-spring-boot-starter</artifactId>
<version>1.0.1</version>
</dependency>public class UserUpdateDto {
private NullableValue<String> name = NullableValue.undefined();
private NullableValue<String> email = NullableValue.undefined();
private NullableValue<Integer> age = NullableValue.undefined();
public NullableValue<String> getName() { return name; }
public void setName(NullableValue<String> name) { this.name = name; }
public NullableValue<String> getEmail() { return email; }
public void setEmail(NullableValue<String> email) { this.email = email; }
public NullableValue<Integer> getAge() { return age; }
public void setAge(NullableValue<Integer> age) { this.age = age; }
}@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PatchMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id,
@RequestBody UserUpdateDto dto) {
User user = userService.update(id, dto);
return ResponseEntity.ok(user);
}
}@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User update(Long id, UserUpdateDto dto) {
User user = userRepository.findById(id).orElseThrow();
dto.getName().ifPresent(user::setName);
dto.getEmail().ifPresent(user::setEmail);
dto.getAge().ifPresent(user::setAge);
return userRepository.save(user);
}
}NullableValue supports Jakarta Bean Validation. Constraints are applied to the wrapped value only when the field is present:
public class UserUpdateDto {
private NullableValue<@NotBlank String> name = NullableValue.undefined();
private NullableValue<@Email String> email = NullableValue.undefined();
private NullableValue<@Min(0) @Max(150) Integer> age = NullableValue.undefined();
// getters/setters
}| Method | Description |
|---|---|
NullableValue.undefined() |
Creates an undefined (absent) value |
NullableValue.of(value) |
Creates a present value (value can be null) |
isPresent() |
Returns true if the field was present in JSON |
get() |
Returns the value; throws if undefined |
orElse(other) |
Returns the value if present, otherwise other |
ifPresent(consumer) |
Executes the consumer if the value is present |
map(function) |
Transforms the value if present |
The starter automatically registers:
- Jackson module for serialization/deserialization of
NullableValue - Jakarta Bean Validation
ValueExtractorforNullableValue
No additional configuration is required.