18

I'm working on a JSF web application in which I need to bring up a "Session Expired" page if the view expires, but a general technical error page for all others. The application only goes to the technical error page when I trigger the exception. Here's the error-page definitions:

<error-page> 
    <exception-type>javax.faces.application.ViewExpiredException</exception-type> 
    <location>/jsps/utility/sessionExpired.jsp</location> 
</error-page> 
<error-page> 
    <exception-type>java.lang.Throwable</exception-type> 
    <location>/jsps/utility/technicalError.jsp</location> 
</error-page> 
<error-page>
    <error-code>500</error-code>
    <location>/jsps/utility/technicalError.jsp</location>
</error-page>

I removed the technicalError.jsp error page elements and it worked fine, but when I put them back I can't get to the sessionExpired.jsp page. How do I tell the web container the order to evaluate these tags so that the right page comes up? Thanks.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Luke
  • 181
  • 1
  • 1
  • 3

2 Answers2

17

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, 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 as <exception-type> and retry. You'll see the expected error page being displayed.

To solve this, simply remove the error page on java.lang.Throwable or java.lang.Exception. If no one exception specific error page matches, then it will fall back to the one for error code of 500 anyway. So, all you need is this:

<error-page> 
    <exception-type>javax.faces.application.ViewExpiredException</exception-type> 
    <location>/jsps/utility/sessionExpired.jsp</location> 
</error-page> 
<error-page>
    <error-code>500</error-code>
    <location>/jsps/utility/technicalError.jsp</location>
</error-page>

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:

  1. Create a JSF page with a simple command button, deploy and run it and open it in webbrowser.
  2. 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).
  3. Go back to webbrowser and press the command button (without reloading page beforehand!).
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks for the detailed explanation. Unfortunately, the solution does not work. The technical error pages comes up instead of session expired page. I terminated the session by restarting the application in the middle of a session. I found that I can do a response.sendRedirect in that try block instead of throwing a runtime exception, but then I'm hard-coding a path to a view instead of letting the container control it. Not preferred, but I don't know what else to do. – Luke Jul 09 '10 at 17:39
  • This works fine here on Tomcat 6.0.20. What servletcontainer are you using? What rootCause are you getting? – BalusC Jul 09 '10 at 17:50
  • I didn't mention that I cannot remove the block for the error code 500. It's part of a coding standard in my shop. – Luke Jul 09 '10 at 17:54
  • Websphere Application Server v6.1. – Luke Jul 09 '10 at 17:54
  • I'm getting the expected root cause, ViewExpiredException. It seems like everything *should* be working, but the container is just not "prioritizing" the errors correctly. – Luke Jul 09 '10 at 17:55
  • I really appreciate your help so far; it's been a privilege to learn from such a knowledgeable person. – Luke Jul 09 '10 at 18:44
  • Websphere, sorry, I don't have that running here right now. I did another test in Glassfish to be sure and it works there as well. Seems to be a Websphere specific issue now .. Try upgrading .. What's the behaviour when you replace `ViewExpiredException` in `` by `ServletException`? – BalusC Jul 09 '10 at 18:55
  • I would just get the technical error page. I've decided to forego letting the container handle it and just do a redirect. Since the issue is WebSphere specific and I'm forced to use it, that's the best I'm going to do. – Luke Jul 14 '10 at 21:12
  • For those in my position looking for more information on implementing BalusC's filter suggestion, this was quite useful: http://javaboutique.internet.com/tutorials/Servlet_Filters/ – volvox Jan 18 '11 at 17:08
  • @Mark: see also the `[servlet-filters]` tag info page: http://stackoverflow.com/tags/servlet-filters/info – BalusC Jan 18 '11 at 17:30
  • This solution is great, but for runtime exceptions only. What about checked exceptions? They still are falling back to ServletException match... – Pavel S. Nov 05 '14 at 08:19
  • @PavelS: use OmniFaces `FacesExceptionFilter` instead: http://showcase.omnifaces.org/filters/FacesExceptionFilter – BalusC Nov 05 '14 at 08:29
  • @BalusC Thanks for your reply! I am already using the OmniFaces filter. It does only part of the job unwrapping from FacesException my custom checked exception. Since the above solution makes no effect, a `ServletException` with my checked exception as its cause is coming to error page matchings where `Throwable` or `Exception` fallback picks up the `ServletException` itself, not concrete checked exception. My current solution is just to not use fallback declaration at all, having error page declarations for all possible concrete exceptions including checked ones... – Pavel S. Nov 05 '14 at 12:45
  • 1
    @PavelS: oh, that way. Just remove `java.lang.Throwable`/`java.lang.Exception` and keep `500` in as general fallback for other exceptions. See also the answer. – BalusC Nov 05 '14 at 12:47
  • @BalusC Thank you so much, having a `500` way went away from my mind. So, OmniFaces filter + no error page declarations for global exceptions + `500` general fallback is a nice way to go for me. – Pavel S. Nov 05 '14 at 12:57
  • @BalusC: Since ViewExpiredException has been wrapped in a ServletException and as there was no error-page declaration for ServletException in web.xml file for OP question, the container is going extract the wrapped exception(i.e ViewExpiredException ), by using the ServletException.getRootCause(). Why the root cause won't be extracted for the second pass? I am unable to understand this line of yours -[....In class hierarchy, ServletException already matches Throwable, so its root cause won't be extracted for the second pass....] – Farhan stands with Palestine Apr 11 '16 at 08:47
  • @Shirgill: ServletException is an instance of Throwable. – BalusC Apr 11 '16 at 08:48
  • @BalusC: Yes I know it very well, The Throwable class is the superclass of all errors and exceptions in the Java language. Request you to please elaborate on that line which I mentioned.Why the root cause won't be extracted for the second pass? – Farhan stands with Palestine Apr 11 '16 at 08:51
  • @Shirgill: Because there's already a match on first pass. There's (incorrectly) an error page configured for java.lang.Throwable and ServletException is an instance of Throwable. – BalusC Apr 11 '16 at 08:53
  • @BalusC: Since Throwable sits on the top of the exception & errors hierarrchy, can you give me an example when there will not be match on first pass. – Farhan stands with Palestine Apr 11 '16 at 08:55
  • @Shirgill: as answered, replace it by `500`. – BalusC Apr 11 '16 at 08:55
  • @BalusC: But if I recall it correctly, then in the stacktrace sometimes, I have seen ...javax.servlet.ServletException: ... for servlet .... for HTTP 500. Or am I completely wrong to assume that HTTP 500 is mapped/corresponds to ServletException in ServletAPI? I think this is the root cause which is confusing me. – Farhan stands with Palestine Apr 11 '16 at 09:03
  • @Shirgill: HTTP 500 is just an [error code](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html). In Java terms, it basically means "an uncaught exception has occurred on server side". The `` allows fine grained error page matching to exception types. If no one matches in the end, then error page associated with `` 500 will be used, if any. – BalusC Apr 11 '16 at 09:06
  • @BalusC: Thank you very much for elucidating it to the deep extent. – Farhan stands with Palestine Apr 11 '16 at 09:07
0

As mentioned by BylusC, deployment descriptor must not contain any error-page handler that will catch ViewExpiredException (wrapped inside ServletException) instead of the correct one - error-page for ViewExpiredException.

Do not forget to verify that server's deployment descriptor (e.g. TomEE/conf/web.xml) does not contain java.lang.Throwable or java.lang.Exception error-page definitions. Because these two web.xml are basically merged.

Yes - application's web.xml has precedence over server's web.xml, but if application's web.xml contains

  • error-page for 500 (/error.xhtml)

and server's web.xml for

  • 500 (/500.html) and for
  • Throwable (/bigerror.html)

then application context will contain merge of those two:

  • 500 (/error.xhtml)
  • Throwable (/bigerror.html)

and ViewExpiredExcpetion error handling will not work correctly.

sasynkamil
  • 859
  • 2
  • 12
  • 23