XML in healthcare
The format that carries the clinical document. Elements, attributes, namespaces, schema validation and XPath — and an honest account of what XML does that JSON cannot.
In one line
XML is a text format for tree-shaped data with a strong sense of structure — and in health IT it is not legacy trivia, because the clinical document you are handed on a referral, a discharge, or a health-information exchange query is very often XML.
Elements and attributes
An XML document is a single tree. Every branch is an element, written as a start tag and an end tag. Elements can carry attributes — name/value pairs on the start tag.
<patient status="active">
<mrn>MRN-4471882</mrn>
<name>
<given>Karen</given>
<family>Whitfield</family>
</name>
<birthDate>1962-11-03</birthDate>
</patient>
When should something be an attribute rather than an element? The working rule: attributes
carry metadata about the thing (status, unit, codeSystem); elements carry the
thing itself. It matters practically, because attributes cannot repeat and cannot nest.
A patient has one status; a patient may have several addresses, so addresses must be
elements.
Two health standards make opposite choices here, and knowing that saves you confusion.
FHIR's XML form puts almost every value in a value attribute; CDA puts clinical
values in attributes on typed elements. Neither is wrong — they are different traditions.
Namespaces, and why they are not optional
Two organisations both invent an element called <code>. Merge their documents and a
parser cannot tell which is which. Namespaces fix that by binding element names to a
URI, declared with xmlns.
<Patient xmlns="http://hl7.org/fhir">
<id value="meera-iyer"/>
<identifier>
<system value="http://example.org/fhir/sid/abha"/>
<value value="91-7412-8523-6900"/>
</identifier>
<name>
<family value="Iyer"/>
<given value="Meera"/>
</name>
<gender value="female"/>
<birthDate value="1989-04-12"/>
</Patient>
The FHIR XML namespace is http://hl7.org/fhir. Get this exactly right. It is a
namespace identifier, not an address you fetch — the fact that it looks like a URL is
incidental. A commonly repeated error writes it as urn:hl7-org:fhir, which is not a FHIR
namespace at all. The URN you may be thinking of is urn:hl7-org:v3, which is the HL7
version 3 / CDA namespace — a different standard entirely. Mix them up and your
validator rejects a perfectly good document, or worse, silently matches nothing.
A namespace can be declared as the default (as above, applying to that element and its
descendants) or bound to a prefix, e.g. xmlns:cda="urn:hl7-org:v3" and then <cda:id/>.
The prefix itself is arbitrary and carries no meaning — only the URI it maps to counts.
Code that matches on the prefix string instead of the namespace URI is a bug waiting for
the first sender who picks a different letter.
Well-formed vs valid
These are two different bars, and people conflate them constantly.
Well-formed is syntax. One root element; every start tag closed; tags nested, not
overlapping; attribute values quoted; the five reserved characters escaped (&,
<, >, ", '). A non-well-formed document is not XML — parsers must
reject it outright.
Valid is conformance to a declared model — usually an XSD schema. Valid means the right elements, in the right order, the right number of times, with the right datatypes.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<xs:element name="patient">
<xs:complexType>
<xs:sequence>
<xs:element name="mrn" type="xs:string"/>
<xs:element name="birthDate" type="xs:date"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
That schema says: a patient contains exactly one mrn then exactly one birthDate, and
birthDate must parse as a date. A document with the two in the wrong order is well-formed
and invalid. Note what you got for free: ordering and typing enforced by the parser,
before your code runs. That is XML's real gift, and JSON has no equivalent built in.
Schema-valid still isn't clinically correct. CDA validation is layered: XSD first, then Schematron rules for the C-CDA template constraints (cardinality, required codes, terminology bindings). A document can pass XSD and fail Schematron badly.
XPath
XPath is a path language for pulling nodes out of a tree — the thing you will actually spend your time on when handed a 4,000-line C-CDA.
//cda:ClinicalDocument/cda:recordTarget/cda:patientRole/cda:id/@extension
//cda:observation[cda:code/@code='29463-7']/cda:value/@value
The first pulls the patient identifier attribute. The second pulls a body-weight value —
LOINC 29463-7 is "Body weight", and it is a LOINC code, not SNOMED CT; the two are
routinely mislabelled in slide decks. XPath predicates in [...], @ for attributes, //
for "anywhere below". Your XPath engine must be told the namespace-to-prefix mapping
separately — this is where most first attempts return an empty node set.
Where XML actually lives in health IT
- HL7 CDA and C-CDA — the clinical document. Namespace
urn:hl7-org:v3. This is XML's stronghold and it is not going away; US certified EHRs still exchange C-CDA at scale. - FHIR — XML is a first-class serialisation alongside JSON. Same resource model,
same field names, two wire formats. Content type
application/fhir+xml. - IHE profiles — XDS metadata, cross-enterprise document sharing, ebXML registries.
- SOAP web services — still the transport in many national and hospital integrations.
- DICOM has an XML representation, and CCDA-on-FHIR, X12 wrappers and government reporting formats all touch XML somewhere.
A C-CDA header, to show what the tradition looks like:
<ClinicalDocument xmlns="urn:hl7-org:v3">
<realmCode code="US"/>
<typeId root="2.16.840.1.113883.1.3" extension="POCD_HD000040"/>
<templateId root="2.16.840.1.113883.10.20.22.1.1" extension="2015-08-01"/>
<id root="2.16.840.1.113883.19.5" extension="MRN-4471882"/>
<code code="34133-9"
codeSystem="2.16.840.1.113883.6.1"
displayName="Summary of episode note"/>
<title>Continuity of Care Document</title>
<effectiveTime value="20260719103000-0400"/>
</ClinicalDocument>
Everything is an attribute; 2.16.840.1.113883.6.1 is the OID for LOINC; the templateId
extension 2015-08-01 marks the C-CDA R2.1 US Realm Header.
XML vs JSON, honestly
JSON is lighter, maps straight onto objects in every language, and is what a modern API should default to. But XML carries three things JSON does not have natively:
- Mixed content — text and markup interleaved.
<p>Take <content styleCode="Bold">two tablets</content> daily</p>is how a CDA narrative block preserves the human-readable document with emphasis and internal links intact. JSON has no clean way to express markup inside a run of text, which is why FHIR'sNarrative.divis a blob of escaped XHTML inside the JSON. - Attributes as a separate axis — metadata attached to a value without inventing a wrapper object.
- A mature validation and query stack — XSD, Schematron, XPath, XSLT, and digital signatures over a canonical form. JSON Schema is good and improving; XSLT has no real JSON counterpart, which is why document transformation pipelines are still XML.
If you are exchanging a document — a signed, human-readable, legally-attested artefact — XML's model fits. If you are exchanging data for an app to render, JSON is less work. This is why FHIR supports both and lets the use case decide.
The errors you will actually hit
- Wrong or missing namespace. The single most common failure. Your XPath returns nothing and everything looks right.
- Unescaped
&. A patient address containingSmith & Sons Clinicbreaks the parse. Escape it or wrap it in<![CDATA[...]]>. - Encoding mismatch. Declaring
<?xml version="1.0" encoding="UTF-8"?>while emitting a different encoding mangles every non-ASCII name. - XXE — XML External Entity injection. A hostile document declares an external entity
and your parser dutifully reads
/etc/passwdor makes network calls. Disable external entity resolution and DTD processing on every parser handling third-party XML. This is a real, exploited vulnerability class in health integrations, not a theoretical one. - Assuming element order is flexible. In XSD
<xs:sequence>it is not. - Treating whitespace as free. Between elements it is usually ignorable; inside a text node it is content.
Reading about namespaces is not the same as debugging one. The data-formats lab puts a real C-CDA and a FHIR XML resource in front of you to parse, namespace-map, XPath and validate — go break them there.