This is because the ViewExpiredException
is been wrapped in a ServletException
as per the JSF specification. Here's an extract of chapter 10.2.6.2 of the JSF 1.2 specification:
10.2.6.2 FacesServlet
Call the execute()
method of the saved Lifecycle
instance, passing the
FacesContext
instance for this request as a parameter. If the execute()
method
throws a FacesException
, re-throw it as a ServletException
with the
FacesException
as the root cause.
How the error pages are allocated is specified in Servlet API specification. Here's an extract of chapter 9.9.2 of Servlet API specification 2.5:
SRV.9.9.2 Error Pages
If no error-page
declaration containing an exception-type
fits using the
class-hierarchy match, and the exception thrown is a ServletException
or
subclass thereof, the container extracts the wrapped exception, as defined by
the ServletException.getRootCause
method. A second pass is made over the error
page declarations, again attempting the match against the error page
declarations, but using the wrapped exception instead.
In class hierarchy, ServletException
already matches Throwable
(even when its entry is removed, the status code would also already match 500
), so its root cause won't be extracted for the second pass.
To prove this specified behaviour, replace javax.faces.application.ViewExpiredException
by javax.servlet.ServletException
and retry. You'll see the expected error page being displayed.
Easiest way to workaround this is to create a Filter
which listens on an url-pattern
of /*
and does basically the following:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
try {
chain.doFilter(request, response);
} catch (ServletException e) {
Throwable rootCause = e.getRootCause();
if (rootCause instanceof RuntimeException) { // This is true for any FacesException.
throw (RuntimeException) rootCause; // Throw wrapped RuntimeException instead of ServletException.
} else {
throw e;
}
}
}
Update: as per the (deleted) comment of the OP: to reliably test this you cannot do a throw new ViewExpiredException()
in a bean constructor or method or so. It would in turn get wrapped in some EL exception. You can eventually add a debug line printing rootCause
in the Filter
to see it yourself.
If you're using Eclipse/Tomcat, a quick way to test ViewExpiredException
is the following:
- Create a JSF page with a simple command button, deploy and run it and open it in webbrowser.
- Go back to Eclipse, rightclick Tomcat server and choose Clean Tomcat Work Directory. This will restart Tomcat and trash all serialized sessions (important! just restarting Tomcat is not enough).
- Go back to webbrowser and press the command button (without reloading page beforehand!).