Technology Blog

Handling JSF and Facelets exceptions

Posted by Javier Ochoa

Oct 5, 2009 5:29:00 AM

Say you need to hide internal application errors to your users in a friendly way and also notify the support staff of this exceptional condition. In this post you'll find a way to do that in a JSF web app. The JSF flavor I'm using is Apache MyFaces 1.1 JSF implementation with the Tomahawk component which provides some extra functionality on top of JSF.

The example I created contains the following:

  • Four xhtml files: contact.xhtml, error.xhtml, raiseExceptions.xhtml, renderingException.xhtml
  • Three backing beans: ContactSupport, ErrorDisplay and ExceptionBean.
  • Two user defined exceptions: AppException and UserAccessException.
  • One Facelets-ViewHandler class, which extends and overrides "handleRenderException" method.

The goal is to catch all kinds of exceptions from the application and treat them differently depending on what kind of exception it is: application, user access, rendering, dbaccess, etc. The exception caught is analyzed and then the either a contact form is shown or an error is displayed. Let's see the steps to accomplish this:

1. Since this is a web app we can let the web container know how to handle error pages for the exception type or error code in the deployment descriptor (web.xml):

1 <!-- Internal server error -->
2 <error-page>
3 <error-code>500</error-code>
4 <location>/error.jsf</location>
5 </error-page>
6 <!-- All exceptions -->
7 <error-page>
8 <exception-type>java.lang.Throwable</exception-type>
9 <location>/error.jsf</location>
10 </error-page>

2. The error.jsf page has a backing bean ErrorDisplay which first loads the exception from FacesContext and second processes it differently depending the exception type:

a. Load the exception:

Here the idea is to try to get the exception first from Request map and if it is not there then Session map. We'll see why the session map is required later...

1 private static final String SERVLET_EXCEPTION_KEY = "javax.servlet.error.exception";
2 private void initializeException() {
3 FacesContext ctx = FacesContext.getCurrentInstance();
4 if (ctx.getExternalContext().getRequestMap().containsKey(
6 this.exception = (Throwable) ctx.getExternalContext()
7 .getRequestMap().remove(SERVLET_EXCEPTION_KEY);
8 } else if (ctx.getExternalContext().getSessionMap().containsKey(
10 this.exception = (Throwable) ctx.getExternalContext()
11 .getSessionMap().remove(SERVLET_EXCEPTION_KEY);
12 }
13 }

b. Process the exception:

Get the root exception from the stack using ExceptionUtils and decide whether this is an exception to show as an error or as a contact form. If the contact form is required, add the exception to the session map (line 9 in this listing).

1 Throwable rootException = ((Throwable) (ExceptionUtils
2 .getExceptions(this.exception).get(ExceptionUtils.getExceptions(
3 this.exception).size() - 1)));
5 if (rootException instanceof UserAccessException) {
6 this.title = "Invalid access";
7 this.message = rootException.getMessage();
8 } else {
9 FacesContext.getCurrentInstance().getExternalContext()
10 .getSessionMap().put(SERVLET_EXCEPTION_KEY,
11 this.exception);
12 try {
13 ((HttpServletResponse) (FacesContext.getCurrentInstance()
14 .getExternalContext().getResponse()))
15 .sendRedirect("contact.jsf");
16 } catch (IOException e) {
17 e.printStackTrace();
18 }
19 }

3. So far so good, now we have a way to handle the exception as an error (error.xhtml) if it is UserAccessException (line 5 in previous listing). Now let's look at the contact.jsf which shows the form to the user. This basically will pull out from the session map the exception and show the message in the details field of this bean:

1 if (FacesContext.getCurrentInstance().getExternalContext()
2 .getSessionMap().containsKey(SERVLET_EXCEPTION_KEY)) {
3 Throwable exception = (Throwable) FacesContext.getCurrentInstance()
4 .getExternalContext().getSessionMap().get(
6 this.details = ((Throwable) (ExceptionUtils
7 .getExceptions(exception).get(ExceptionUtils.getExceptions(
8 exception).size() - 1))).getMessage();
9 }

4. Ok now we have a way to handle our JSF application exceptions, but what about Facelet rendering problems? As these happen at the very end of the execution stack, they need a special treatment described in this post. Just be sure to use the same Key for the Session Map so it can be pulled out on the Contact page.

1 context.getExternalContext().getSessionMap().put(
2 "javax.servlet.error.exception", ex);
3 ((HttpServletResponse) context.getExternalContext().getResponse())
4 .sendRedirect("contact.jsf");

Of course you'll get the big picture by looking at the code available here

Topics: Agile and Development, Java