Files
familienarchiv/docs/adr/035-optional-string-three-way-patch-semantics.md
Marcel 6fc5ce6ddd
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
docs: update GLOSSARY for JourneyItem view types; add ADR-035
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>
2026-06-08 17:06:25 +02:00

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>, not String — 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-nullable is removed from pom.xml; JacksonConfig.java is kept as a placeholder for future custom modules.