Stage 5 — PERSIST
What happens
FhirClientService.conditionalUpdate() writes the FHIR resource to HAPI FHIR using a conditional update — PUT /fhir/{Resource}?identifier=...:
PUT /fhir/Patient?identifier=urn:br01.mrn|MRN123456
Host: hapi:8080
Authorization: Bearer {service-account-token}
Content-Type: application/fhir+json
{...Patient resource...}
HAPI responds:
HTTP/1.1 200 OK
Location: /fhir/Patient/MRN123456/_history/3
ETag: "3"
Conditional update vs create
JamBridge always uses conditional update (PUT ?identifier=...), never unconditional create (POST). This ensures:
- The same patient visiting the same facility twice does not create duplicate Patient resources
- Version history is maintained — HAPI increments
_historyon every update - The FHIR identifier remains the authoritative link between the local MRN and the FHIR resource
When the circuit breaker opens (HAPI FHIR is down), messages are queued internally. The sending HIS still receives MSA|AA — it is not aware the FHIR write is pending. This prevents the HIS from flooding JamBridge with retries. The queue drains automatically when HAPI recovers.
Circuit breaker
A Resilience4j circuit breaker wraps every HAPI FHIR call:
CLOSED ──(5 failures)──► OPEN ──(30s wait)──► HALF-OPEN ──(1 success)──► CLOSED
When OPEN:
- New messages are queued in
SecondaryRetryQueue(PostgreSQL-backed) - The sending HIS receives
MSA|AA— it is not aware the FHIR write is pending - JamBridge retries with exponential backoff (up to 10 attempts)
- When HAPI recovers and the circuit closes, the queue drains automatically
Second consent check
HAPI FHIR has two interceptors installed via the HAPI Plugin JAR:
SmartScopeInterceptor— validates the service account Bearer tokenConsentEnforcementInterceptor— fires before every JPA read/write
This provides a second consent check even if JamBridge's Stage 4 check was somehow bypassed.