All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m20s
CI / OCR Service Tests (pull_request) Successful in 25s
CI / Backend Unit Tests (pull_request) Successful in 3m48s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Semgrep Security Scan (pull_request) Successful in 22s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m7s
Fixes GLOSSARY position-step value (1000→10), adds DEFERRABLE constraint note, and documents GeschichteView, JourneyItemView, and DocumentSummary read-model types. ADR-035 records the decision to use Optional<String> for three-way PATCH semantics instead of jackson-databind-nullable (which targets Jackson 2.x and is incompatible with Spring Boot 4.0 / Jackson 3.x). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2.0 KiB
2.0 KiB
ADR-035 — Optional<String> for three-way PATCH semantics
Status: Accepted
Date: 2026-06-08
Issue: #751 (JourneyItem CRUD API)
Context
The PATCH /api/geschichten/{id}/items/{itemId} endpoint must distinguish three cases for the note field:
| JSON body | Intended meaning |
|---|---|
{"note": "text"} |
Set note to "text" |
{"note": null} |
Clear the note |
{} (absent) |
Leave note unchanged |
The standard library for this on Jackson 2.x is jackson-databind-nullable (JsonNullable<T> from org.openapitools). However, that library targets com.fasterxml.jackson.* (Jackson 2.x) and is incompatible with Spring Boot 4.0 / Spring Framework 7, which uses tools.jackson.* (Jackson 3.x). The module fails to register and throws at startup.
Decision
Use Optional<String> with Java's default field initializer (= null) to encode the three states:
@Data
public class JourneyItemUpdateDTO {
private Optional<String> note = null; // Java default — absent = no-op
}
| Java value | JSON wire | Semantics |
|---|---|---|
null (default) |
field absent | no-op |
Optional.empty() |
{"note": null} |
clear |
Optional.of("x") |
{"note": "x"} |
set |
Jackson 3.x natively maps a JSON null to Optional.empty() and leaves absent fields at their Java default. No custom module is needed.
Consequences
- No external dependency for PATCH semantics — simpler pom.xml.
- The DTO field type is
Optional<String>, notString— service code must null-check the field first (if (noteField == null) return;) and then call.orElse(null)to unwrap. - This pattern applies to any future PATCH DTO that needs three-way semantics on a nullable field.
jackson-databind-nullableis removed frompom.xml;JacksonConfig.javais kept as a placeholder for future custom modules.