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
git clone https://github.com/NHA-ABDM/ABDM-wrapper.git && cd ABDM-wrapper && git checkout bcc69513
- Run the service (full Spring context, in-memory H2 SNOMED loads automatically):
./gradlew -p fhir-mapper bootRun → starts on :8085.
- 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
- 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:
- Emit absolute fullUrls (
urn:uuid:<uuid>, or a resolvable absolute base URL) and make every intra-bundle
reference match the entry's fullUrl.
- 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").
All document converters emit relative
Bundle.entry.fullUrl→ fail ABDM DocumentBundle profile validationSummary
Every FHIR document converter in
fhir-mapperemits bundles whoseBundle.entry.fullUrl(and thematching intra-bundle references) are relative — e.g.
Composition/<uuid>,Patient/<uuid>. The FHIRdocument-bundle rules enforced by the ABDM IG require
Bundle.entry.fullUrlto be an absolute URI (theABDM examples use
urn:uuid:). As a result the emitted bundles parse as base R4 but are non-conformant tothe ABDM
DocumentBundleprofile, 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)
master@bcc69513daddd18971a819a7afa44ec2b0ccf979(2025-02-21)6a8b9c0c6794, built 2026-06-25)ndhm.in#6.5.0Bundle-InvoiceRecord-example-01.jsonvalidates0 errors, 0 warnings, 29 noteswith the same command → the ABDM profiles are loaded and the gate is real.Steps to reproduce
git clone https://github.com/NHA-ABDM/ABDM-wrapper.git && cd ABDM-wrapper && git checkout bcc69513./gradlew -p fhir-mapper bootRun→ starts on:8085.fhir-mapper/postmanCollection/FHIR.postman_collection.jsonwork), e.g.:curl -X POST localhost:8085/v1/bundle/prescription -H 'Content-Type: application/json' --data @prescription.json -o prescription-bundle.jsonjava -jar validator_cli.jar prescription-bundle.json -version 4.0.1 -ig ndhm.in#6.5.0Observed — the smoking gun (one error per entry, input-independent)
The number of
fullUrl must be an absolute URLerrors equals the number ofBundle.entryelements, forevery type tested:
fullUrl must be absoluteerrors/v1/bundle/prescription/v1/bundle/diagnostic-reportVerbatim, representative (prescription; element path + line included):
The same error fires once for every entry — relevant excerpt of the failing bundle's
fullUrlvalues(resource type + UUID only):
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:Cross-resource references are constructed the same way (
BundleResourceIdentifier.<TYPE> + "/" + id) in thecomposition makers, so references and fullUrls are both relative.
Expected & fix shape (verified)
Bundle.entry.fullUrlmust be an absolute URI, with intra-bundle references using the same value. Emittingurn:uuid:<uuid>for everyfullUrland matchingreferenceclears 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 thefullUrl must be absoluteerrors from10 → 0 under the identical pinned validation.
Two implementation points for whoever picks this up:
urn:uuid:<uuid>, or a resolvable absolute base URL) and make every intra-bundlereferencematch the entry's fullUrl.urn:uuid:, the resource logicalidmust be a valid lowercase UUID. Today some makers reuseexternal ids verbatim (e.g.
Practitioner/Predatorabove — the practitioner id is used as the resourceid), which is not a UUID and would fail theurn:uuid:form; keep the external id onidentifierinstead.
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 fromPOST /v1/bundle/prescriptiononmaster@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").