7 Tips for Exception Handling in GWT


(17 comments)
June 11th 2012


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!

I'm an "old" programmer who has been blogging for almost 20 years now. In 2017, I started Highline Solutions, a consulting company that helps with software architecture and full-stack development. I have two degrees from Carnegie Mellon University, one practical (Information and Decision Systems) and one not so much (Philosophy - thesis here). Pittsburgh, PA is my home where I live with my wife and 3 energetic boys.
I recently released a web app called TechRez, a "better resume for tech". The idea is that instead of sending out the same-old static PDF resume that's jam packed with buzz words and spans multiple pages, you can create a TechRez, which is modern, visual, and interactive. Try it out for free!
Got a Comment?
Comments (17)
Ashton
July 24, 2012
Great post, Ben!
Some of our biggest exceptions are IncompatibleRemoteServiceException for when the app doesn’t get an update and is stale or InvocationException on the mobile app when someone losses internet connection.
I would love to hear your thoughts on handling app updates while balance cacheing and performance.
-AT
Joel
July 24, 2012
Ben, thanks for the great post.
Just wanted to point out a typo in Tip 4. Log exceptions on the server.
You’ve got
where it ought to be
Thanks again!
Joel
July 24, 2012
Grr, I failed at the HTML formatting. That should have said that in the first code snippet, you’re setting the property “gwt.logging.simpleRemoteHandler” to DISABLED when it should be ENABLED.
Ben
July 30, 2012
Thanks for the comments!
@Joel – Fixed the typo.
@Ashton – good call on the IncompatibleRemoteServiceException…and nice thought for a future post maybe.
Kiran
August 01, 2012
Hi,
Thanks.
But there is a small bug in the method “public Throwable unwrap(Throwable e)”. Currently it keeps invoking itself with the same Throwable object, which causes StackOverflowException. I think here we need to invoke with the first cause. So the method will be
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;
}
Ben
August 03, 2012
Kiran – Oh man…good catch…sorry about that! You are correct (and it is fixed now in the post). I obviously mis-wrote that method when I copied it over for the blog post. Thanks much for point that out.
Javier
August 21, 2012
Great post. Really helpful
Ravesh
August 06, 2012
Thanks Ben. Really helpful.
Francesco
August 21, 2012
Thanks so much! This saved me a lot of time!
Nick
January 25, 2013
Thanks Ben!
Its a great post, I have a question though, when ever an exception is thrown by my RPC service i get a generic stack trace which is not helpful. All my services throw java.lang.Exception. I also want to see the stack trace of what caused the exception in the service but i just see stack trace as:
java.lang.NullPointerException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader.instantiate(ServerSerializationStreamReader.java:914)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader.deserialize(ServerSerializationStreamReader.java:556)
at com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamReader.readObject(AbstractSerializationStreamReader.java:119)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader$ValueReader$8.readValue(ServerSerializationStreamReader.java:138)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader.deserializeValue(ServerSerializationStreamReader.java:385)
at com.google.gwt.user.server.rpc.RPC.decodeRequest(RPC.java:303)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:206)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:248)
at com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62)
at com.cccis.gwt.common.controller.SpringGWTController.handleRequest(SpringGWTController.java:30)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
As you can see in this stacktrace, i caused a nullpointerexception in the service. I want to see where it occured in the service (line no and method).
Please advice,
Thanks.
Eugene
August 23, 2013
Ben: this is really good and informative post!
Thanks a lot!
July 18, 2013
This is a great blog post. I used it as a starting point for experimenting with GWT’s stack trace logging capability. As a result, I ended up rewriting the GWT compiler and libraries to do a much better job.
You can read more about my efforts (which will hopefully make it into GWT some day) here: http://igg.me/at/gwt-stack-traces/x/3494291
Pratik
September 14, 2013
Hello,
First of all, this is really an awesome blog post and able to get many things out of it for GWT application.
On top of the exception you described, we are continuously facing problem related to “Socket Exception: ” (Stack trace is mentioned at the end of the post).
We have created the application in GWT 2.4, and running on Jboss 7.1. We are literally stuck up on why this error is comming up and what are the ways to resolve these error?
We google lot many things but not able to find out the suitable post except yours, so earging to help us out to resolve this kind of error.
What we observer till date is, user is able to send data from client to server using RPC and client’s internet connectivity are bed sometimes. So If we get this error, how we can handle/catch it on a Client Side and show him a proper message related to connectivity.
Hoping to get the valuable feedback on the same.
——————————————————————————————————–
Following are the some of the exception that we get from server log.
Caused by: java.net.SocketException: Software caused connection abort: recv failed
00:41:48,176 ERROR [stderr] (http–0.0.0.0-80-152) at java.net.SocketInputStream.socketRead0(Native Method)
00:41:48,176 ERROR [stderr] (http–0.0.0.0-80-152) at java.net.SocketInputStream.read(SocketInputStream.java:150)
00:41:48,176 ERROR [stderr] (http–0.0.0.0-80-152) at java.net.SocketInputStream.read(SocketInputStream.java:121)
00:41:48,176 ERROR [stderr] (http–0.0.0.0-80-152) at java.io.DataInputStream.readFully(DataInputStream.java:195)
00:41:48,176 ERROR [stderr] (http–0.0.0.0-80-152) at java.io.DataInputStream.readFully(DataInputStream.java:169)
00:41:48,176 ERROR [stderr] (http–0.0.0.0-80-152) at net.sourceforge.jtds.jdbc.SharedSocket.readPacket(SharedSocket.java:842)
00:41:48,176 ERROR [stderr] (http–0.0.0.0-80-152) at net.sourceforge.jtds.jdbc.SharedSocket.getNetPacket(SharedSocket.java:723)
00:41:48,176 ERROR [stderr] (http–0.0.0.0-80-152) at net.sourceforge.jtds.jdbc.ResponseStream.getPacket(ResponseStream.java:466)
00:41:48,176 ERROR [stderr] (http–0.0.0.0-80-152) at net.sourceforge.jtds.jdbc.ResponseStream.read(ResponseStream.java:103)
00:41:48,176 ERROR [stderr] (http–0.0.0.0-80-152) at net.sourceforge.jtds.jdbc.ResponseStream.peek(ResponseStream.java:88)
00:41:48,176 ERROR [stderr] (http–0.0.0.0-80-152) at net.sourceforge.jtds.jdbc.TdsCore.wait(TdsCore.java:3932)
00:41:48,176 ERROR [stderr] (http–0.0.0.0-80-152) at net.sourceforge.jtds.jdbc.TdsCore.executeSQL(TdsCore.java:1046)
Caused by: ClientAbortException: java.net.SocketException: Connection reset by peer: socket write error
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:403) [jbossweb-7.0.13.Final.jar:]
at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:354) [jbossweb-7.0.13.Final.jar:]
at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:426) [jbossweb-7.0.13.Final.jar:]
at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:415) [jbossweb-7.0.13.Final.jar:]
at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:89) [jbossweb-7.0.13.Final.jar:]
at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:83) [jbossweb-7.0.13.Final.jar:]
at com.google.gwt.user.server.rpc.RPCServletUtils.writeResponse(RPCServletUtils.java:330) [gwt-servlet.jar:]
at com.google.gwt.user.server.rpc.RemoteServiceServlet.writeResponse(RemoteServiceServlet.java:357) [gwt-servlet.jar:]
at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:256) [gwt-servlet.jar:]
at com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62) [gwt-servlet.jar:]
… 21 more
We are on urge to find the solution of handling these kind of exceptions hindering RPCs(Whether be at client side or server side).
September 29, 2013
Hi
I think there is a type in 1st tip. You forgot “);” characters at the end of setUncaughtExceptionHandler line.
public class Example implements EntryPoint {
public void onModuleLoad() {
GWT.setUncaughtExceptionHandler(new
GWT.UncaughtExceptionHandler() {
public void onUncaughtException(Throwable e) {
// do exception handling stuf
}); // << correct line
// do module loading stuff
}
Enguerrand Dibanda
January 21, 2014
Hi,
great post, I found it is very helpfull!
The DeferredCommand is however deprecated. It would be nice to update that part with the Scheduler API
Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
public void execute() {
onModuleLoad2();
}
});
August 07, 2019
Awesome post, helped me a lot. Wanted just to thank you :-)
Shahram
February 11, 2021
Very useful and well explained post. Helped me a lot. Thank you very much.