Skip to content

All document converters emit relative Bundle.entry.fullUrl → fail ABDM DocumentBundle validation #157

Description

@raunaqp

All document converters emit relative Bundle.entry.fullUrl → fail ABDM DocumentBundle profile validation

Summary

Every FHIR document converter in fhir-mapper emits bundles whose Bundle.entry.fullUrl (and the
matching intra-bundle references) are relative — e.g. Composition/<uuid>, Patient/<uuid>. The FHIR
document-bundle rules enforced by the ABDM IG require Bundle.entry.fullUrl to be an absolute URI (the
ABDM examples use urn:uuid:). As a result the emitted bundles parse as base R4 but are non-conformant to
the ABDM DocumentBundle profile
, producing one validation error per emitted entry.

This is a structural, input-independent defect and it affects all seven shipped converters:
PrescriptionConverter, DiagnosticReportConverter, DischargeSummaryConverter, OPConsultationConverter,
ImmunizationConverter, WellnessRecordConverter, HealthDocumentConverter.

Environment (pinned)

  • Repo: pristine master @ bcc69513daddd18971a819a7afa44ec2b0ccf979 (2025-02-21)
  • Validator: HL7 FHIR Validation tool v6.9.11 (Git# 6a8b9c0c6794, built 2026-06-25)
  • FHIR version 4.0.1, IG ndhm.in#6.5.0
  • Reproduced from a fresh clone with no extra files in the tree:
    $ git rev-parse HEAD
    bcc69513daddd18971a819a7afa44ec2b0ccf979
    $ git status --porcelain
    (empty)
    
  • Smoke gate passed first: the published Bundle-InvoiceRecord-example-01.json validates
    0 errors, 0 warnings, 29 notes with the same command → the ABDM profiles are loaded and the gate is real.

Steps to reproduce

  1. git clone https://github.com/NHA-ABDM/ABDM-wrapper.git && cd ABDM-wrapper && git checkout bcc69513
  2. Run the service (full Spring context, in-memory H2 SNOMED loads automatically):
    ./gradlew -p fhir-mapper bootRun → starts on :8085.
  3. POST a request body to a document endpoint (the repo's own sample bodies in
    fhir-mapper/postmanCollection/FHIR.postman_collection.json work), e.g.:
    curl -X POST localhost:8085/v1/bundle/prescription -H 'Content-Type: application/json' --data @prescription.json -o prescription-bundle.json
  4. Validate the emitted bundle with the pinned validator:
    java -jar validator_cli.jar prescription-bundle.json -version 4.0.1 -ig ndhm.in#6.5.0

Observed — the smoking gun (one error per entry, input-independent)

The number of fullUrl must be an absolute URL errors equals the number of Bundle.entry elements, for
every type tested:

endpoint Bundle.entry count fullUrl must be absolute errors total errors
/v1/bundle/prescription 10 10 22
/v1/bundle/diagnostic-report 8 8 19

Verbatim, representative (prescription; element path + line included):

Error @ Bundle.entry[0] (line 1, col1876): The fullUrl must be an absolute URL (not 'Composition/e2661cef-c9e4-4e08-88ea-91379d44c821')

The same error fires once for every entry — relevant excerpt of the failing bundle's fullUrl values
(resource type + UUID only):

Composition/e2661cef-c9e4-4e08-88ea-91379d44c821
Patient/77c5079c-a62b-47bd-b4cf-081ff93ecf5f
Practitioner/Predator
Organisation/eefb1e6f-6532-4fb4-b10c-8ee990f1ecb7
Encounter/63f38e76-5fd8-4361-a660-81d704723697
MedicationRequest/c442b47b-95bc-421e-b9b8-a51d5edcac2d
... (one per entry)

Root cause (exact location)

The converters build entries with a relative fullUrl. Two forms exist, both relative:

  • PrescriptionConverter.java:137-177 (and the other inline converters) —
    .setFullUrl(BundleResourceIdentifier.COMPOSITION + "/" + composition.getId()), etc.
  • DiagnosticReportConverter.java:202-207 — shared helper:
    private void addBundleEntry(List<Bundle.BundleEntryComponent> entries, Resource resource, String resourceIdentifier) {
      if (resource != null && resource.getId() != null) {
        entries.add(
            new Bundle.BundleEntryComponent()
                .setFullUrl(resourceIdentifier + "/" + resource.getId())   // <-- relative; must be absolute
                .setResource(resource));
      }
    }

Cross-resource references are constructed the same way (BundleResourceIdentifier.<TYPE> + "/" + id) in the
composition makers, so references and fullUrls are both relative.

Expected & fix shape (verified)

Bundle.entry.fullUrl must be an absolute URI, with intra-bundle references using the same value. Emitting
urn:uuid:<uuid> for every fullUrl and matching reference clears the entire error class.

Proof of fix (output-only rewrite of the emitted prescription bundle — the converter was not changed):
rewriting the 10 relative fullUrls/refs to urn:uuid: dropped the fullUrl must be absolute errors from
10 → 0 under the identical pinned validation.

Two implementation points for whoever picks this up:

  1. Emit absolute fullUrls (urn:uuid:<uuid>, or a resolvable absolute base URL) and make every intra-bundle
    reference match the entry's fullUrl.
  2. When using urn:uuid:, the resource logical id must be a valid lowercase UUID. Today some makers reuse
    external ids verbatim (e.g. Practitioner/Predator above — the practitioner id is used as the resource
    id), which is not a UUID and would fail the urn:uuid: form; keep the external id on identifier
    instead.

Scope

This issue is the structural fullUrl defect only. The transcripts contain other, data-dependent
errors that come from the sample payloads, not the bundle structure; they are a separate defect family and
survive the fullUrl fix (12 unrelated errors remained after the 10→0 fix above). Those will be tracked
separately and are intentionally excluded here — one cause, one fix.

Attachments

  • prescription-bundle.attach.json — emitted bundle from POST /v1/bundle/prescription on master@bcc69513 (sample name/dob scrubbed; fullUrls unchanged).
  • prescription-validation.attach.txt — full validator transcript for that bundle (*FAILURE*: 22 errors, 10 of class "fullUrl must be absolute").

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions