Skip to content

Error handling

In 1.0 a runtime error — a bad cast, a missing document, a failed xsl:result-document — simply aborted the transform. XSLT 3.0 adds xsl:try/xsl:catch so you can recover, and xsl:assert so you can fail deliberately with a useful message.

xsl:try / xsl:catch

Wrap the risky work in xsl:try; if it raises a dynamic error, control jumps to a matching xsl:catch instead of aborting:

<xsl:try>
  <xsl:variable name="d" select="doc('prices.xml')"/>
  <total><xsl:value-of select="sum($d/prices/price)"/></total>
  <xsl:catch>
    <total error="prices unavailable">0</total>
  </xsl:catch>
</xsl:try>

Inside the xsl:catch, a set of err: variables describe what went wrong:

Variable Holds
$err:code the error's QName, e.g. err:FODC0002
$err:description the human-readable message
$err:value any value the error carried
$err:module / $err:line-number where it was raised
<xsl:catch>
  <error code="{$err:code}"><xsl:value-of select="$err:description"/></error>
</xsl:catch>

You can narrow a catch to specific codes with errors= (a list of QName patterns), and have several xsl:catch blocks — the first match wins:

<xsl:try>
  <xsl:sequence select="$x cast as xs:integer"/>
  <xsl:catch errors="err:FORG0001">…not a valid integer…</xsl:catch>
</xsl:try>

rollback-output

If the xsl:try body had already written some result before failing, rollback-output="yes" discards that partial output so the xsl:catch starts clean. Without it, whatever was written before the error stays. Streaming bodies cannot be rolled back, so the attribute must be no there.

Raising your own: error()

The error() function aborts with a code and message of your choosing — useful for enforcing preconditions:

<xsl:if test="not(catalog/cd)">
  <xsl:sequence select="error(xs:QName('local:empty'), 'Catalogue has no entries')"/>
</xsl:if>

It pairs naturally with xsl:try elsewhere: raise a precise error here, catch and translate it into output there.

xsl:assert — checked assumptions

xsl:assert (3.0) states an invariant. When assertions are enabled (a processor option — off by default, so production runs pay nothing), a false test aborts with your message; when disabled, the instruction is skipped entirely:

<xsl:assert test="every $p in //price satisfies $p castable as xs:decimal"
            select="'Every price must be numeric'"/>

Use it for "this should never happen" checks that document your assumptions and catch bad input early during development, without slowing the shipped transform.

Where to go next