• cdi
  • components
  • contexts
  • converters
  • eventlisteners
  • exceptionhandlers
  • facesviews
  • filters
  • functions
  • managedbeans
  • renderkits
  • resourcehandlers
  • scripts
  • taghandlers
  • utils
  • validators
  • viewhandlers
-
  • FacesMessageExceptionHandler
  • FullAjaxExceptionHandler

The FullAjaxExceptionHandler will transparently handle exceptions during ajax requests exactly the same way as exceptions during synchronous (non-ajax) requests.

By default, when an exception occurs during a JSF ajax request, the enduser would not get any form of feedback if the action was successfully performed or not. In Mojarra, only when the project stage is set to Development, the enduser would see a bare JavaScript alert with only the exception type and message. It would make sense if exceptions during ajax requests are handled the same way as exceptions during synchronous requests, which is utilizing the standard Servlet API <error-page> mechanisms in web.xml.

Installation

This handler must be registered by a factory as follows in faces-config.xml in order to get it to run:

 <factory>
     <exception-handler-factory>org.omnifaces.exceptionhandler.FullAjaxExceptionHandlerFactory</exception-handler-factory>
 </factory>

Error pages

This exception handler will parse the web.xml and web-fragment.xml files to find the error page locations of the HTTP error code 500 and all declared specific exception types. Those locations need to point to Facelets files (JSP is not supported) and the URL must match the FacesServlet mapping (just mapping it on *.xhtml should eliminate confusion about virtual URLs).

The location of the HTTP error code 500 or the exception type java.lang.Throwable is required in order to get the FullAjaxExceptionHandler to work, because there's then at least a fall back error page whenever there's no match with any of the declared specific exceptions. So, you must at least have either

 <error-page>
     <error-code>500</error-code>
     <location>/WEB-INF/errorpages/500.xhtml</location>
 </error-page>

or

 <error-page>
     <exception-type>java.lang.Throwable</exception-type>
     <location>/WEB-INF/errorpages/500.xhtml</location>
 </error-page>

You can have both, but the java.lang.Throwable one will always get precedence over the 500 one, as per the Servlet API specification, so the 500 one would be basically superfluous.

The exception detail is available in the request scope by the standard Servlet error request attributes like as in a normal synchronous error page response. You could for example show them in the error page as follows:

 <ul>
     <li>Date/time: #{of:formatDate(now, 'yyyy-MM-dd HH:mm:ss')}</li>
     <li>User agent: #{header['user-agent']}</li>
     <li>User IP: #{request.remoteAddr}</li>
     <li>Request URI: #{requestScope['javax.servlet.error.request_uri']}</li>
     <li>Ajax request: #{facesContext.partialViewContext.ajaxRequest ? 'Yes' : 'No'}</li>
     <li>Status code: #{requestScope['javax.servlet.error.status_code']}</li>
     <li>Exception type: #{requestScope['javax.servlet.error.exception_type']}</li>
     <li>Exception message: #{requestScope['javax.servlet.error.message']}</li>
     <li>Stack trace:
         <pre>#{of:printStackTrace(requestScope['javax.servlet.error.exception'])}</pre>
     </li>
 </ul>

Exceptions during render response can only be handled when the javax.faces.FACELETS_BUFFER_SIZE is large enough so that the so far rendered response until the occurrence of the exception fits in there and can therefore safely be resetted. When rendering of the error page itself fails due to a bug in the error page itself and the response can still be resetted, then a hardcoded message will be returned informing the developer about the double mistake.

Error in error page itself

When the rendering of the error page itself failed due to a bug in the error page itself, then the FullAjaxExceptionHandler will reset the response and display a hardcoded error message in "plain text".

Normal requests

Note that the FullAjaxExceptionHandler does not deal with normal (non-ajax) requests at all. To properly handle JSF and EL exceptions on normal requests as well, you need an additional FacesExceptionFilter. This will extract the root cause from a wrapped FacesException and ELException before delegating the ServletException further to the container (the container will namely use the first root cause of ServletException to match an error page by exception in web.xml).

Customizing FullAjaxExceptionHandler

If more fine grained control is desired for determining the root cause of the caught exception, or whether it should be handled, or determining the error page, or logging the exception, then the developer can opt to extend this FullAjaxExceptionHandler and override one or more of the following protected methods:

Don't forget to create a custom ExceptionHandlerFactory for it as well, so that it could be registered in faces-config.xml. This does not necessarily need to extend from FullAjaxExceptionHandlerFactory.

Demo

This exception handler is also configured on this showcase web application. The following error pages are been configured on this showcase web application:

 <error-page>
     <exception-type>javax.faces.application.ViewExpiredException</exception-type>
     <location>/WEB-INF/errorpages/expired.xhtml</location>
 </error-page>
 <error-page>
     <exception-type>java.sql.SQLException</exception-type>
     <location>/WEB-INF/errorpages/database.xhtml</location>
 </error-page>
 <error-page>
     <exception-type>java.lang.RuntimeException</exception-type>
     <location>/WEB-INF/errorpages/bug.xhtml</location>
 </error-page>
Demo

The buttons in the below demo will each purposefully throw an exception. You'll see that an error page template is presented regardless of if it's an ajax request or not.

Also, if you wait 30 minutes or manually delete the JSESSIONID cookie or press the below "Invalidate session" button and then click any of the buttons on the demo, then you'll get a view expired exception which will also end up in a specific error page.

Source code
<p>
    The buttons in the below demo will each purposefully throw an exception. You'll see that an error page template
    is presented regardless of if it's an ajax request or not.
</p>
<h:form>
    <h:commandButton value="throw runtime exception on ajax request" action="#{exceptionBean.throwRuntimeException}">
        <f:ajax execute="@form" render="@form" />
    </h:commandButton>
</h:form>
<h:form>
    <h:commandButton value="throw runtime exception on normal request" action="#{exceptionBean.throwRuntimeException}" />
</h:form>
<h:form>
    <h:commandButton value="throw SQL exception on ajax request" action="#{exceptionBean.throwSQLException}">
        <f:ajax execute="@form" render="@form" />
    </h:commandButton>
</h:form>
<h:form>
    <h:commandButton value="throw SQL exception on normal request" action="#{exceptionBean.throwSQLException}" />
</h:form>
<h:form>
    <h:commandButton value="cause epic fail on ajax request" action="#{exceptionBean.throwEpicFailException}">
        <f:ajax execute="@form" render="@form" />
    </h:commandButton>
</h:form>
<p>
    Also, if you wait 
    #{of:formatNumber(session.maxInactiveInterval / 60, '#')}
    minutes or manually delete the <code>JSESSIONID</code> cookie or press the below "Invalidate session" button 
    and then click any of the buttons on the demo, then you'll get a view expired exception which will also end up 
    in a specific error page.
</p>
<p>
    <input type="button" value="Invalidate session"
        onclick="$.get('#{request.contextPath}/invalidatesession?#{now.time}', alert('Session invalidated!'))" />
</p>