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

DirectiveUse whenNamespacesEffect
xs:includeSplitting one namespace across filesMust match the including schemaTextual inclusion at that point
xs:importReferencing another namespaceDifferent namespaceAdds a reference, qualified by prefix
xs:redefineChanging a definition while including itSame namespacePowerful, confusing, best avoided
xs:overrideReplacing a component from an included schemaSame namespaceXSD 1.1 replacement for redefine
xs:anyLeaving a hole for extension contentWhatever the instance usesWeakens 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

ChangeCompatible?Reason
Add an optional elementYesOld documents remain valid
Add an element with minOccurs="1"NoOld documents no longer validate
Add an optional attributeYesAbsent means the default applies
Rename an elementNoConsumers look for the old name
Change a type from string to dateNoExisting values may not match the lexical space
Tighten a restrictionNoValues that used to validate now fail
Add an enum valueDependsSafe for a lenient consumer, breaking for a strict one
Remove a deprecated elementNoAnnounce, 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.

Attributes vs child elements: modelling decisions Parsing XML in code: DOM, SAX and pull parsers

Last refreshed 2026-09-18.