Exception handling best practices in Java are pretty straight-forward by now, and for the most part this carries over into the world of GWT (since GWT is, from the end developer's perspective, just Java!). There are, however, a few tricks and gotchas that are important to be aware of. In this blog post I'll describe a few that I've encountered, but please feel free to share your own.
Starting with the client tier and working my way to the server, here we go...
1. Handle all uncaught client-side exceptions
The first thing and simplest thing to do is to ensure that all unhandled exceptions bubbling out of the client tier are caught. You can accomplish this by registering an UncaughtExceptionHandler, like this:
public class Example implements EntryPoint {
public void onModuleLoad() {
GWT.setUncaughtExceptionHandler(new
GWT.UncaughtExceptionHandler() {
public void onUncaughtException(Throwable e) {
// do exception handling stuf
}
// do module loading stuff
}
}
Pretty straight-forward. What happens if you don't set the UncaughtExceptionHandler in your application? Well, in hosted mode, the default handler will conveniently log the exception and stack trace to the console. In web mode, however, the handler is null, and so exceptions "escape" to your browser, which is likely not the user experience you're looking for (though it does provide the opportunity to use a Javascript debugger for further diagnosis).
Also, be aware that your custom UncaughtExceptionHandler only takes effect after the onModuleLoad() returns. In other words, if there was an exception raised where the comment "do module loading stuff" is in the example above, this exception would not be handled using your custom UncaughtExceptionHandler that you just defined. This is a known issue, and will not be resolved. As a work-around, if you expect exceptions could be thrown within onModuleLoads(), then you can extract the guts of onModuleLoad() to another method, and invoke it using a DeferredCommand:
public class Example implements EntryPoint {
public void onModuleLoad() {
GWT.setUncaughtExceptionHandler(new
GWT.UncaughtExceptionHandler() {
public void onUncaughtException(Throwable e) {
// do exception handling stuff
}
});
DeferredCommand.addCommand(new Command() {
public void execute() {
onModuleLoad2();
}
});
}
private void onModuleLoad2() {
// do module loading stuff
}
}
2. Unravel GWT's UmbrellaException
Once your UncaughtExceptionHandler is set, you may notice that some of the exceptions caught are instances of GWT's UmbrellaException, rather than the actual exception thrown within your code. GWT uses UmbrellaExceptions to wrap exceptions in certain cases (loops, etc.), and it nests the root exception within it. For the purpose of easier debugging and more accurate error handling, it's often helpful to unwrap these exceptions, like this:
GWT.setUncaughtExceptionHandler(new
GWT.UncaughtExceptionHandler() {
public void onUncaughtException(Throwable e) {
Throwable unwrapped = unwrap(e);
// do exception handling stuff
}
public Throwable unwrap(Throwable e) {
if(e instanceof UmbrellaException) {
UmbrellaException ue = (UmbrellaException) e;
if(ue.getCauses().size() == 1) {
return unwrap(ue.getCauses().iterator().next());
}
}
return e;
}
}
3. Log exceptions on the client
Now that you have a handler catching your rogue client side exceptions and also a handy method to unravel them to their root causes, you'll want to actually do something useful with these exceptions to help developers more easily diagnose and resolve the underlying problems. While the appropriate action may differ per application (e.g. present an error dialog, email the support team, etc.), at the very least the exception should be logged. Fortunately, GWT delivers a simple but powerful solution for client-side logging, piggy-backing off of the standard java.util.logging package. Plugging a logger into the UncaughtExceptionHandler is easily accomplished:
public class Example implements EntryPoint {
private Logger logger = Logger.getLogger("");
public void onModuleLoad() {
GWT.setUncaughtExceptionHandler(new
GWT.UncaughtExceptionHandler() {
public void onUncaughtException(Throwable e) {
logger.log(Level.ERROR, "Ex caught!", e);
}
// do module loading stuff
}
}
Leveraging a helpful set of handlers that GWT provides, loggers can write to a number of different channels: development mode window, console, in-window pop-up, Firebug, and, the coup-de-gras, to the server(which I'll cover in a bit). These channels are easily configurable via your .gwt.xml file:
Logging in GWT is very well documented, so there's no need to retread here.
4. Log exceptions on the server
The majority of the logging handlers write to the client side, however in a production environment this often isn't much help to you. When your users stumble upon an error, it's unlikely (and actually unsafe from a security perspective!) to expect them to look in client-side log files to help you diagnose the problem. Instead, your application should do this work for you, by logging all unexpected exceptions to the server. Fortunately, remote logging of this sort is as simple as enabling it in the .gwt.xml config file:
...and then defining the remote logging servlet in your web.xml file:
remoteLogging com.google.gwt.logging.server.RemoteLoggingServiceImpl remoteLogging /your-gwt-module-name/remote_logging
Now, with one simple call to the log() method in your UncaughtExceptionHandler, the exception is sent automatically to your server log. Note, however, that by default it's assumed that you're using java.util.logging, which may not necessarily be the case. To hook these logs into, say, Log4J, check out SL4J.
Finally, it's possible that instead of using GWT's remote logging, you may want to implement a simple custom RPC to log (and possibly do other things - e.g. send email, etc.) when an unexpected exception is encountered. For example:
GWT.setUncaughtExceptionHandler(new
GWT.UncaughtExceptionHandler() {
public void onUncaughtException(Throwable e) {
myService.handleException(e);
}
}
Seems straight-forward, right? Be careful! First, not all exceptions are serializable or follow the rules of serializable (ugh!). For example, GWT's JavascriptException inherits Serializable, but doesn't implement a public no-arg constructor, so when you try to send this over the wire via your RPC, a run-time Serialization exception is thrown. And since this exception would be thrown inside your exception handler, it would effectively swallowed. Second, the stack trace within an exception is transient, and so is lost from client to server (so if you need it on the server side, send it as a separate parameter). Which leads me to the next tip...
5. Consider logging exception stack traces
Of course knowing that an exception occurred isn't usually helpful unless you know exactly where it was thrown from! On the server side we take this for granted, since stack traces are typically logged by default with our exceptions. On the client side with GWT side, however, it's not so easy. When Java code gets compiled into Javascript, it is both shrunk and obfuscated. This is obviously useful for performance and security, but from the perspective of supporting applications, it makes things much trickier. For example, instead of seeing a stack trace with method/class names and line numbers, you might get something like this:
Unknown.Jq(Unknown Source) Unknown.Tm(Unknown Source) Unknown.Sm(Unknown Source)
Not too helpful! Fortunately, there are different options for how GWT compiles into Javascript which provide different amounts of information for debugging. Of course nothing in life is free; the trade-offs are security and performance. If you wanted nice clean Javascript that resembles your Java code and that produces nice clean stack traces, then you can flip the GWT compiler "style" flag to either "pretty" or "detailed", however this would obviously increase the size the Javascript (and thus take longer to download)...and any user could more easily grock your client side code. There's a nice compromise though, which is to provide file names and/or line numbers in your stack traces, but to keep the source code obfuscated. This can be configured in your .gwt.xml file:
Note, again, that this does increase the size of your Javascript bundle. For one application I worked on, I found that just turning on the emulatedStack increased the Javascript by 30%, and turning on emulatedStack plus line numbers increased it by 100% (i.e. doubled the size!). Depending on the performance requirements of your application, this may or may not be acceptable.
Finally, it's helpful to know that even with the obfuscated stack traces, there is still hope for hunting down mysterious exceptions. What seems like jibberish is just obfuscated code, and you can manually translate the obfuscated method names (e.g. "Jq", "Tm", or "Sm" from the above example) to the Java method names using the symbolMaps file that GWT generates. If only there were a way to do this programmatically...
6. Use the StackTraceDeobfuscator
GWT provides a utility class called the StackTraceDeobfuscator which (as it advertises!) deobfuscates stack traces, translating the condensed, jibberish stack trace (like above) into a one that you would expect. If you're using GWT's remote logger, then it helpfully uses this by default, with one caveat: it knows where to find the symbolMaps file. GWT places this in the "WEB-INF/deploy/your-app/symbolMaps" directory, and you can instruct the RemoteLoggerService to look there accordingly:
symbolMaps your-app/symbolMaps
You could also call the StackTraceDeobfuscator manually, if, for example, you had your own RPC service that handled exceptions in some custom way. I wrote a simple subclass of the StackTraceDeobfuscator, and called it like this:
public class LogServiceImpl extends LogService {
public void logError(Throwable t, String stackTrace) {
StackTraceElement[] sts = stackTraceDeobfuscator.deobfuscateStackTrace(t.getStackTrace());
t.setStackTrace(sts);
LOG.logError("An error happened", t);
}
}
7. Throw exceptions explicitly in RPC Services
Now, to the server side (which, thankfully, is much less tricky!). Imagine you have a simple RPC service that throws a custom exception, MyRuntimeException, which extends from RuntimeException. For example:
public class MyServiceImpl extends MyService {
public void foo() {
throw new MyRuntimeException();
}
}
By Java's rules, there is no obligation that you explicitly declare (either in the impl or interface) that foo throws MyRuntimeException. For the sake of simplicity, you may choose not to, however you might be surprised then to find that when you catch this exception in your AsyncCallback...
myService.foo(new AsyncCallback() {
public void onSuccess(void v) {
}
public void onFailure(Throwable t) {
if(t instanceof MyRuntimeException) {
// do something
}
}
}
...the object t will not be an instance of MyRuntimeException, but rather an UnexpectedException. This is because GWT did not know to compile MyRuntimeException into Javascript, and so the client code does not know of this type. To remedy, make sure you explicitly throw MyRuntimeException in your service interface (but not in your Async interface!):
public interface MyService {
void foo() throws MyRuntimeException;
}
Note that if you threw some subclass of MyRuntimeException, the instanceof check would work, however be aware that GWT would then compile all concrete subclasses of MyRuntimeException, which could slow build time.
Whew...that's it. Please share any tips you have, or let me know if you see anything awry with what I've written. Thanks!