JSP Documents (XML Syntax)
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Jakarta Pages 4.0 (Jakarta EE 11), with Jakarta Expression Language 6.0, Jakarta Standard Tag
Library 3.0 and Jakarta Servlet 6.1, running on Apache Tomcat 11.0.26 and Java 21. Everything was verified on 2026-10-08:
the facts against the Pages 4.0 specification (chapters 5, 6, 8 and 10, section 3.3.7 and appendix B) and its Javadoc, and
every example by building it into a Maven WAR and requesting it on Tomcat 11.0.26 with curl; the outputs and error
messages shown are the ones that run produced. A JSP document is a JSP page that is also a well-formed, namespace-aware XML
document. The container reads it as XML first and as a JSP page second, so the <% %> syntax is gone and every JSP
construct has an XML form. (The container side, request dispatch and web.xml, is on Servlet Fundamentals.) The Bookshelf of this page serves its catalogue as XHTML, as plain XML, as a CSV text file and
as a tag-file-based list, then uses the XML view (the representation that validators receive) to forbid scripting in a
whole application.
What a JSP Document Is and Why It Exists
A JSP page in the usual syntax is a text file with some <% %> and ${ } islands. Most of what authors write is
HTML, and HTML tolerates unclosed <br>, unquoted attributes and bare ampersands, so the usual syntax makes no attempt to
be XML. That is fine for HTML, and a problem when the output must be XML: XHTML served as application/xhtml+xml, SVG, Atom
or RSS feeds, a SOAP-era web-service reply, or a document that goes on to an XSLT pipeline. A JSP document keeps the page
well-formed by construction, because a parser checks the file before any Java is generated.
The specification lists the intended uses: pass a document straight to the container, manipulate it with XML-aware
tools, generate it from another XML document with a transformation, or generate it programmatically. It also separates two
audiences. JSP documents are for page authors. The XML view is internal to the container and is of interest only to tag
library authors and container implementors, because that is what a TagLibraryValidator sees (see The XML View).
| Aspect | Status (2026-10-08) |
|---|---|
Specification |
Current and normative: chapter 6 (JSP documents) and chapter 10 (XML view). Nothing in them was removed in Pages 4.0; the changes in appendix C are |
Usage |
Niche. Most applications generate HTML5, which is not well-formed XML, and use the usual syntax. The XML syntax is the better choice when the output must be XML |
Validation |
Only DTD validation is required for JSP documents. XML Schema validation and XInclude are listed as possible future directions (section 6.5), not as features |
Engines |
Tomcat (Jasper) 11.0.26 implements it, and every example below ran there. Only Tomcat was exercised for this page |
Struts |
The Struts part of this section (see Struts Getting Started) writes its views in the usual syntax; no Struts tag was exercised in a JSP document |
Identifying a JSP Document
The container must decide, for every file it treats as a JSP page, whether the file is in the usual syntax or in XML syntax. Section 6.2.1 gives three rules and a strict order of precedence.
| Rank | Rule | Bookshelf example |
|---|---|---|
1 |
A |
|
2 |
Otherwise, with a Servlet 2.4 or later |
|
3 |
Otherwise, if the file is a JSP page and its top element is |
A file named |
If a file is identified as a JSP document and is not well-formed and namespace-aware XML, translation fails. Nothing is sent to the client except the 500 error page.
The web.xml below belongs to the Bookshelf of this page (property groups are covered on the Configuration and Property Groups page). The first group switches one file back to the usual syntax even
though its extension is .jspx, which is the example the specification itself uses for false; the second makes every
.xhtml file a JSP document. Both use the Servlet 6.1 descriptor.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_1.xsd"
version="6.1">
<jsp-config>
<jsp-property-group>
<url-pattern>/WEB-INF/views/legacy.jspx</url-pattern>
<is-xml>false</is-xml>
</jsp-property-group>
<jsp-property-group>
<url-pattern>*.xhtml</url-pattern>
<is-xml>true</is-xml>
</jsp-property-group>
</jsp-config>
</web-app>
A .xhtml file that is not a valid JSP page in the usual syntax shows the effect. books.xhtml is a JSP document only
because of the second group:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:c="jakarta.tags.core">
<body><c:forEach var="b" items="${books}"><p>${b.title}</p></c:forEach></body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"><body><p>Dune</p><p>Neuromancer</p></body></html>
legacy.jspx contains an unclosed <br> and a taglib directive, both illegal in XML. With <is-xml>false</is-xml> it
is translated as a normal page and the container never runs an XML parser on it:
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<html>
<body>
<c:forEach var="b" items="${books}">${b.title}<br></c:forEach>
</body>
</html>
<html>
<body>
Dune<br>Neuromancer<br>
</body>
</html>
A First JSP Document: the Bookshelf as XHTML
The smallest useful JSP document is a template XML element tree with one action inside. The Bookshelf servlet forwards the
list of books to /WEB-INF/views/books-xml.jspx (the servlet is the one from the Getting Started page, with a parameter
that selects the view for these experiments). The page below is XHTML 1.0 Strict with a DOCTYPE generated by jsp:output,
a content type chosen by jsp:directive.page and the Jakarta Tags core library bound with xmlns:c.
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:c="jakarta.tags.core">
<jsp:directive.page contentType="application/xhtml+xml; charset=UTF-8" />
<jsp:output doctype-root-element="html"
doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" />
<head><title>Bookshelf</title></head>
<body>
<h1>Bookshelf</h1>
<ul>
<c:forEach var="b" items="${books}">
<li>${b.title} by <c:out value="${b.author}"/> (${b.year})</li>
</c:forEach>
</ul>
</body>
</html>
HTTP/1.1 200
Content-Type: application/xhtml+xml;charset=UTF-8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Bookshelf</title></head><body><h1>Bookshelf</h1><ul><li>Dune by Frank Herbert (1965)</li><li>Neuromancer by William Gibson (1984)</li></ul></body></html>
Several things happened that a page in the usual syntax would not do. The container added an XML declaration because there
is no jsp:root. It inserted the DOCTYPE immediately before the first element, from the three jsp:output attributes. It
removed every whitespace-only text node (the indentation and line breaks between elements), which is why the output is one
line. The jsp prefix is bound to http://java.sun.com/JSP/Page on the root element, and the prefix itself is irrelevant:
only that URI marks an element as a standard action. The jsp namespace declaration does not appear in the output, while
the XHTML namespace does, because the html element is template data and is passed through.
Namespaces, Standard Actions and Tag Libraries
A page in the usual syntax imports a tag library with <%@ taglib prefix="c" uri="..." %>. A JSP document cannot,
because <%@ is not XML, so the taglib directive is not allowed in a JSP document and libraries are imported with
xmlns attributes. Standard actions are recognized by namespace, never by prefix.
| Form | Meaning | Bookshelf example |
|---|---|---|
Plain URI |
Looked up in the taglib map of |
|
|
The TLD is found by the "TLD resource path" mechanism of section 7.3.2: a path inside the web application or a JAR |
|
|
A directory of tag files, as in section 8.4 |
|
Four rules decide what happens to other elements.
-
Standard actions are the elements in the namespace
http://java.sun.com/JSP/Page, with any prefix. In a JSP document the prefix is arbitrary but the URI is not: the specification showswombat:rootworking, and shows that bindingjspto another URI turnsjsp:rootinto ordinary template data. -
Custom actions are the elements in the namespace of an imported library, introduced on the root element or on any descendant. Unlike JSP 1.2, not every library has to be declared on the root.
-
Template elements are everything else. They can have qualified names (and be in a namespace) or be unqualified, and they are written to the output.
-
An unknown namespace is not an error. A plain-URI namespace that matches no taglib map entry produces no translation error; its elements are treated as uninterpreted template data.
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:x="urn:example:unknown">
<body><x:note>kept as plain markup</x:note></body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns:x="urn:example:unknown" xmlns="http://www.w3.org/1999/xhtml"><body><x:note>kept as plain markup</x:note></body></html>
An unbound prefix is a different matter. The XML parser has no idea what c: means, so a document that uses c:forEach
without an xmlns:c fails before JSP processing starts. This is the most common first error after copying a page from the
usual syntax:
<html xmlns="http://www.w3.org/1999/xhtml">
<body><c:forEach var="b" items="${books}"><p>${b.title}</p></c:forEach></body>
</html>
org.apache.jasper.JasperException: /WEB-INF/views/noxmlns.jspx (line: [2], column: [45]) The prefix "c" for element "c:forEach" is not bound.
A default namespace also changes what unqualified elements mean. If xmlns="..." pointed at a tag library, unqualified
elements would become invocations of its tags, a trap the specification points out in its namespace-aware example
(6.4.2). The Bookshelf pages bind XHTML as the default namespace and give every library an explicit prefix.
jsp:root
jsp:root is the optional root element of a JSP document. It was mandatory in JSP 1.2, and the specification expects most
documents in Pages 4.0 not to use it. It matters for two reasons: it marks a file as a JSP document without the .jspx
extension or a property group, and it lets a document produce something other than a single XML document: a sequence of
XML documents, or non-XML content such as plain text. It may appear only as the root of a JSP document or of a tag file in
XML syntax, or the translation fails.
| Attribute | Required | Meaning |
|---|---|---|
|
yes |
The version of the specification the page uses. The container rejects other values with a translation error. Valid values are |
|
no |
Namespace declarations for standard actions, custom actions or generated template text |
Tomcat 11.0.26 checks the version and lists the accepted values in its message: Invalid version number: [5.0], must be
"1.2", "2.0", "2.1", "2.2", "2.3", "3.0", "3.1" or "4.0" (observed with version="5.0").
The second use is easiest to see with two root-level elements. A real XML document has one root, so a document without
jsp:root could not do this; with it, the two book elements are emitted as a sequence, and, by default, without an XML
declaration:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="4.0">
<book>Dune</book>
<book>Neuromancer</book>
</jsp:root>
<book>Dune</book><book>Neuromancer</book>
The same mechanism generates non-XML content. The Bookshelf export below is a semicolon-separated text file. There are no
template elements, only jsp:text and an action. The line break is inside the jsp:text body, so it is kept verbatim.
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:c="jakarta.tags.core"
version="4.0">
<jsp:directive.page contentType="text/plain; charset=UTF-8" />
<c:forEach var="b" items="${books}">
<jsp:text>${b.id};${b.title};${b.author};${b.year}
</jsp:text>
</c:forEach>
</jsp:root>
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
1;Dune;Frank Herbert;1965
2;Neuromancer;William Gibson;1984
CSV cells that contain ;, quotes or line breaks need quoting, and a title that starts with = is interpreted as a formula
when the file is opened in a spreadsheet. A real export belongs in a servlet with a CSV library; the example shows only the
mechanism.
jsp:output: XML Declaration and DOCTYPE
jsp:output controls the prolog of the generated document and exists only in the XML syntax. It cannot have a body and may
appear in JSP documents and in tag files in XML syntax. In a page in the usual syntax it is a translation error; Tomcat
reports <jsp:output> must not be used in standard syntax (observed).
| Attribute | Required | Meaning |
|---|---|---|
|
no |
|
|
only with |
The name that follows |
|
no |
Requests a DOCTYPE and supplies the system literal |
|
no |
The public identifier; only legal together with |
| Where | Default |
|---|---|
JSP document with a |
|
JSP document without a |
|
Tag file in XML syntax |
always |
The DOCTYPE is determined at translation time and written just before the first output element. The format is
<!DOCTYPE root PUBLIC "public" "system"> when a public identifier is present and <!DOCTYPE root SYSTEM "system">
otherwise. Repeating an attribute with a different value is a translation error. The author is responsible for the URI being
valid.
Three Bookshelf variants show the declaration rules. root-decl.jspx forces the declaration on a document that has a
jsp:root; noroot-omit.jspx removes it from one that does not:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="4.0">
<jsp:output omit-xml-declaration="no"/>
<book>Dune</book>
</jsp:root>
<catalogue xmlns:jsp="http://java.sun.com/JSP/Page">
<jsp:output omit-xml-declaration="true"/>
<book>Dune</book>
</catalogue>
<?xml version="1.0" encoding="UTF-8"?>
<book>Dune</book>
<catalogue><book>Dune</book></catalogue>
Asking for a DOCTYPE without a system literal is rejected at translation time, as the specification demands:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page">
<jsp:output doctype-root-element="html"/>
<body/>
</html>
org.apache.jasper.JasperException: /WEB-INF/views/doctype-bad.jspx (line: [2], column: [44]) <jsp:output>: 'doctype-root-element' and 'doctype-system' attributes must appear together
A DOCTYPE written in the source file is something else: it makes the container validate the document (see
DTD Validation and Entities). jsp:output only writes a DOCTYPE into the response.
Directives in XML Syntax
Only the page and include directives (see Directives) have XML elements that work in an ordinary JSP document; the others belong to tag
files.
| Usual syntax | XML syntax | Rule |
|---|---|---|
|
|
Must be a child of the root element. Same attributes and meaning as the |
|
|
May appear anywhere in the document. Translation-time inclusion; the XML view expands it in place |
|
|
The |
|
|
Tag files only; the XML view uses the same elements |
jsp:directive.include is how a document reuses a fragment. The fragment below is itself XML with its own default
namespace, which is the safe style, because the specification resets the default namespace to "" at the root of an
included XML segment so that the includer’s default namespace is not inherited:
<p xmlns="http://www.w3.org/1999/xhtml">Total: ${books.size()} books</p>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page">
<body>
<h1>Bookshelf</h1>
<jsp:directive.include file="/WEB-INF/views/footer.jspf" />
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"><body><h1>Bookshelf</h1><p xmlns="http://www.w3.org/1999/xhtml">Total: 2 books</p>
</body></html>
Note the redundant xmlns on the fragment’s element and the line break after it in the output (observed). For request-time inclusion of
another resource, jsp:include is used exactly as in the usual syntax; it is a standard action and needs no change.
A taglib directive in a document fails at parse time, because <%@ is not legal XML:
<html xmlns="http://www.w3.org/1999/xhtml">
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<body><p>Dune</p></body>
</html>
org.apache.jasper.JasperException: /WEB-INF/views/bad-taglib.jspx (line: [2], column: [4]) The content of elements must consist of well-formed character data or markup.
Scripting Elements in XML Syntax
Declarations, scriptlets and expressions have exactly one legal form in a JSP document, the XML elements. The old
<% %> forms are not XML, so a scriptlet in a document is a parse error:
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<p><% out.print("Dune"); %></p>
</body>
</html>
org.apache.jasper.JasperException: /WEB-INF/views/bad-scriptlet.jspx (line: [3], column: [9]) The content of elements must consist of well-formed character data or markup.
The XML elements have no attributes, and their bodies hold the code. The code must itself be well-formed text, so < and
& are written as < and & or wrapped in a CDATA section.
| Usual syntax | XML syntax | Role |
|---|---|---|
|
|
Members of the generated servlet class; shared by all requests, so they are not thread-safe state |
|
|
Statements in |
|
|
A value written to the output |
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page">
<jsp:declaration>int visits = 0;</jsp:declaration>
<body>
<jsp:scriptlet>visits++; int n = ((java.util.List<?>) request.getAttribute("books")).size();</jsp:scriptlet>
<p>Books: <jsp:expression>n</jsp:expression></p>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"><body><p>Books: 2</p></body></html>
The one place the XML syntax invents new notation is the request-time attribute value of a custom action. The usual
syntax writes <c:out value="<%= expr %>"/>, which is not well-formed because of the < in the attribute. In a JSP
document the same value is written value="%= expr %": without the angle brackets, and with optional white space around
the expression. This notation applies to custom actions only. On a template element such as <a href="%= url %"> the text
is not an expression and is passed through verbatim; the specification’s workaround is to build the element from
jsp:text and jsp:expression. Modern pages do not need either: href="${url}" is an EL expression in both syntaxes.
Everything that applies to scripting in the usual syntax applies here too: <scripting-invalid>true</scripting-invalid>
in a property group makes the container reject these elements, and the better replacement is EL, JSTL and tag files. The
XML view section below turns the same idea into a validator that works at the level of a whole application.
Template Data, Whitespace, EL and Quoting
A page in the usual syntax passes any text it does not understand through to the response. A JSP document passes
structured template data: XML elements that are not actions, after the parser has processed them, plus the content of
jsp:text. The semantic model does not change; only how the data is described. The sections below follow the rules in
section 6.2.3 and 6.3.9.
Whitespace
White space (space, tab, carriage return, line feed) has one rule: text nodes that contain only white space are dropped,
with the single exception of text inside a jsp:text element, which is kept verbatim. That is why the XHTML example
above is one line, and why a single line break survives in the CSV export (it is inside jsp:text). It also means that
trim-directive-whitespaces has no effect on JSP documents; it does not need to, because the parser already did the work.
jsp:text and CDATA
jsp:text encloses template data. It has no attributes, may contain EL expressions, and cannot contain nested actions or
scripting elements (a nested jsp:text is a translation error). Inside a JSP document its body must still be well-formed,
so characters such as < and & are escaped or put into a CDATA section, which the container passes through unparsed.
The specification gives ${foo.content > 3} as an expression that is legal in a JSP page and illegal in a document, and
suggests gt. On Tomcat 11.0.26 a > in a text node was accepted (observed with bad-gt.jspx), since > is legal XML character
data; a <, a && or an attribute value containing < is what fails (see Pitfalls). The word operators lt,
gt, le, ge, and and eq work everywhere, so use them.
<html xmlns="http://www.w3.org/1999/xhtml">
<body><p>${books[0].title} \${books[0].title} <jsp:text xmlns:jsp="http://java.sun.com/JSP/Page"><![CDATA[<b>bold</b> & ${books[0].year}]]></jsp:text></p></body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"><body><p>Dune ${books[0].title} <b>bold</b> & 1965</p></body></html>
The first expression was evaluated, the second was escaped by the backslash and printed literally, and the CDATA content
reached the response unchanged, including the expression, which was still evaluated. In template text and attribute values
EL works as in the usual syntax and \$ escapes it. The only difference is the backslash: \\ is not an escape sequence in
an XML attribute, so what the usual syntax would print as \2 for \\${1+1} a document prints as \${1+1}.
Entities Are Resolved, Output Is Not Re-escaped
Because the container parses the file as XML, character and entity references are decoded when the file is read, and the decoded characters are what goes to the response. The output is not an XML serializer: it does not escape them again.
<p xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:c="jakarta.tags.core">Tom & Jerry <jsp:text>Tom &amp; Jerry</jsp:text> <c:out value="Tom & Jerry"/> <c:out value="${books[0].title} & co" escapeXml="true"/></p>
<?xml version="1.0" encoding="UTF-8"?>
<p xmlns="http://www.w3.org/1999/xhtml">Tom & Jerry Tom & JerryTom & JerryDune & co</p>
Tom & Jerry in the source became a bare & in the response, which is not well-formed XML and breaks an XML consumer.
To emit an entity, produce the reference itself (&amp; in a jsp:text) or let an action escape it: c:out with its
default escapeXml turned a & coming from the value into &. The same applies to dynamic data. EL in a template
text node is not escaped, so a title containing < or & produces a broken document. Escape data with c:out or the
fn:escapeXml function.
Dynamic Elements and Attributes
When the name of an element or the value of an attribute has to be computed, the usual standard actions apply:
jsp:element, jsp:attribute and jsp:body are standard actions that exist in both syntaxes and are the most flexible
mechanism the document syntax has for dynamic structure. The Bookshelf headings below use h2 for books older than 1970 and
h3 for the rest, with a generated id:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:c="jakarta.tags.core">
<body>
<c:forEach var="b" items="${books}">
<jsp:element name="${b.year lt 1970 ? 'h2' : 'h3'}">
<jsp:attribute name="id">book-${b.id}</jsp:attribute>
<jsp:body>${b.title}</jsp:body>
</jsp:element>
</c:forEach>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"><body><h2 id="book-1">Dune</h2><h3 id="book-2">Neuromancer</h3></body></html>
For simple substitution no action is needed: <row>${table.value}</row> and indent="${table.indent}" are legal in a
document, with the expression in the body or in an attribute.
Encoding, Content Type and the XML Declaration
JSP documents use XML’s own encoding rules instead of the usual-syntax page-encoding rules. The specification’s appendix B
says the page encoding of a JSP document is determined as in appendix F.1 of XML 1.0 (byte-order mark or the encoding
pseudo-attribute of the XML declaration, UTF-8 by default), and the file is read with that encoding. There is no pageEncoding
to maintain for a document in XML syntax.
<?xml version="1.0" encoding="ISO-8859-1"?>
<p xmlns="http://www.w3.org/1999/xhtml">Gabriel García Márquez</p>
HTTP/1.1 200
Content-Type: text/xml;charset=UTF-8
<?xml version="1.0" encoding="UTF-8"?>
<p xmlns="http://www.w3.org/1999/xhtml">Gabriel García Márquez</p>
The source was read as Latin-1 because its declaration says so, and the response is re-encoded in UTF-8; the declaration the
container generated names the response encoding, not the source encoding. The same is true of the content type. Without a
contentType attribute the initial response content type is text/html for pages in the usual syntax and text/xml for
JSP documents, and the initial response encoding for documents is UTF-8 (section 4.2). All the Bookshelf pages above show
text/xml;charset=UTF-8 for that reason. XHTML served as text/xml is rendered as XML, not as an HTML page; set
application/xhtml+xml with jsp:directive.page (as books-xml.jspx does) if the browser should treat it as XHTML, or
use the default-content-type property of a property group for a set of pages.
The XML view character encoding (the general rules are on the Character Encoding page), the encoding used when the view is externalised to a validator, is always UTF-8.
DTD Validation and Entities
A JSP document that has a DOCTYPE declaration must be validated by the container during translation, and failures are
translation errors like any other. Pages 4.0 requires only DTD validation; containers should not validate against other
schema languages. This is also how a document gets custom entities: an internal DTD subset defines them. The catch is that
validation also applies to every element and attribute. A DOCTYPE without declarations for the document’s elements, and for
the xmlns attributes, fails. The specification warns that a DTD must make explicit provision for the jsp prefix and its
xmlns:jsp attribute if the document is going to use JSP elements.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE p [
<!ELEMENT p ANY>
<!ATTLIST p xmlns CDATA #FIXED "http://www.w3.org/1999/xhtml">
<!ENTITY author "Frank Herbert">
]>
<p xmlns="http://www.w3.org/1999/xhtml">&author; wrote Dune & others</p>
<?xml version="1.0" encoding="UTF-8"?>
<p xmlns="http://www.w3.org/1999/xhtml">Frank Herbert wrote Dune & others</p>
The same file with <!DOCTYPE p [ <!ENTITY author "Frank Herbert"> ]> and nothing else fails with Element type "p" must
be declared. (observed), because the presence of a DOCTYPE turns validation on. Prefer a document with no DOCTYPE and put the
DOCTYPE in the output with jsp:output. The response then contains the identifier but the container does not try to fetch
and validate against the DTD.
Tag Files in XML Syntax
The same rules apply to tag files. A tag file in XML syntax has the extension .tagx, a .tag file is never in XML
syntax, and jsp:root can (but need not) appear in a .tagx and cannot appear in a .tag. The tag directives become
jsp:directive.tag, jsp:directive.attribute and jsp:directive.variable. Two differences from JSP documents: the
default of omit-xml-declaration is always yes, and the tag directive cannot have a pageEncoding attribute (the
encoding comes from XML); using it is a translation-time error.
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="4.0">
<jsp:directive.tag body-content="empty" />
<jsp:directive.attribute name="book" required="true" type="org.example.bookshelf.Book" />
<li xmlns="http://www.w3.org/1999/xhtml">${book.title} (${book.year})</li>
</jsp:root>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:c="jakarta.tags.core" xmlns:b="urn:jsptagdir:/WEB-INF/tags">
<body><ul><c:forEach var="x" items="${books}"><b:bookrow book="${x}"/></c:forEach></ul></body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"><body><ul><li xmlns="http://www.w3.org/1999/xhtml">Dune (1965)</li><li xmlns="http://www.w3.org/1999/xhtml">Neuromancer (1984)</li></ul></body></html>
Tag files live under /WEB-INF/tags (or /META-INF/tags in a JAR), and the directory in urn:jsptagdir: and the document needs no
taglib directive. Tag files in general, with .tag syntax, are the subject of the tag-files page of this part.
The XML View
Every JSP page, in either syntax, has an XML view: an XML document derived from it by a fixed mapping. The container does
not run the page from it. Its job is validation. When a tag library declares a <validator> in its TLD, the container
hands the validator a jakarta.servlet.jsp.tagext.PageData object whose getInputStream() returns the XML view, and the
validator returns ValidationMessage objects for anything wrong. For a JSP document the transformation is small. For a page
in the usual syntax it is a full rewrite.
| Step | What happens |
|---|---|
1 |
Include directives are expanded in place (so the view of a document that includes a usual-syntax fragment contains that fragment’s elements as well) |
2 |
A |
3 |
The |
4 |
The |
5 |
A |
| Usual syntax | XML view |
|---|---|
|
Removed |
|
|
|
An |
|
Expanded in place |
|
|
Standard action |
XML syntax, request-time attributes converted |
Custom action |
Unchanged, request-time attributes converted |
Template text |
A |
Because a page in the usual syntax has all of its template text collapsed into jsp:text elements, a validator sees no
detail about its HTML. A JSP document keeps its template elements visible, so a validator (or an XSLT transformation)
can check, for example, that a page contains exactly one h1. That is the actual advantage of writing in XML.
jsp:id is an attribute that the container must support on every element of the view, in the namespace of the JSP
standard actions with any prefix that the container picks, and it is unique within a view. A validator puts it in
ValidationMessage.getId() so the container can map the problem back to a location. It is not available to page authors.
| Attribute value in the page | XML view | Result when ${foo} is [bar] |
|---|---|---|
|
|
|
|
|
|
|
|
|
Seeing the XML View: a Validator That Dumps It
The best way to understand the mapping is to look at it. The validator below writes the XML view of every page that imports
its library to a file; it accepts every page by returning null. It is a debugging aid only: no production code should
write files from a validator.
package org.example.bookshelf;
import jakarta.servlet.jsp.tagext.PageData;
import jakarta.servlet.jsp.tagext.TagLibraryValidator;
import jakarta.servlet.jsp.tagext.ValidationMessage;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class XmlViewDumper extends TagLibraryValidator {
@Override
public ValidationMessage[] validate(String prefix, String uri, PageData page) {
try (InputStream in = page.getInputStream()) {
String xml = new String(in.readAllBytes(), StandardCharsets.UTF_8);
Files.writeString(Path.of(System.getProperty("java.io.tmpdir"), "xmlview-" + prefix + ".xml"), xml);
} catch (IOException e) {
return new ValidationMessage[] {new ValidationMessage(null, e.toString())};
}
return null;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-jsptaglibrary_4_0.xsd"
version="4.0">
<tlib-version>1.0</tlib-version>
<short-name>xv</short-name>
<uri>https://example.org/bookshelf/xmlview</uri>
<validator>
<validator-class>org.example.bookshelf.XmlViewDumper</validator-class>
</validator>
</taglib>
A page opts in by importing the library, with a taglib directive in the usual syntax or with xmlns:xv in a document. The
XML view of books-xml.jspx (the document from the start of the page, without the jsp:output) is:
<?xml version="1.0" encoding="UTF-8" ?>
<jsp:root
version="2.0"
xmlns:jsp="http://java.sun.com/JSP/Page"
jsp:id="0"
>
<jsp:directive.page
jsp:id="1"
pageEncoding="UTF-8"
contentType="application/xhtml+xml; charset=UTF-8"
/>
<html
xmlns:xv="https://example.org/bookshelf/xmlview"
xmlns:c="jakarta.tags.core"
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns="http://www.w3.org/1999/xhtml"
jsp:id="2"
>
...
<c:forEach
items="${books}"
var="b"
jsp:id="8"
>
<li
jsp:id="9"
>
${b.title}
...
</li>
</c:forEach>
...
</html>
</jsp:root>
The jsp:root, the jsp:id attributes and the forced pageEncoding="UTF-8" are the steps of the transformation, and the
template elements (html, head, body, li) are still elements. Compare the XML view of the usual-syntax books.jsp
of the Getting Started page, which imports the same library:
<?xml version="1.0" encoding="UTF-8" ?>
<jsp:root
version="2.0"
xmlns:c="jakarta.tags.core"
xmlns:xv="https://example.org/bookshelf/xmlview"
xmlns:jsp="http://java.sun.com/JSP/Page"
jsp:id="0"
>
<jsp:directive.page
jsp:id="1"
pageEncoding="UTF-8"
contentType="text/html; charset=UTF-8"
/>
<jsp:text
jsp:id="2"
>
<![CDATA[
]]>
</jsp:text>
...
<jsp:text
jsp:id="7"
>
<![CDATA[
<body>
]]>
</jsp:text>
...
<c:forEach
var="b"
items="${books}"
jsp:id="11"
>
...
<jsp:text jsp:id="14">${b.title}</jsp:text>
...
<c:out
value="${b.author}"
jsp:id="16"
/>
...
</c:forEach>
...
</jsp:root>
The two taglib directives became xmlns attributes on the root, each line of the HTML became a jsp:text, the EL
expression got its own jsp:text, and the comment is gone. Two things in Tomcat 11.0.26’s views differ from the
specification text: the root says version="2.0" although section 10 does not fix a number, and the jsp:output of the
document is not in its view (it is consumed to generate the prolog). Treat the view as an implementation of the mapping, not
as a byte-exact format. Validators should parse it with a namespace-aware XML parser and not with string matching.
The Bookshelf Worked Example: Forbidding Scripting in the Whole Application
The scripting-invalid property group turns scripting off for a set of files, one URL pattern at a time. The XML view lets an
application go further and enforce its own rule in one place, with an error message of its own and with a line reference.
The validator below parses the view with a namespace-aware SAX parser and reports every jsp:declaration, jsp:scriptlet
and jsp:expression, whichever syntax the page used. The validator reports the container-assigned jsp:id as the location.
package org.example.bookshelf;
import jakarta.servlet.jsp.tagext.PageData;
import jakarta.servlet.jsp.tagext.TagLibraryValidator;
import jakarta.servlet.jsp.tagext.ValidationMessage;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
public class NoScriptingValidator extends TagLibraryValidator {
private static final String JSP_NS = "http://java.sun.com/JSP/Page";
@Override
public ValidationMessage[] validate(String prefix, String uri, PageData page) {
List<ValidationMessage> problems = new ArrayList<>();
try (InputStream in = page.getInputStream()) {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.newSAXParser().parse(in, new DefaultHandler() {
@Override
public void startElement(String ns, String local, String qName, Attributes atts) {
if (JSP_NS.equals(ns)
&& (local.equals("scriptlet") || local.equals("expression") || local.equals("declaration"))) {
problems.add(new ValidationMessage(atts.getValue(JSP_NS, "id"),
"Scripting element jsp:" + local + " is not allowed"));
}
}
});
} catch (Exception e) {
problems.add(new ValidationMessage(null, e.toString()));
}
return problems.isEmpty() ? null : problems.toArray(new ValidationMessage[0]);
}
}
The TLD is the same shape as xmlview.tld, with the class name replaced and <uri>https://example.org/bookshelf/no-scripting</uri>.
A clean document and two offenders, one in each syntax, show the effect:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="jakarta.tags.core"
xmlns:ns="https://example.org/bookshelf/no-scripting">
<body><c:forEach var="b" items="${books}"><p>${b.title}</p></c:forEach></body>
</html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:ns="https://example.org/bookshelf/no-scripting">
<body><p><jsp:expression>1 + 1</jsp:expression></p></body>
</html>
<%@ taglib prefix="ns" uri="https://example.org/bookshelf/no-scripting" %>
<html><body><p><%= 1 + 1 %></p></body></html>
validated-ok.jspx 200 <html xmlns="http://www.w3.org/1999/xhtml"><body><p>Dune</p><p>Neuromancer</p></body></html>
validated-bad.jspx 500 Validation error messages from TagLibraryValidator for [ns] in [/WEB-INF/views/validated-bad.jspx]: 5: Scripting element jsp:expression is not allowed
validated-bad.jsp 500 Validation error messages from TagLibraryValidator for [ns] in [/WEB-INF/views/validated-bad.jsp]: 6: Scripting element jsp:expression is not allowed
Tomcat printed the jsp:id (5 and 6) as the location, and rejected the page when it was translated, so the error
appears on the first request of a page, or at precompilation. The rule applies to a page
only if the page imports the library, so a real application would add the library to every view through a prelude in a
property group (include-prelude). The parsing uses plain JAXP, and the same view could be handed to an XML Schema validator or an XSLT stylesheet.
Security Notes
-
Escape dynamic data. A JSP document is not an XML serializer. EL in text or in an attribute is inserted as is, so a title such as
</p><script>breaks the structure and can inject markup, exactly as in the usual syntax. Usec:out, which escapes by default, orfn:escapeXmlaround every value that is not trusted, in text and in attributes. -
A broken document is a denial of service for the XML consumer. Unescaped
&and<make the output ill-formed, and a client that insists on well-formed XML shows an error instead of the page. -
Keep documents under
WEB-INF. Same rule as for pages in the usual syntax: a.jspxunder the web root can be requested directly; a view under/WEB-INF/views/can only be reached through the servlet. -
DOCTYPE and entities. Leave the DOCTYPE out of source files unless a trusted internal subset is needed. A DOCTYPE turns on validation and entity resolution in the container’s parser, and any external entity is a way for a document to make the server read a resource. Documents are written by developers, not users, so the risk is a supply-chain one: review
.jspxfiles from third parties as code. -
Scripting.
jsp:scriptletis Java code, and the security properties of scriptlets are those of the usual syntax; see the validator above.
Pitfalls and Error Names
| Symptom or error | Cause | Fix |
|---|---|---|
|
A |
Use |
|
The document uses a prefix without an |
Declare |
|
An HTML-style element that is not closed |
Write |
|
A |
Use |
Output contains |
Entities are decoded at parse time and the output is not re-escaped |
Emit |
|
A source DOCTYPE turned on validation, and the DTD does not declare the elements or the |
Remove the DOCTYPE and generate one with |
|
|
Rename the file to |
|
|
Use |
|
Incomplete |
Supply the system literal (and the root element) |
Browser shows a tree of XML instead of the page |
The default content type of a JSP document is |
|
Expected whitespace is missing |
Whitespace-only text nodes are dropped |
Put it in |
A page named |
A property group with |
Check |
Unknown tag silently printed as markup (observed) |
The |
Check the URI against the TLD |
What Changed Recently
| Release | Change |
|---|---|
JSP 1.2 |
|
JSP 2.0 |
|
JSP 2.1 |
The sequence |
Jakarta Pages 3.0 (EE 9) |
Namespace move from |
Jakarta Pages 3.1 |
|
Jakarta Pages 4.0 (EE 11) |
Java 17 minimum; |
Jakarta Tags 3.0 |
The core library URI is |
The XML syntax itself has been stable since JSP 2.0. Section 6.5 of the specification, a non-normative section, still lists two wished-for features that nobody has implemented: producing XML natively (a node tree instead of text) and XML Schema and XInclude support.
Where the Books Are Outdated
Web Development with JavaServer Pages (Manning, 2001, listed in the section bibliography) treats JSP documents and the XML view in its chapter on validating custom tag libraries, and JSTL in Action (Manning, 2003) spends its early chapters on the XML rules that JSP syntax follows. Both were written for JSP 1.2.
| The book’s era | Today (Pages 4.0) |
|---|---|
|
Optional; libraries can be declared where they are used; |
XML syntax is an alternative that most containers did not complete |
Part of the specification, with |
Taglib URIs such as |
|
|
Same API, in |
|
|
TLDs with a JSP 1.2 DTD |
XML Schema TLDs ( |
Generating XML with scriptlets and |
Documents generate XML directly, and JSTL and tag files cover the rest |
XHTML delivered with the usual syntax and a |
|
References
-
Jakarta Server Pages 4.0 specification: sections 1.10.5, 3.3.1, 3.3.3, 3.3.7, 3.3.9, 4.2, 4.4, 5.12, 5.13, 6.1 to 6.5, 7.4.1.2, 8.6, 10.1, 10.2, appendices B and C
-
Jakarta Pages 4.0 — 6.1 Overview of JSP Documents and of XML Views
-
Jakarta Pages 4.0 — 6.3.1 Namespaces, Standard Actions, and Tag Libraries
-
Jakarta Pages 4.0 — 6.2.2 Overview of Syntax of JSP Documents
-
Jakarta Pages 4.0 — 6.4.3 Example: Generating non-XML documents
-
Jakarta Pages 4.0 — 10.1.11 Request-Time Attribute Expressions
-
Jakarta Pages 4.0 — 3.3.9 Removing Whitespaces from Template Text
-
Jakarta Pages 4.0 — 10.1 XML View of a JSP Document, JSP Page or Tag File
-
Jakarta Pages 4.0 — 10.2 Validating an XML View of a JSP page
-
Jakarta Pages 4.0 — 10.1.2 JSP Pages or Tag Files in JSP Syntax
-
Jakarta Pages 4.0 — 6.5 Possible Future Directions for JSP documents