Thursday, August 30, 2007

The case of the disappeared Exception message in GWT RPC

Regular readers of this blog (both of you) will remember my recent love confession for GWT. While I am glad to say that my feelings have not changed one bit, like all maturing relationships, we are getting more intimately familiar with one another. To the point where you know things about your significant other that you'd rather never found out. And since I know there are many others with feelings for GWT, I thought I'd give you a tip or two, in case you want to make a pass.

In my previous post I didn't elaborate on the marvelous feature of GWT RPC. The RPC feature gives your GWT client the ability to communicate with your Java-based backend in a very powerful, yet painless, way. I still plan to discuss it more thoroughly in a future post, but for now I'd like to mention a particular bug (or feature) of the current implementation.

The RPC mechanism allows a client to make a Remote Procedure Call to the server supplying the necessary call parameters and getting back the results. Quite often (as it turns out) the server will encounter an exceptional condition during the execution of this call and will throw an exception to the caller. Luckily an Exception in Java contains a detailed message about the problem encountered (or more accurately its ancestor, Throwable) that the client can rely upon to present the situation to the user. To give an example, you could have an Exception class EarthShatteringException like the following:

public EarthShatteringException extends Exception implements Serializable {

private static final long serialVersionUID = 1L;

public
EarthShatteringException() {
}

public
EarthShatteringException(String message) {
super(message);
}

public
EarthShatteringException(Throwable cause) {
super(cause);
}

public
EarthShatteringException(String message, Throwable cause) {
super(message, cause);
}

}



This is pretty much elementary stuff, you extend the base Exception class and also implement the Serializable interface, in order to give GWT a chance to convert your exception object to tiny little bits, transmit them over the wire and recreate the object on the client. So the way you actually throw such an exception on your server might be something like the following:

public void doGreatStuff() throws EarthShatteringException {
if (something.isWrong())
throw new EarthShatteringException("God help us!");
else
doTheGreatStuff();
}


On the client you deal with the exception in a callback function you provide, called onFailure:

public void onFailure(Throwable caught) {
displayError(caught.getMessage());
}


In this example we just display the message contained in the exception to the user.

At least that's what you would expect. In fact, what this code will do in GWT 1.4.60 (the latest release as of this writing) is display the string null.

How could that be you say? I'm glad you asked.

Serialization in GWT is a little different than in the rest of Javaland. This is because the client in runtime is actually JavaScript code, compiled from our original Java code by the miraculous GWT compiler. The compiler uses its own private implementation of the standard Java class libraries, that implements a quite large, but still limited subset of it. In our particular case the implementation of the Throwable class does not implement the Serializable interface and the side effects that entails.

So when our exception gets transmitted over to the client, what gets instantiated is an EarthShatteringException type, but its supertypes are not serialized (hence transmitted) themselves. Therefore our stored error message gets lost somewhere in Cyberspace, never to be seen again. If your server-side code attempted to throw a standard exception subclass from the standard class library, like IllegalArgumentException, things could be even worse, like getting a ClassCastException.

The good news is that this is a known issue among the GWT developers. The bad news is that it is not fixed yet. However, a simple workaround exists. If you store a detailed error message in your own exception object and override the getMessage method (or its cousins) to return that copy, instead of the supertype's, it will produce the excpected outcome. For the previous example we could do it like this (changes in bold):


public EarthShatteringException extends Exception implements Serializable {

private static final long serialVersionUID = 1L;

private String message;

public
EarthShatteringException() {
}

public
EarthShatteringException(String message) {
super(message);
this.message = message;
}

public
EarthShatteringException(Throwable cause) {
super(cause);
}

public
EarthShatteringException(String message, Throwable cause) {
super(message, cause);
this.message = message;
}

public String getMessage() {
return message;
}
}


If you are a grumpy kind of person and hasten to whine about the apparent intricacies of GWT, rest assured that most of the time GWT RPC is painless as advertised. In fact it seems so easy that people are already hooking it to other server-side frameworks, like Seam and Spring. It provides an elegant way to hook a modern server architecture with the most innovative web client technology on the planet.

I promise you, you'll love it.

4 comments:

Anonymous said...

I encountered this recently as well, but there IS a workaround. Just ensure that all of the checked exceptions on your RPC calls extend SerializableException, and the message will be marshalled and appear correctly.

past said...

Yes indeed, this is another workaround for the matter. Thanks for pointing it out.

Nevertheless, I still prefer the workaround I mentioned in the post, since although it is more work, it also keeps my domain model GWT-independent. This is why I opted for Serializable, instead of IsSerializable in the first place.

It may be unimportant in some cases, but I'd rather be able to reuse this codebase with a different presentation tier.

Megas said...

Thanks, this information helped me to find my problem!

past said...

Glad to have helped!

Creative Commons License Unless otherwise expressly stated, all original material in this weblog is licensed under a Creative Commons Attribution 3.0 License.