Skip to main content

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:

  1. The same patient visiting the same facility twice does not create duplicate Patient resources
  2. Version history is maintained — HAPI increments _history on every update
  3. The FHIR identifier remains the authoritative link between the local MRN and the FHIR resource
Sending HIS sees MSA|AA even when circuit is OPEN

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

HAPI FHIR has two interceptors installed via the HAPI Plugin JAR:

  • SmartScopeInterceptor — validates the service account Bearer token
  • ConsentEnforcementInterceptor — fires before every JPA read/write

This provides a second consent check even if JamBridge's Stage 4 check was somehow bypassed.