JSF Error Pages That Actually Work

By , 27 October 2012

JSF Error Pages That Actually Work
JSF Error Pages That Actually Work

Here is an annoying problem using JSF error pages for JSF requests. Everything looks just fine, HttpServletResponse.sendError() sends the error page, but JSF continues processing and starts throwing exceptions after the response is complete. This happens even if you call FacesContext.responseComplete(), and also when the error page is sent at different stages of the JSF lifecycle.

It seems like invoking the FacesServlet for sendError() breaks the state of the original FacesContext.

When sending an error during view build I get this exception:

JSF Error Pages That Actually Work
java.lang.NullPointerException
	at com.sun.faces.facelets.util.Resource.getResourceUrl(Resource.java:105)
	at com.sun.faces.facelets.impl.DefaultResourceResolver.resolveUrl(DefaultResourceResolver.java:77)
	at com.sun.faces.facelets.impl.DefaultFaceletFactory.resolveURL(DefaultFaceletFactory.java:229)
	at com.sun.faces.facelets.impl.DefaultFacelet.getRelativePath(DefaultFacelet.java:273)
	at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:341)
	at com.sun.faces.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:199)
	at com.sun.faces.facelets.tag.ui.DecorateHandler.apply(DecorateHandler.java:145)
	at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93)
	at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:86)
	at com.sun.faces.facelets.impl.DefaultFacelet.apply(DefaultFacelet.java:149)
	at com.sun.faces.application.view.FaceletViewHandlingStrategy.buildView(FaceletViewHandlingStrategy.java:838)
	at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:100)
	at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)

and if renderView() has already started, it gets even more obscure:

java.lang.NullPointerException
	at org.richfaces.skin.SkinFactoryImpl.clearSkinCaches(SkinFactoryImpl.java:94)
	at org.richfaces.skin.SkinFactoryPreRenderViewListener.processEvent(SkinFactoryPreRenderViewListener.java:35)
	at javax.faces.event.SystemEvent.processListener(SystemEvent.java:106)
	at com.sun.faces.application.ApplicationImpl.processListeners(ApplicationImpl.java:2169)
	at com.sun.faces.application.ApplicationImpl.invokeListenersFor(ApplicationImpl.java:2145)
	at com.sun.faces.application.ApplicationImpl.publishEvent(ApplicationImpl.java:303)
	at com.sun.faces.application.ApplicationImpl.publishEvent(ApplicationImpl.java:247)
	at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:108)
	at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
	at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
	at javax.faces.webapp.FacesServlet.service(FacesServlet.java:594)P

JSF continues to RENDER phase in an all messed up drunken way.

  • Calling FacesContext.responseComplete() from your managed bean's @PostConstruct method doesn't help because rendering has already started.
  • Additionally, calling FaceContext.responseComplete() from a preRenderView listener just doesn't work. It looks like the preRenderView event is added during view construction which happens in the Render View phase anyway. Could this be a regression bug?
  • Finally, throwing an exception to be caught by an error filter or exception handler doesn't resolve the problem because JSF swallows the exception from @PostConstruct and rethrows its own. 

I couldn't believe something so basic should be so complicated.

Well it turns out there is a fairly simple solution. Calling reponse.setStatus() instead of response.sendError() does not interrupt the JSF lifecycle. This works nicely, except the original view is still rendered in spite of the error.

So all we have to do is manually render a new view (the error page) as soon as the error occurs. This doesn't break JSF state and lets the lifecycle finish without all those random exceptions.

Here's what I'm talking about.

    /**
     * The standard request.sendError() breaks JSF state if it is called 
     * too late in the lifecycle. This method does the same thing but 
     * copes better with interrupting the current request.
     */
    public void sendError(FacesContext faces, int code, String message) {
        try {
            faces.getExternalContext().setResponseStatus(code);
            faces.getExternalContext().getRequestMap().put
                    ("javax.servlet.error.message", message);
            ViewHandler views = faces.getApplication().getViewHandler();
            String template = "/error/" + code + ".xhtml";
            UIViewRoot view = views.createView(faces, template);
            faces.setViewRoot(view);
            views.getViewDeclarationLanguage(faces, template).
                    buildView(faces, view);
            views.renderView(faces, view);
            faces.responseComplete();
        } catch (IOException ioe) {
            throw new RuntimeException(ioe);
        }
    }

This method works any time before the view has started rendering. Normally it should be triggered during the view build by an event or managed bean @PostConstruct method. In fact it also works during the render phase but you get a mixed up response (see the comments below).

Hope you find that useful.

NB: if you use this method yourself, don't forget to update the code with the correct path of your error templates.

About Roger Keays

JSF Error Pages That Actually Work

Roger Keays is an artist, an engineer, and a student of life. He has no fixed address and has left footprints on 40-something different countries around the world. Roger is addicted to surfing. His other interests are music, psychology, languages, the proper use of semicolons, and finding good food.

Leave a Comment

Please visit https://rogerkeays.com/jsf-error-pages-that-actually-work to add your comments.

Comment posted by: Bjørn Stenfeldt, 8 years ago

For me, 3 lines was enough:

FacesContext fc = FacesContext.getCurrentInstance();
((HttpServletResponse) fc.getExternalContext().getResponse()).sendError(404);
fc.responseComplete();

Comment posted by: Michelo Vaca Cardozo, 9 years ago

Hello Roger,

I need your help, How do I implement this method in my app?

Thanks!

Comment posted by: , 11 years ago

I just tried this little experiment:

<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html">
   <h:outputText value="Hello World!"/>
   <h:outputText value="${ui.sendError(facesContext, 404, 'Woah')}"/>
   <h:outputText value="Error Sent"/>
</ui:composition> 

And it returned a 404 with "Hello World", my error page AND "Error Sent". It's all combined because everything is running in the same FacesContext, but notice that the call to responseComplete() doesn't terminate the rendering once it has already started.

You have to make sure it happens before (or while) the view is being built to prevent the render occurring. From most Facelets and JSTL tags it will work.

Comment posted by: Torben, 11 years ago

Thanks for that tip! Ran into the same issue recently, too. And thought about the same "I couldn't believe something so basic should be so complicated" :-)

I still have one question: Will this also work if JSF has already started rendering the page? I.e. not only using this approach to handle an error that occurs in a postConstruct or preRenderView method but for an error that comes from a method that is used e.g. in some rendered attribute? Have you tested that? Or would such an exception then be caught by a JSF ExceptionHandler (where you could use your sendError method)?