XML Schema in practice: reuse and evolution
Complex type extension and restriction, named groups and attribute groups, include versus import, and how to version a schema so old readers keep working.
Reusing definitions
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:shop="urn:example:shop"
targetNamespace="urn:example:shop"
elementFormDefault="qualified">
<!-- a named simple type, reused by several elements -->
<xs:simpleType name="MoneyType">
<xs:restriction base="xs:decimal">
<xs:fractionDigits value="2"/>
<xs:minInclusive value="0"/>
</xs:restriction>
</xs:simpleType>
<!-- an abstract base complex type -->
<xs:complexType name="PartyType" abstract="true">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="email" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="id" type="xs:ID" use="required"/>
</xs:complexType>
<!-- extension adds fields and keeps the base sequence -->
<xs:complexType name="BusinessPartyType">
<xs:complexContent>
<xs:extension base="shop:PartyType">
<xs:sequence>
<xs:element name="vatNumber" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="tier" type="xs:string"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!-- a named group, reusable across several content models -->
<xs:group name="AuditFields">
<xs:sequence>
<xs:element name="createdAt" type="xs:dateTime"/>
<xs:element name="updatedAt" type="xs:dateTime" minOccurs="0"/>
</xs:sequence>
</xs:group>
</xs:schema>- Extension appends to the base sequence, so the base fields must come first in the document.
- Restriction narrows the base and must repeat the content model, which is why extension is usually the better choice.
- A named group is a snippet of a content model, not a type, and it can be referenced from any complex type.
- An attribute group collects attributes, which is the only way to reuse them since XML Schema has no attribute inheritance.
include and import
| Directive | Use when | Namespaces | Effect |
|---|---|---|---|
xs:include | Splitting one namespace across files | Must match the including schema | Textual inclusion at that point |
xs:import | Referencing another namespace | Different namespace | Adds a reference, qualified by prefix |
xs:redefine | Changing a definition while including it | Same namespace | Powerful, confusing, best avoided |
xs:override | Replacing a component from an included schema | Same namespace | XSD 1.1 replacement for redefine |
xs:any | Leaving a hole for extension content | Whatever the instance uses | Weakens validation deliberately |
<xs:import namespace="urn:example:common"
schemaLocation="common.xsd"/>
<xs:include schemaLocation="order-types.xsd"/>
<!-- a deliberate extension point, with a namespace requirement -->
<xs:element name="extension" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>The schemaLocation attribute is a hint, not an instruction. A validating parser may resolve it from a local catalogue instead, which is why a schema that validates on one machine can fail on another.
Versioning without breaking readers
| Change | Compatible? | Reason |
|---|---|---|
| Add an optional element | Yes | Old documents remain valid |
Add an element with minOccurs="1" | No | Old documents no longer validate |
| Add an optional attribute | Yes | Absent means the default applies |
| Rename an element | No | Consumers look for the old name |
| Change a type from string to date | No | Existing values may not match the lexical space |
| Tighten a restriction | No | Values that used to validate now fail |
| Add an enum value | Depends | Safe for a lenient consumer, breaking for a strict one |
| Remove a deprecated element | No | Announce, measure, then remove |
<!-- keep both during a transition, and document the deprecation -->
<xs:sequence>
<xs:element name="total" type="shop:MoneyType"/>
<xs:element name="totalAmount" type="shop:MoneyType" minOccurs="0">
<xs:annotation>
<xs:documentation>Deprecated: use total. Removed in schema version 3.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>💡
Version the schema, not the namespace, unless you intend a hard break. Changing a namespace URI invalidates every existing document and every XPath expression, which turns a small schema change into a migration project.
FAQ
Should I use <code>elementFormDefault="qualified"</code>?
Yes for a new schema with a target namespace. It makes local elements inherit the namespace, which is what most people expect and what avoids the namespace trap that breaks XPath.
Can one schema describe two versions?
With a choice or an optional duplicate element, yes. It gets unreadable quickly, so prefer a separate schema per major version and a documented compatibility rule.
Related
Attributes vs child elements: modelling decisions Parsing XML in code: DOM, SAX and pull parsers
Last refreshed 2026-09-18.