Error Handling
|
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 (section 1.4 "Error Handling" and the ErrorData Javadoc), Jakarta Expression
Language 6.0, Jakarta Standard Tag Library 3.0 and Jakarta Servlet 6.1 (section 10.9 "Error Handling" and the deployment
descriptor), running on Apache Tomcat 11.0.26 with Java 21. Everything was verified on 2026-10-08: the facts against the
specifications, their Javadoc and the Tomcat 11.0 documentation, and every example by building it into a Maven WAR, deploying
it as g3errors.war on Tomcat 11.0.26 and calling it with curl; the outputs shown are the outputs that run produced
(headers that do not matter, such as Set-Cookie and Date, and blank lines are left out). A page that fails is a fact of life: a
database goes away, a parameter is not a number, a book is withdrawn. What matters is what the visitor sees, what the
operator finds in the log, and whether the failure leaks anything an attacker can use. The running scenario is the
Bookshelf, a small catalogue: its detail page /book?id=... is forwarded to a JSP, and the page grows from "print a
stack trace" to an error page that shows a reference number, logs the details once and escapes everything it prints.
Where an Error Can Happen
Section 1.4 of the Pages specification separates two moments, and a third one appears in practice. The table lists them with what each produces and what the specification promises.
| Moment | Examples | What the container does | Where the specification speaks |
|---|---|---|---|
Translation time |
Unterminated tag, unknown directive attribute, |
A fatal translation failure makes every later request for that page fail; for HTTP the status is 500. If translation happens before the first request, notification is implementation dependent |
1.4.1 |
Request time, before the buffer is flushed |
An uncaught |
The request and the exception are forwarded to the |
1.4.2 |
Request time, after the buffer was flushed |
The same exceptions, but the response is already committed |
A dispatch to an error page "may fail"; the status line has been sent and cannot change |
|
The two mechanisms that can answer a failure are the JSP page directive (errorPage, per page) and the deployment
descriptor (<error-page>, per application, by status code or exception type). The diagram shows how Tomcat 11 chooses between
them for a request that reaches a JSP. The text of the specification is: if the page defines an error page through the page
directive, the error pages of web.xml are not used for that page.
The branch "translation error" goes to web.xml because the errorPage attribute of a page that did not compile does not exist at
run time; the example in Translation-Time Errors shows it.
The Bookshelf Used on This Page
All the examples are files of one WAR, deployed with the context path /g3errors. The Java classes are in the package
org.example.bookshelf (imports and the package line are left out of the listings). The JSPs live under WEB-INF/views,
so they are reachable only through a servlet forward, and the error pages under WEB-INF/views/errors. The pom.xml is the
one from the Getting Started page of this section (Jakarta Servlet 6.1, Pages 4.0 and EL 6.0 with scope provided, JSTL 3.0 in
the WAR).
| File | Mapping or role | Concept |
|---|---|---|
|
|
forwards to |
|
|
page with |
|
|
the same page without |
|
target of |
|
|
|
status-code, exception-type and |
|
|
|
|
|
committed response, self-referencing error page, translation errors |
|
|
the scriptlet "before" snippet |
|
|
|
|
application scope |
the model, the exception, one-time logging |
The model is a catalogue whose find method throws a runtime exception for an unknown id, and a listener that puts the
catalogue and the logger into application scope.
public class BookNotFoundException extends RuntimeException {
public BookNotFoundException(long id) {
super("No book with id " + id);
}
}
public class Catalog {
private final List<Book> books = List.of(
new Book(1, "Dune", "Frank Herbert", 1965),
new Book(2, "Neuromancer", "William Gibson", 1984));
public Book find(long id) {
return books.stream().filter(b -> b.id() == id).findFirst()
.orElseThrow(() -> new BookNotFoundException(id));
}
}
errorPage and isErrorPage
Two attributes of the page directive connect a failing page to the page that reports the failure.
| Attribute | Meaning | Default and rules |
|---|---|---|
|
URL of the resource that receives any |
The default is implementation dependent. If the URL names a JSP, that page’s |
|
Marks the page as a possible target of another page’s |
|
|
Response buffer size; interacts with errors because only an uncommitted response can be redirected to an error page |
|
The failing page and the reporting page of the Bookshelf are two files. The first calls a method of the catalogue from EL, so an unknown id throws.
<%@ page errorPage="/WEB-INF/views/error.jsp" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<c:set var="book" value="${catalog.find(param.id)}"/>
<!DOCTYPE html>
<html>
<body>
<h1><c:out value="${book.title}"/></h1>
<p>by <c:out value="${book.author}"/> (${book.year})</p>
</body>
</html>
A normal request renders the book; an unknown id sends the request and the exception to error.jsp, which the next sections
write. The status of the response is 500, as the specification prescribes for an error page:
curl -si 'http://localhost:18080/g3errors/book?id=1' # 200, <h1>Dune</h1>
curl -si 'http://localhost:18080/g3errors/book?id=99' # 500, the error page below
The isErrorPage rule is enforced when the page is translated. A page that is not an error page and prints exception does not
compile; Tomcat reports exception cannot be resolved to a variable and the visitor gets the error page of web.xml.
exception without isErrorPage (translation error, observed)<%@ page contentType="text/html; charset=UTF-8" %>
<html><body><%= exception %></body></html>
Getting at the Exception: exception, ErrorData and Request Attributes
An error page has four ways to see what went wrong. They are not equivalent, and one of them, the exception variable, is
not available in EL at all.
| Expression | Type | Notes |
|---|---|---|
|
|
Defined only when |
|
|
|
|
|
The |
|
|
The raw request attribute from the Servlet specification. |
${exception} in EL is empty even on a page with isErrorPage="true": the table 1-7 variable is a scripting variable, and the
JSP expression-language resolvers do not know a name exception. On Tomcat 11.0.26 the expression printed nothing, while
${pageContext.exception} printed the exception class. Use pageContext.
The class of the exception is not always the class you threw. When an EL method call throws, Jasper wraps the exception in a
org.apache.jasper.el.JspELException whose cause is yours, and a servlet that forwards to the JSP sees that exception rethrown
as a jakarta.servlet.ServletException. The error page below prints the chain so that the layers are visible:
book.jsp?id=99 chain, as printed by error.jsp (observed)throwable: org.apache.jasper.el.JspELException
pageContext.exception: org.apache.jasper.el.JspELException
cause: org.example.bookshelf.BookNotFoundException
root cause:
A scriptlet that calls catalog.find directly throws BookNotFoundException itself, with no wrapper. Code that wants the
original exception walks getCause() until it reaches the application’s own type.
Writing ${exception.class.name} to print the class does not parse on Tomcat 11.0.26: the page fails with a
JasperException "Failed to parse the expression". Use the bracket form ${exception['class'].name} or call
${exception.getClass().getName()}; both printed the name in the test. This matters because many error-page examples written
for older containers use the dot form.
ErrorData
jakarta.servlet.jsp.ErrorData is an immutable bean built from the Servlet error attributes and exposed as
pageContext.errorData (PageContext.getErrorData()). It is meaningful only on an error page. Because the properties are plain
getters, EL reads them directly, which the specification gives as the reason for the class: ${pageContext.errorData.statusCode}.
| EL property | Java method | Value | Since and notes |
|---|---|---|---|
|
|
The status code of the error; 500 for an uncaught exception |
JSP 2.0 |
|
|
The request URI. On |
JSP 2.0 |
|
|
The name of the servlet invoked: |
JSP 2.0 |
|
|
The |
JSP 2.0 |
|
|
The request method; printed |
JSP 4.0: new |
|
|
The raw query string, or |
JSP 4.0: new |
The constructor ErrorData(Throwable, int, String, String) is deprecated for removal since 4.0 and replaced by
ErrorData(Throwable, int, String, String, String, String) with the method and the query string. Only containers construct
ErrorData; application code never calls it.
Writing an Error Page: Reference, Log, Escape, Do Not Leak
An error page is the one page an attacker can trigger on demand, so it follows four rules: show the visitor a short message and a reference number, log the details once on the server, escape everything that came from the request, and never print a stack trace or an exception message. The Bookshelf puts the logging in a small application-scoped object so that it is shared by every error page and testable.
public class ErrorLog {
private static final Logger LOG = Logger.getLogger(ErrorLog.class.getName());
public String report(ErrorData data) {
String ref = UUID.randomUUID().toString().substring(0, 8);
String line = "error ref=%s status=%d method=%s uri=%s query=%s servlet=%s".formatted(
ref, data.getStatusCode(), data.getMethod(), data.getRequestURI(),
data.getQueryString(), data.getServletName());
if (data.getThrowable() != null) {
LOG.log(Level.SEVERE, line, data.getThrowable());
} else {
LOG.log(Level.WARNING, line);
}
return ref;
}
}
The error page calls it from EL, prints only escaped values, and keeps the diagnostic list short so that the example shows each
ErrorData property; a production page shows only the heading and the reference.
errorPage<%@ page isErrorPage="true" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<c:set var="ref" value="${errorLog.report(pageContext.errorData)}"/>
<!DOCTYPE html>
<html>
<body>
<h1>Something went wrong (<c:out value="${pageContext.errorData.statusCode}"/>)</h1>
<p>Quote reference <strong><c:out value="${ref}"/></strong> when you contact support.</p>
<ul>
<li>method: <c:out value="${pageContext.errorData.method}"/></li>
<li>uri: <c:out value="${pageContext.errorData.requestURI}"/></li>
<li>query: <c:out value="${pageContext.errorData.queryString}"/></li>
<li>servlet: <c:out value="${pageContext.errorData.servletName}"/></li>
<li>throwable: <c:out value="${pageContext.errorData.throwable['class'].name}"/></li>
<li>pageContext.exception: <c:out value="${pageContext.exception['class'].name}"/></li>
</ul>
</body>
</html>
The visible result for a missing book, and the single log line that accompanied it:
$ curl -si 'http://localhost:18080/g3errors/book?id=99'
HTTP/1.1 500
Content-Type: text/html;charset=UTF-8
<h1>Something went wrong (500)</h1>
<p>Quote reference <strong>b009b2f8</strong> when you contact support.</p>
<li>method: GET</li>
<li>uri: /g3errors/WEB-INF/views/book.jsp</li>
<li>query: id=99</li>
<li>servlet: jsp</li>
<li>throwable: org.apache.jasper.el.JspELException</li>
# Tomcat log, one entry with the stack trace under it:
SEVERE [http-nio-18080-exec-280] org.example.bookshelf.ErrorLog.report error ref=b009b2f8 status=500 method=GET uri=/g3errors/WEB-INF/views/book.jsp query=id=99 servlet=jsp
A request such as ?id=%3Cscript%3Ealert(1)%3C/script%3E is a hostile value. getQueryString() returns the raw string and the
error page printed it through c:out, so the page contained id=%3Cscript%3E... as inert text. Printing ${pageContext.errorData.queryString}
without c:out, or building a link from it, would be a reflected cross-site scripting hole; the same holds for requestURI,
which also comes from the request.
The status of a JSP error page is, by default, ${pageContext.errorData.statusCode}; the specification allows the page to set another
value, including 200. Do not: a 200 turns an error into a "soft error" that crawlers index and monitoring does not count.
Because the directive path is a forward (not an ERROR dispatch), a filter mapped to <dispatcher>ERROR</dispatcher> does not run for
pages that use errorPage, so a filter is the wrong place to log them. A bean called from the error page logs both paths, as long as
every error page calls it.
Error Pages in web.xml: Status Codes, Exception Types and the Default
When a page has no errorPage attribute, or the failure is not an exception of a JSP (sendError, a servlet exception, a 404), the
deployment descriptor decides. The container matches the failure to an <error-page>; the page given by <location> receives an
ERROR dispatch. The Servlet 6.1 rules, summarised:
| Failure | Matching rule |
|---|---|
|
An |
An exception or error that escapes |
An |
Neither fits |
The default entry, an |
Several entries |
Entries must be unique by status code and by exception class name |
Dispatch from code |
The mechanism does not intervene when errors occur through |
The Bookshelf declares three entries:
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/views/errors/not-found.jsp</location>
</error-page>
<error-page>
<exception-type>org.example.bookshelf.BookNotFoundException</exception-type>
<location>/WEB-INF/views/errors/book-missing.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/WEB-INF/views/errors/generic.jsp</location>
</error-page>
The locations may be under WEB-INF, which keeps the error pages out of reach of a direct request. The three entries were
exercised with curl; the line that explains each result follows the output.
$ curl -s http://localhost:18080/g3errors/nothing # HTTP 404, the status-code page
<h1>Not found</h1>
<p>Nothing lives at <code>/g3errors/nothing</code>.</p>
<p>status=404 throwable= exception=</p>
$ curl -s http://localhost:18080/g3errors/missing # HTTP 500, matched by exception type
<h1>No such book</h1>
<p>No book with id 99</p>
<p>matched by type, exception=org.example.bookshelf.BookNotFoundException, status=500</p>
$ curl -s 'http://localhost:18080/g3errors/book-plain?id=99' # HTTP 500, no errorPage: the Throwable entry
<h1>Error 500</h1>
<p>Reference 64f5ca11. Throwable: jakarta.servlet.ServletException,
caused by org.example.bookshelf.BookNotFoundException,
The third result is a pitfall. /book-plain throws the same BookNotFoundException, but the exception reaches the container as a
ServletException that wraps the JSP failure. The first pass matches the java.lang.Throwable entry (every exception is a
Throwable), so the second, root-cause pass never happens and the specific BookNotFoundException page is not used. Either map
the wrapper types the JSP engine produces, give the JSP an errorPage, unwrap in a filter, or let the generic page branch on the
cause. A servlet that throws the exception itself (/missing) matches the specific entry, as the second result shows.
An error page reached from web.xml reads the same information. pageContext.errorData works on those pages too, and the request
attributes of the Servlet 6.1 error dispatch are visible; the page printed status=500, method=GET, uri=/g3errors/book-plain,
servlet=pages and type=class jakarta.servlet.ServletException for the third request. A POST to /book?id=99 printed
method: POST, because the request that failed was a POST while the error dispatch itself is always a GET.
The error attributes, the ERROR dispatcher type and the rule that an error page is invoked through a GET are explained with
their own listings on the error pages section of Servlet Fundamentals;
this page adds only what is specific to JSP.
| Situation | Handled by | Reason |
|---|---|---|
Uncaught exception, page has |
The page’s |
The directive wins; the |
Uncaught exception, page has no |
|
|
|
|
|
|
Tomcat’s own report |
|
Translation error |
|
|
c:catch: Handling a Failure Inside the Page
c:catch (see JSTL Core) catches any Throwable thrown by the actions nested in it and, optionally, stores it in a page-scoped variable. It is
the granular alternative: the specification says actions of central importance should not be wrapped, so that their exception
reaches the error page, while actions of secondary importance are wrapped so that they never invoke the error-page mechanism.
The variable has page scope, is removed if no exception occurred, and without var the exception is swallowed.
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html>
<body>
<c:catch var="problem">
<c:set var="book" value="${catalog.find(param.id)}"/>
<h1><c:out value="${book.title}"/></h1>
</c:catch>
<c:if test="${not empty problem}">
<p class="warning">That book is not available.</p>
</c:if>
</body>
</html>
With ?id=99 the response is a normal 200 page with the paragraph "That book is not available."; with ?id=1 it prints <h1>Dune</h1>.
A first version of the page also put the exception into an HTML comment for the developer; it printed the class name
(org.apache.jasper.el.JspELException), the source line of the page and the EL expression to anyone who viewed the page source.
Put diagnostics in the log, not in the response. c:catch has two more limits: everything that was already written stays written (the
partial output before the failure is not removed unless the buffer is reset), and a swallowed exception is never logged unless the page
logs it.
A Committed Response Cannot Be Redirected
The buffer attribute of the page directive sets the response buffer. Output is held until the buffer fills or the page ends; only
until then can the container still replace the response with an error page. After the buffer was flushed the status line and the
headers are on the wire. The specification says a later attempt to dispatch an uncaught exception to an error page "may fail".
<%@ page buffer="1kb" errorPage="/WEB-INF/views/error.jsp" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html>
<body>
<c:forEach begin="1" end="200" var="i">
<p>Row ${i} of the catalogue export</p>
</c:forEach>
<c:set var="late" value="${catalog.find(99)}"/>
</body>
</html>
The observed response was HTTP/1.1 200 with Transfer-Encoding: chunked and 7715 bytes: 181 of the 200 rows, flushed in 1 kB
chunks, then a second <html> document, the error page, appended after them. The client received a broken page with a success status. The
fixes are the default buffer (8 kB or more) for pages with a late failure, computing everything that can fail before the first byte is
written (load the data in the servlet, then forward), and never relying on the error page for a page that streams. Setting buffer="none"
makes it worse, because every byte is sent at once.
Translation-Time Errors
A JSP that does not compile has no running code, so its errorPage cannot apply. The visitor and the log see a
org.apache.jasper.JasperException.
errorPage is declared but cannot take effect)<%@ page errorPage="/WEB-INF/views/error.jsp" contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<html><body>
<c:forEach var="b" items="${catalog.books}">
<p>${b.title}</p>
</body></html>
On Tomcat 11.0.26 the log says broken.jsp (line: [6], column: [7]) Unterminated [<c:forEach] tag, the status is 500, and the page rendered
was the Throwable entry of web.xml (errors/generic.jsp, printing org.apache.jasper.JasperException), not error.jsp. In
production, precompile the JSPs at build time so that translation errors show up in the build instead of on the first request; the
Jasper configuration page of this section covers precompilation.
Error Pages That Fail or Refer to Themselves
An error page is itself a page and can fail. The specification requires the container to detect a self-referencing error page and
throw a translation error. The test below points the errorPage of a page at the page itself and makes the page fail.
errorPage is itself<%@ page errorPage="/WEB-INF/views/self.jsp" isErrorPage="true" contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<c:set var="x" value="${catalog.find(99)}"/>
<html><body>self</body></html>
Tomcat 11.0.26 did not report a translation error. The page compiled and ran; the failure forwarded to itself again and again until
the stack overflowed, and the container answered 500 with a ServletException caused by a java.lang.StackOverflowError, which web.xml
rendered through the Throwable page. With no exception thrown, a self-referencing page behaves normally (the first version of the test answered 200).
So keep error pages trivial: no calls that can throw, no errorPage of their own, and no dependency on the state that failed (a database,
the session).
The Container’s Own Error Report
When no error page matches, Tomcat writes its own HTML report through the ErrorReportValve. For sendError(410, "This book was withdrawn") it
printed the status, the type, the message, a description and the server version Apache Tomcat/11.0.26; for an exception it also prints
the stack trace unless the valve is configured otherwise. Both pieces are information an attacker uses: the product and version select
the exploit, the stack trace shows packages and file names. The valve has two switches (default true) and can also return static files:
| Attribute | Meaning | Default |
|---|---|---|
|
Whether the error report (message and stack trace) is in the HTML |
|
|
Whether the server version is in the HTML |
|
|
Path of a UTF-8 HTML file to return for status |
none |
|
Path of a UTF-8 HTML file for an exception class and its subclasses |
none |
Disabling both returns only the status code. The valve belongs to the host, normally declared in conf/server.xml:
<Valve className="org.apache.catalina.valves.ErrorReportValve" showReport="false" showServerInfo="false"/>
The shared Tomcat of this test could not be reconfigured, so the host-level snippet above was not run. What was run is the temptation
to keep the setting in the application: a META-INF/context.xml with the same <Valve> inside <Context>. It changed the report to the
bare "HTTP Status 418 – Unknown Reason" as intended, but it also made every <error-page> stop working: the 404 page, the exception pages and the
Throwable page were all replaced by the bare status page, because the context-level valve writes the report before the host dispatches
to the descriptor’s error pages. Configure the valve at the host, or leave the application’s <error-page> entries to do the work and make sure
every status and the Throwable has one, including a default entry.
The Scriptlet Way and Why It Leaks
Older JSP code prints the exception from a scriptlet. This is a "before" snippet kept only to recognise it in legacy code.
<%@ page isErrorPage="true" import="java.io.PrintWriter" contentType="text/html; charset=UTF-8" %>
<html><body>
<h1>Oops</h1>
<pre><% exception.printStackTrace(new PrintWriter(out)); %></pre>
</body></html>
With /legacy?id=99 the page answered 500 and printed 3204 bytes: the exception message, the application’s class names and line numbers
(Catalog.java:16, legacy_jsp.java:123), and the Jasper and Tomcat internals. The replacement is the
page above: the reference id for the visitor, ErrorLog for the operator. A scriptlet that reads exception gets the
BookNotFoundException itself, with no JspELException wrapper, because the exception was thrown by the scriptlet code and not by EL.
Errors in Struts
Struts 7 does not use errorPage. Action exceptions are handled by exception-mapping elements and the exception interceptor, and
validation errors by field and action errors; see Exception Handling and Global Results and Validation in the Struts part of this section. The JSP side of a Struts application
is still the same: a JSP result that can fail has the same errorPage, web.xml and escaping rules as above, and the container error pages
catch what the framework does not.
Pitfalls and Error Names
| Symptom or error | Cause | Fix |
|---|---|---|
Empty output where |
|
|
|
The dot form |
|
|
|
Set |
The |
EL wraps it in |
Map the wrapper types, add |
Two documents in one response and a 200 status (observed) |
The failure happened after the buffer was flushed |
Compute first, forward later; keep the default buffer; do not stream from a JSP |
The |
The page did not compile, so its directive does not exist at run time |
Precompile; keep a default |
Endless forward ending in |
The error page’s own |
Keep error pages trivial and without |
Stack trace, class names or |
Scriptlet |
Reference id plus log; |
|
|
|
All |
A context-level |
Configure the valve at the host |
|
The error is reported for the forward target |
Log the original request from a filter or |
An error filter never sees my JSP failure |
The |
Log from the error page, as |
A 404 or 500 answered with status 200 |
The error page set the status, or a proxy replaced it |
Leave the status the container set |
|
The response was already committed |
Same cure as the committed-response example |
What Changed Recently
-
Jakarta Pages 4.0 (Jakarta EE 11):
ErrorDatagainedgetMethod()andgetQueryString()and a six-argument constructor, matching the new Servlet error attributesjakarta.servlet.error.methodandjakarta.servlet.error.query_string; the four-argument constructor is deprecated for removal (change log items "Update ErrorData to add support for the new attribute…"). -
Jakarta Servlet 6.1: introduces
RequestDispatcher.ERROR_METHODandERROR_QUERY_STRING(both "Since: Servlet 6.1" in the Javadoc). The specification’s Table 10-1 does not listjakarta.servlet.error.method; the Javadoc and change log do. -
Jakarta Pages 3.1 and 4.0:
errorOnELNotFound(3.1) turns an unknown EL identifier into aPropertyNotFoundException, which then goes through the error-page mechanism; 4.0 removed the deprecatedisThreadSafeattribute, thejsp:pluginaction and other items unrelated to errors. -
EL 6.0: the Tomcat 11.0.26 observations about
.classand about wrapped exceptions apply to this stack; re-test when changing containers. -
Jakarta EE 9 and later: every
javax.servlet.error.*attribute isjakarta.servlet.error.*.
Where the Books Are Outdated
The JSP 1.2 and JSTL 1.0 books cover the same ideas, and the ideas still hold: an errorPage attribute, an isErrorPage attribute, a
c:catch for the page-local failure, error pages that collect diagnostics, and pageContext.exception for JSTL pages. What is different
today:
| Older material | Today |
|---|---|
|
|
Error page prints |
Never; show a reference and log the details. Containers add their own report unless it is turned off |
|
Only when |
|
Failed to parse on Tomcat 11.0.26; use the bracket form |
|
Six, with |
|
Same elements, but the descriptor uses the Jakarta EE namespace and |
Error pages in the public web root |
Put them under |
Scriptlet error handling, |
|