Runtime Exceptions

Feedback is vitally important when developing an application, and that is one of the areas where Tapestry has always excelled.

Especially during development, requests can fail. There can be errors in templates, broken code in your application, or something unexpected.

Build-in Exception Report Page

Tapestry has a built-in exception report page that captures an amazing wealth of information:

This exception report features:

  • The full stack of exceptions, top to bottom.
  • All non-null properties of each exception.
  • The stack trace at the deepest level.
  • Key request properties, header, attributes, and parameters.
  • Key session propertes
  • A break down of the thread in your application
  • A listing of all JVM System properties

Ajax Detailed Exception Reports

A similar detailed exception report is also built-in to Tapestry's Ajax support. When an Ajax request fails, Tapestry's client-side code will create an <iframe> to present this same information:

Detailed Exception Report Files

In addition, Tapestry will write a text file for the exception with a similar level of detail. The default location for the detailed exception report files is a relative directory build/exceptions. You can configure the location by setting SymbolConstants.EXCEPTION_REPORTS_DIR.

If you want to turn off the writing of detailed exception reports files to the file system, you can add the following to your application module (usually AppModule.java):

AppModule.java (partial)
     /**
     * By default, Tapestry's ExceptionReporter implementation writes verbose text files to the
     * "build/exceptions" directory. This replaces that implementation with one that does nothing.
     * (The exceptions still get logged elsewhere.)
     */
    @Decorate(serviceInterface = ExceptionReporter.class)
    public static ExceptionReporter preventExceptionFileWriting(final ExceptionReporter exceptionReporter) {
        return new ExceptionReporter() {
            @Override
            public void reportException(Throwable exception) {
            }
        };
    }

Overriding the Handling of Specific Exceptions

In production, you may want to override the exception report page (even if you keep the text file output). However, Tapestry's (from version 5.4) default exception reporter also allows you to handle specific exception types in a pre-determined manner, similar to how servlet spec's standard error-page/exception-type configuration option allows you to map exception types to URLs. At times, it's simpler to just catch exceptions at the outermost layer of your application instead of carrying a typed exception through multiple layers of abstractions just so you could show a sensible error message to the user, especially if you can't do anything more clever about it anyway. Exception type mapping in Tapestry is much more powerful than what the servlet spec dictates. If your email service or an external payment service goes down, you can't do much more than display an error message to the user, so why would you need to implement separate pages for each exception? Often, it'd be nicer if you could just reuse the page template for any fatal exception and simply display a different error message. In addition to contributing handlers for specific types of exceptions, you may also provide context for rendering the same error page template with a different output.

You can contribute an error page, mapping it to an exception type:

AppModule.java (partial)
    public void contributeRequestExceptionHandler(MappedConfiguration<Class<?>, Object> configuration) {
        configuration.add(SmtpNotRespondingException.class, ServiceFailure.class);
    }

If a simple exception type to page mapping doesn't do it for you, you can also contribute a custom handler for that particular exception type. An ExceptionHandlerAssistant can contain arbitrarily complex logic for handling a specific exception type and use other Tapestry services. If ExceptionHandlerAssistant.handleRequestException(Throwable exception, List<Object> exceptionContext) returns an Object representing an URL the main handler will issue a redirect to that URL. It's valid to return either a String, a Link or a Class; the last case implies a page class. If the ExceptionHandlerAssistant returns null, it's assumed that the assistant has independently handled the exception. You can either contribute an instance of an ExceptionHandlerAssistant or a class that implements ExceptionHandlerAssistant. Below, we contribute an instance handling ServiceExceptions:

AppModule.java (partial)
    public void contributeRequestExceptionHandler(OperationQueue operationQueue, MappedConfiguration<Class<?>, Object> configuration) {
        final ExceptionHandlerAssistant assistant = new ExceptionHandlerAssistant() {
        @Override
        public Object handleRequestException(Throwable exception, List<Object> exceptionContext) throws IOException {
          ServiceException serviceException = (ServiceException)exception;
          if (serviceException.isInterruptedOperationRecoverable()) {
              operationQueue.add(serviceException.getInterruptedOperation());
              return OperationScheduled.class;
          }
          else return ServiceUnavailable.class;
        }
        };
        configuration.add(ServiceException.class, assistant);
    }

You can also specify context for the exception page. For generic exceptions, the context is taken from the exception class name minus the word "Exception" in case that's how the class name ends. For example, you have a following class:

SmtpNotResponding.java
public class SmtpNotRespondingException extends RuntimeException {
 ...
}

If an SmtpNotRespondingException is thrown during an action request, user is directed to ServiceFailure page with a String context smtpnotresponding (i.e. to URL /servicefailure/smtpnotresponding). The contributed exception handling works both for regular action requests and ajax action requests. In the latter case, the module will use Javascript to redirect to the error page. If the exception thrown is not one of the contributed types, the exception is handled like any other exception, as explained above.

If your custom-handled exception implements the interface org.apache.tapestry5.ContextAwareException you can fully specify the context for the error page. For example, you could implement a following Exception class:

SmtpNotRespondingException.java
    public class SmtpNotRespondingException extends RuntimeException implements ContextAwareException {
        private Object[] context;  
        public EmailServiceException(Object[] context) {
            super();
            this.context = context;
        }
        // Defined in ContextAwareException interface
        public Object[] getContext() {
            return context;
        }
    }

The contributed exception handling mechanism can easily be overused. Typically, if you can handle the exception locally, you should. Likewise, you shouldn't blindly wrap any checked exceptions inside runtime exceptions just to avoid writing try-catch blocks in higher layers. The mechanism for contributed exception types is best used for handling serious but rarely occurring exceptions happening in the action request cycle that you cannot otherwise cope with.