Server Side Exceptions
The default setting for handling exceptions in the server is not optimal. An exception on the server-side is caught and handled by the class DefaultExceptionHandler
. Only the exception message is transferred to the client but nothing is logged. As this happens inside an http 200 response, the web app container doesn’t notice that something went wrong. The default behavior can be changed by replacing the DefaultExceptionHandler during the initialization of the RequestFactory servlet.
Therefore, a custom servlet that extends from the default RequestFactoryServlet
needs to be defined to pass a custom exception handler in the constructor.
package cleancodematters.server; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.web.bindery.requestfactory.server.ExceptionHandler; import com.google.web.bindery.requestfactory.server.RequestFactoryServlet; import com.google.web.bindery.requestfactory.shared.ServerFailure; public class CustomRequestFactoryServlet extends RequestFactoryServlet { static class LoquaciousExceptionHandler implements ExceptionHandler { private static final Logger LOG = LoggerFactory.getLogger( LoquaciousExceptionHandler.class ); @Override public ServerFailure createServerFailure( Throwable throwable ) { LOG.error( "Server error", throwable ); return new ServerFailure( throwable.getMessage(), throwable.getClass().getName(), null, true ); } } public CustomRequestFactoryServlet() { super( new LoquaciousExceptionHandler() ); } }
The 2nd parameter of ServerFailure denotes the exception type. Optionally, the exception stacktrace can be passed as 3rd parameter. However, this inflates the http response and displaying a stacktrace to an end-user is questionable.
Finally, make sure to register the custom servlet in the web.xml:
<servlet> <servlet-name>gwtServlet</servlet-name> <servlet-class>cleancodematters.server.CustomRequestFactoryServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>gwtServlet</servlet-name> <url-pattern>/gwtRequest</url-pattern> </servlet-mapping>
The Client Side
When firing a request, clients usually pass a subclass of Receiver. The default implementation raises a RuntimeException containing the message from ServerFailure if the fatal-flag is set to true. If running in dev mode this exception is written to the development mode console, in production mode this causes a JS error.
To change this, one could either provide a customized receiver class from which all receivers extend. Alternatively, a generic exception handler can be registered using GWT#setUncaughtExceptionHandler()
.
You might also have noticed, that in most cases, a UmbrellaException is thrown which wraps the RuntimeException. Here’s a customized exception handler that extracts the original exception and displays the message to the user.
package cleancodematters.client; import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; import com.google.gwt.user.client.Window; import com.google.web.bindery.event.shared.UmbrellaException; public class CustomUncaughtExceptionHandler implements UncaughtExceptionHandler { @Override public void onUncaughtException( Throwable e ) { // Get rid of UmbrellaException Throwable exceptionToDisplay = getExceptionToDisplay( e ); // Replace with your favorite message dialog, e.g. GXT's MessageBox Window.alert( exceptionToDisplay.getMessage() ); } private static Throwable getExceptionToDisplay( Throwable throwable ) { Throwable result = throwable; if (throwable instanceof UmbrellaException && ((UmbrellaException) throwable).getCauses().size() == 1) { result = ((UmbrellaException) throwable).getCauses().iterator().next(); } return result; } }
(To not bother your users with empty dialog boxes you should also check for NullPointerExceptions whose message is usually empty.)
A custom exception handler needs to be registered once, e.g. in your application’s entry point by calling
GWT.setUncaughtExceptionHandler( new CustomUncaughtExceptionHandler() );