Versioning an XML Format Without Breaking Existing Readers
Version attribute or new namespace? Test results show how strict schemas reject added elements and how an extension point lets old readers keep working.

A format is a promise to everyone who reads it. The day you publish version 1, programs you have never seen start to depend on it, and changing it becomes an act of negotiation. XML makes some kinds of change safe and others not, and which is which depends on what readers do with things they do not understand. Plan the rule for changes before the first release, because the rule you did not write is the one your readers invented.
Three kinds of change
Compatible additions. A new optional element or attribute. Old documents are still valid for the new reader, and new documents are readable by an old reader if it ignores what it does not know.
Compatible-looking changes that are not. Making an optional field required, tightening a value range, changing the order of a sequence. Old documents become invalid for the new reader.
Breaking changes. Renaming, removing or changing the meaning of an element: <total> that used to include tax and now excludes it. Nothing in the syntax flags it. The only defense is a version signal and a migration plan.
The rule that makes additions safe is on the reader: ignore what you do not recognize (the "must ignore" rule, called the Postel principle in its general form). A reader that fails on an unknown element makes every addition a breaking change. The writer cannot fix that; the reader has to be written to tolerate extras, and a schema can encourage it.
What a strict schema does to an addition
A schema for version 1 describes an order with a customer and a total, in that order:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="order">
<xs:complexType>
<xs:sequence>
<xs:element name="customer" type="xs:string"/>
<xs:element name="total" type="xs:decimal"/>
</xs:sequence>
<xs:attribute name="version" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:schema>Version 2 of the document adds a currency element:
<order version="2"><customer>Ana</customer><total>19.90</total><currency>EUR</currency></order>xmllint --noout --schema strict.xsd v2a.xmlv2a.xml:1: element currency: Schemas validity error : Element 'currency': This element is not expected.
v2a.xml fails to validateA reader that validates its input against the version 1 schema refuses the version 2 document, although the change is the most innocent one possible. This is the first thing to know about XSD: the default behavior is closed. Every addition is a break for a validating reader, so a schema that is meant to last needs an explicit open spot.
An extension point
XSD offers xs:any, a wildcard that accepts elements you did not name. A common version limits it to elements from other namespaces, so extensions are clearly separated from the core vocabulary:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="order">
<xs:complexType>
<xs:sequence>
<xs:element name="customer" type="xs:string"/>
<xs:element name="total" type="xs:decimal"/>
<xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="version" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:schema>The document with the same value in an extension namespace:
<order version="2" xmlns:x="urn:example:ext"><customer>Ana</customer><total>19.90</total><x:currency>EUR</x:currency></order>Validating the two documents against the open schema:
open + plain extra
v2a.xml:1: element currency: Schemas validity error : Element 'currency': This element is not expected. Expected is ( ##other* ).
v2a.xml fails to validate
open + ns extra
v2b.xml validatesThe message for the unprefixed element names what is expected, ##other*, which is the schema telling you where the extension point sits. The namespaced element passes. This costs something: extensions must carry a namespace, so the format owner decides when a new field graduates from the extension namespace into the core. That is a feature, since it makes the compatible-addition path explicit. Namespaces for extensions are the subject of namespaces for extensions without chaos.
Signalling the version
A version marker has two jobs: let readers choose a code path, and let them refuse what they cannot handle. Three places to put it:
**A version attribute on the root.** Cheap, easy to read before parsing anything else, and appropriate for compatible changes. A reader can log it and branch when it matters. The limit is that it is advisory: nothing in the XML parser prevents a version 1 reader from trying a version 5 file.
The namespace URI. urn:example:orders:2 is a different vocabulary from urn:example:orders:1. A reader that looks for elements in the version 1 namespace finds none in a version 2 document and fails at once, loudly, which is the point. Use a new namespace for changes that must not be read by old code. The cost is that every reader, every XPath and every schema is touched (selecting elements with a namespace in XPath).
The schema location or file name. Useful for human routing, but nothing enforces it.
Use the attribute for evolution, and the namespace for a break. Do not change the namespace for compatible additions, because the cost lands on every consumer for no benefit.
A policy you can write down
Additions are optional elements or attributes, appended at the end of a sequence, or in the extension namespace.
Readers must ignore unknown elements and attributes, and must not fail validation on them.
Removing or renaming requires a deprecation period of N releases where both forms are written.
A change in meaning gets a new element name, never a reuse of the old one.
The
versionattribute is incremented on every release that adds, and the namespace changes only on a break.Publish the schema for every version, and keep the old ones.
Check the policy with the tool, not with trust: keep a set of documents from each past version and validate them against the current schema on every change to it. A failing old document is a break you introduced.
Related reading
The cluster hub is designing XML documents that age well. To detect that a producer changed shape without telling you, see detecting structure changes in a feed over time. Validation commands are in validating XML against XSD with xmllint. The wildcard is defined in the XML Schema structures specification, and the general guidance on extensibility in the W3C versioning note. Try a schema fix in the XML formatter before validating.
When to break compatibility on purpose
The old meaning was wrong. If a field was defined badly, such as a total that mixes taxed and untaxed items, keeping it perpetuates the error. Introduce the correct field under a new name, deprecate the old, and remove after the period.
The format cannot grow. A design with no extension point and positional elements can only be extended by breaking it. A new major version with the lessons applied is cheaper than another ten workarounds.
Security. A feature that cannot be made safe, such as external entities in a public format, is removed even if it breaks someone.
There is exactly one reader and you own it. For an internal file read by your own code, deploy the reader first and skip the ceremony. The policy above is for formats others read.


