Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Spring and checked & unchecked exceptions

A few weeks ago a colleague asked about checked and unchecked exceptions and I mentioned offhand that it is useful to understand Spring's exception design and choices. This is a better response...

The Spring exception library has been around a long time and it has survived because it matches the semantics of servicing problems rather than categorizing technical failings. In particular, I am addressing the org.springframework.dao.DataAccessException hierarchy of exceptions. It is worth the time to read Chapter 9 of Expert One-On-One J2EE Design and Development to better understand Spring's exceptions  

The first question we need to ask is why do we use exceptions? For me an exception is due to an unanticipated or unforeseen problem that MUST be handled outside of the normal call chain. If we have a method that is expected to return a value and it can't then this is an exception. If we have a method that can be expected to not return a value then that is not an exception. For example, if the method "int getFoo(int bar)" is expected to have a valid return value for every value of bar then any problems must raise an exception. However, if the method does not have a valid return value for every value of bar then the method is badly specified. The method would be better specified as "Optional<Integer> getFoo(int bar)" or, better yet, named "findFoo". Once you have a well specified method then you can consider how to use exceptions.

What I like about Spring's data access exceptions is that they derive from three base classes RecoverableDataAccessException, NonTransientDataAccessException, and TransientDataAccessException. These base classes let the caller know how to respond to the exception -- and this is important -- if the caller wants to. For example, a method raising NonTransientDataAccessException (or its subclasses) can't be "retried" to get a different result. Whereas, a method raising TransientDataAccessException could  be retried, and a method raising RecoverableDataAccessException could be retried once some mitigation has been undertaken. For the example, "int getFoo(int)" could throw a NonTransientDataAccessException (well, a subclass like NotFoundException) if the given "bar" does not have a corresponding "foo".  

You can also see how we could have a similar set of base exceptions for process failures, eg RecoverableProcessException, NonTransientProcessingException, and TransientProcessingException. 

As to whether to use checked exceptions or not, I think there are two factors to consider. The first factor is how likely can intermediaries in the call chain practically respond to the exception? The second consideration is how important is it for the caller to know about the exceptions thrown by the method? I think understanding how to respond to exceptions is critical to building stable, recoverable applications. However, in a K8 world where failed applications have a small functional scope and can be restarted automatically stability and recoverability are less important. So, these days I am comfortable with unchecked exceptions BUT the exceptions should be declared on the method signature -- doing so better documents the method.

The end is nigh for NetBeans and Java GUIs

I recently updated my Mac to Mojave -- yea, just days before Catalina was released! I did this for a few reasons, but mostly I was unsure if JDK 11 would work on the macOS I was running and I needed JDK 11 for some consulting work. Since updating to Mojave I have found both NetBeans 8.2 (using JDK 8) and Apache NetBeans 11.1 (using OpenJDK 11) to be unstable. They don't freeze, but parts of the interface just stop working after a few hours of use. Mouse clicking fails in some places. Some keyboard commands fail. Typed input fails. The only solution is to restart NetBeans. This is not a good nor long term development environment tactic.

I have been worried for some time that Java on the Mac is at an end. Apple abandoned Java back in 2017. I didn't expect Oracle or anyone else to really put much effort into keeping the Java GUI toolkits current with macOS changes. I don't even expect Java GUIs to remain for much longer, either. JavaFX was dropped in JDK 11. I am sure Swing would have gone too if it were not for some very important customers that are still dependent on it, for example, the US military and especially the Navy.

I have similar observations about NetBeans. NetBeans always seemed like Oracle's unwanted stepchild from the Sun acquisition. Updates were infrequent and with little innovation. Oracle has now passed it on to Apache, but how much attention is it going to get there? There are commercial applications that are built on top of the NetBeans platform and so some support will come from them, but it would be foolish for these commercial applications to remain desktop bound for too much longer. Even Eclipse, the quintessential Java tool for building Java tools, has moved its focus from being a desktop application to being a web application.

It is finally time to move off of NetBeans and to move off of any Java GUI application. I say this with a heavy heart as I have enjoyed and benefited from both over very many years. RIP.

Aside: If you are interested in large scale, pluggable, user interface design then do read everything you can about the NetBeans platform. It is remarkably well designed.

Gists, Terraform, horizon charts, and PDF thumbnails, but not all at the same time

I was working my way through the Terraform Two-Tier example and after finding omissions I wrote down the missing parts and posted them to a GitHub gist. I used a gist as, I think, search engines will likely rank this higher than a posting here in my blog and so is better for folks looking for help.

I don't use GitHub gists much, but, apparently, I have been using them them for a long time -- 11 years! I had forgotten about some of the work I had done and dropped into gists. The horizon chart gist was part of my Horizon Chart blog post. I don't seemed to have posted anything about PDF Thumbnailer, however.

I originally created PDF Thumbnailer as part of a larger project to enable public commentary on South Kingstown, RI's budget. (SK is my home town.) When this project started in 2007 there were no available online tools for multiple users commenting on a PDF. So I needed to build one. The web interface consisted of just two principle pages, an overview page containing thumbnails of the whole budget with comment indicators laid over the thumbnails, and a details page of the whole budget and commentary interleaved.

To interleave the comments I needed to place them between the text, charts, and tables of the budget document. Parsing and rebuilding the PDF or converting it to HTML and rending it in the browsers of the time was more work than I could commit too. Instead, I compromised on the spacial proximity of a comment and its context. I converted the PDF pages to images and sliced each image into 1" tall strips. I could then interleave strips and comments on the details page as needed. While a comment was not attached to the context it was close enough to establish the connection.

In these sketches the pages are bounded in red, the strips in blue, and the comments in purple.



The project was never completed, but it did leave behind the thumbnail creator. I rebuilt it this morning and here are a few example pages from Rescue.org's recent report Missing Persons: Refugees Left Out and Left Behind in the SDGs

A top software development position in a startup wants ...

Having just taken a job with Raytheon and lamenting my lack of success getting a top software development position in a startup a friend asked "What is needed for new startups?"

A top software development position in a startup wants several years of experience with AWS infrastructure and a comprehensive knowledge of JavaScript use (and packaging) in the backend (node.js) and the frontend (Angular, React, and sometimes Vue). The startup still want the other skills of scalable architecture design (processing and data), excellent written and spoken communication, and mentoring, but having these is not enough. I was also surprised that even startups that will hire remote staff still want that staff close to home base.

As to the Java language and its ecology, this has little interest for them. In fact, many of the infrastructure foundations that are today implemented in Java -- ActiveMQ, Kafka, Zookeeper, Tomcat, etc -- are being replaced with "lighter" Go and Rust implementations. And with the accelerating move to managed infrastructure startups care little about how these foundations are implemented. They would rather rent, eg, a queuing service from AWS or GCP, and let the provider worry about implementing turnkey, scalable performance.

Even Citizens Bank, after having recently become independent of the Royal Bank of Scotland, is replacing their Java J2EE implementation with an AWS hosted, node.js microserviced, and React front ends. I have even heard that some of their services are from a "bank in a box" supplier.

I don't disagree with these changes except for the unfounded confidence in JavaScript -- the one language to rule them all. I would not want to run my own data center and infrastructure anymore, either. Assuming, that is, I had the very sizable budget for it.

Equivalence and Equality

There is an interesting debate going on over at The Eternal Issue About Object.hashCode() in Java. The root of the problem in the debate is that the participants are not distinguishing between equivalence and equality (aka identity). In computing we make a distinction between the two. Any two instances are equivalent if their values are the same. Equality is a stronger equivalence. Ie, it is not enough that the values are the same, but the values must be the same instances. Eg, A = "joe", B = "joe", and C = A then A, B, and C  are equivalent while only A and C are equal.

Java has made a mess of this distinction because its Object class 1) implements an equals() method and 2) the implementation is equality. So, by default, no two instance are ever equivalent. Most people would consider "joe" and "joe" to be equal, but not Java. (Java should have defined equals() and hashCode() as abstract or, better yet, define them in an Equatable interface.)

My reading of the Eternal Issue article is that there is a contract that base-class instances would use a value, provided during construction, to distinguish themselves. This is equivalence. Eg
class Baseclass {
   String name;
   Baseclass(String name) { 
      this.name = name; 
   }
   boolean equals(Baseclass that) { 
      this.name.equals(that.name); 
   }
}
The sub-class would depend on this, in part, for its determination of equivalence, eg
class Subclass extends Baseclass {
   int age;
   Subclass(String name, int age) { 
      super(name); 
      this.age = age; 
   }
   boolean equals(Subclass that) { 
      super.equals(that) && this.age == that.age; 
   }
}
Now, what happens when you remove Baseclass's equals() method? Doing that changes Baseclass's distinguishing behavior from equivalence to equality as instances now default to using Object.equals(). This is a extraordinary change to the contract between the classes. Subclass equivalence will immediately start to fail because no two Subclass instances will ever be at the same memory location.

Never mix equivalence and equality and an inheritance hierarchy.

A consequence of the rule is that since you decided not to use name to distinguish Baseclass instances then all instances are equivalent. Ie, they are indistinguishable. The only correct change that adheres to the contract would be not to remove equals() but to to replace it with
boolean equals(Baseclass that) { true; }
Of course, any change to Baseclass is likely a bad idea and I would never recommend that you respond to this change with anything less than tar and feathers. But, at least, make sure the changer of the Baseclass understands what they did vis a vis the contract.

Grumble about the JDK standard libraries

I recently wrote a Slack application and restricted myself to using only the JDK. For some reason, I wanted to reminded myself of how awkward Java programming is for newbies.

No one chooses a Java implementation without the expectation of needing to include a shedload of external libraries. Luckily, Java has the most best of class libraries available, that are easily incorporated with Maven, and that have facilitated the great variety of successful applications being built and maintained every day. The JDK's standard libraries are, however, doddering and incomplete, especially as to building applications for the internet. Scripting languages like Python, PHP, and Ruby do have standard libraries that have evolved to incorporate the internet. And these languages are being successfully used by newbies [1]. Nevertheless, I had my question to answer.

My application uses Slack's outgoing webhooks and so is a specialized HTTP server. The JDK does have an HTTP server, but it is in the com.sun namespace and does not implement the Servlet API. Implementing the Servlet API would give the newbie the experience of using a standard container and, moreover, allow his or her application to be deployed to any number of cloud providers. Implementing an HTTP server is not a quick task and so I choose to use the JDK's.

When Slack sends a webhook request the body content is url form encoded, eg "a=1&b=2&b=3".  (You more often see this used in URL queries.) Apart from an early spat over delimiting with ampersands vs semicolons this encoding[2] has been universally, consistently used for decades. Unfortunately, the JDK does not have a standard means to encode or decode this data. I implemented a decoder.

The response to the webhook is a JSON encoded message. JSON's adoption rate has been breakneck, but standard libraries have caught up. The Java Community Process (JCP) did define a JSON API several years ago, but an implementation is not in the JDK. While outputting JSON is straightforward, ensuring it is syntactically correct calls for having support. I implemented an encoder

Lastly, and most sadly, is the situation with fixed width hex values. Slack wants colors expressed in the RGB hex notation, ie #RRGGBB. JDK has a Color class, but it can't be used to create an RGB hex value. (Its Color.toString() method is practically useless.) To format the value you can use

String.format("%02x%02x%02x", c.getRed(), c.getGreen(), c.getBlue()) 

but, honestly, what newbe is going to know that or have any hope of Googling the answer?

Gumble over. Back to real world Java development.


[1] That academics use Java in introductory programming classes is appalling.

[2] It is not really "encoding" and "decoding," but marshalling and unmarshalling.

Java 8 Method Reference: How to Use it

"Java 8 Method Reference: How to Use" it is a great posting on how to create and use method references.

Awesome Java list of exceptional, reusable Java libraries

Andreas Kull is doing a good job of curating the Awesome Java list of exceptional, reusable Java libraries.

Some comments about a healthy software development organization

I have been thinking a lot about healthy software development organizations recently [1]. I have never headed a development organization. I have always been an individual contributor working as an architect, a principle engineer, a team leader, or one of the many other roles on my the way to these. So I work within an existing organization and have some command to shape it. There are limits to this command, however. The shape is principally formed and administered by the department's head.

I have worked for several heads of development. All were good people. All managed differently. Some were curt, some loyal, some inclusive, and some neglectful. I have learned much from working with them. Here is some of what I have learned.

A healthy development organization is one that has
  1. Staff with a range and overlap of skills & experiences.
  2. Work that is finished when all steps are complete.
  3. Communication is balanced between structured & unstructured.
  4. Achievements are celebrated.
In a larger organization these points are easier to achieve, but even a small organization must position itself to attain them.

You need to have a staff with a range of skills & experience and without large gaps in either. For example, having three junior developers and an architect is not healthy. Beyond the one bus problem is the problem that the larger the gap the more likely the organization will create a implementation that is of irregular consistency. Consistency enables an organization to potentially use many more of its staff to resolve bugs and add improvements. You don't want to have anyone say "Andrew wrote that. I don't touch it without his input."

The work of a software product is not just code. We all know that, but the pressure to release the implementation alone is great. I am a firm advocate in documentation and testing. Testing is easier for developers to accept as it is a channel for more coding; coding is something they want to get good at and enjoy doing it even when the effort is going badly. Getting development documentation written is an uphill struggle. 

I have only ever worked at one organization where developers prepared documentation without acrimony and that was the CASE company Cadre. I was an apprentice programmer at the time and ready to accept, without question, everything I learned there. I have since come to be more selective, but the importance of documentation has remained. Documentation is a part of my third point about communications. Communications includes all its in-person and online forms. Communication is about coming to a common understanding and then achieving consensus.

A software project has a product goal and an implementation goal. The product's goal, ie having an implementation that works, is the easy part of software development. Having an implementation that the development organization can support is much harder. The initial expression of the implementation is not code, but a written design. This written design might only consist of a few diagrams and an enumeration of constraints and problem areas, but having it written down means that someone made the effort to attain a comprehensive grasp of the product implementation and to communicate it to others. From that the development staff can begin to come to a common understanding. There are other documentation needs, but for now, lets start with an upfront design!

My last point is one coders and heads of development publicly dismiss, even belittle, but privately value when it is done well. So much of a developer's work is unseen. The reviewer finds problems. Who finds successes? (We don't even have a title for such a role!) Developers want others to know about their trials (their stories), and for their accomplishments and improvements to be acknowledged. None of this is needed for the product. All of this is needed for a healthy development organization.

[1] I am going to use the term software development and not software engineering. My ego is buffed by the engineering term, but, frankly, software development is far from an engineering discipline as the term is used in other inventive organizations. Our work is, with the best of meanings, craft.

JNDI, Tomcat, and ClassLoaders

If you want to use JNDI within a servlet container than you need to be careful to use the corresponding class loader for your shared resource. So, ensure that you have only one copy of the class in your JVM and then when you want to bind or lookup the resource use the resource's classloader. For example,
public <T> void bind(String name, Class<T> valueClass, T value) throws NamingException {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    try {
        Context c = new InitialContext();
        Thread.currentThread().setContextClassLoader(valueClass.getClassLoader());
        c.bind(name, value);
    } finally {
        Thread.currentThread().setContextClassLoader(cl);
    }
}

public <T> T lookup(String name, Class<T> valueClass) throws NamingException {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    try {
        Context c = new InitialContext();
        Thread.currentThread().setContextClassLoader(valueClass.getClassLoader());
        return (T) c.lookup(name);
    } finally {
        Thread.currentThread().setContextClassLoader(cl);
    }
}
I am passing the valueClass to bind() so that an interface can be used if wanted.

Sleeping dogs

Sometimes you forget those services that quietly run for months at a time in a corner of the datacenter. I am thinking of using gRPC to replace the communication layer for our micro-services. They currently use a home-grown RPC over HTTP that, while successful and simple to use for new services, is home-grown, non-streaming, and Java specific. One of our earliest micro-services handles over 2B requests per month. Perhaps I should let sleeping dogs lie....

“What’s in a name? That which we call a rose by any other name would smell as sweet.” Not in software!

Names have great signification in code. A good name can make the object's value comprehensible and usage clear. A variable called milk_jug is likely to have all the aspects of a jug, but specialized to only hold milk. I had a problem with picking a name.

I had a variable that did not itself hold the value, but acted as though it did. Is was not a proxy. Nor was it a reference. The variable held only a portion of the value but could itself get the remaining portion when needed. This kind of delayed acquisition behavior is common in programming. It is commonly used when the remaining portion of the value is costly to acquire, eg takes a long time to compute or retrieve, or holding it puts a burden on the system, eg using a significant amount of memory.

I ended up calling the variable mik_jug_winding. Winding as in "The river winds through the valley." I like it, but given that I felt the need to write a posting about it perhaps it lacks the needed signification. 

Java 8 is not Java

Java 8 is not Java. Java 8 is a good tool. Java's shift towards being a functional language is unstoppable. Java, however, as a principled, strongly typed, object-oriented, imperative programing language is no more. I have heard the argument that as a developer I can stick with Java 7 syntax using -source 7, but this is not practical. Libraries will evolve to use use functional practices. Old bugs will only be fixed in these evolved libraries. As functional practices becomes ever more rooted in the libraries their use from Java 7's syntax will become ever more cumbersome and perhaps impossible. If you want to continue developing in Java then you and your team will have to evolve with it.

So these days I am thinking ever more about what language to move to. I think the perspective of my employer would be to remain with a language targeting the JVM. I haven't asked, but I know well enough its conservative approach to solutions. The software architect in me would like to pick a language that is more aligned with a fail fast approach to service runtime and its corollary of needing to start fast.

Lots of folks at this week's O'Reilly Software Architecture conference in NYC are favoring Go (commonly called "golang" to make the conversation clearer and searchable). That it compiles to a binary (and even a statically linked binary) for direct execution on the host is a significant boost to start fast. Starting slow would be your architect's fault: Too much in-process preparation and not enough just in time initialization or not enough externalization of functionality into a service. I will look at Go and its libraries. I do worry that is the new black, like Ruby was several years ago.

Bundling a Java tool into an OS X application

A quick reminder to myself about bundling a Java tool into an OS X application.

Directory structure

JConsole.app
JConsole.app/Contents/
JConsole.app/Contents/Info.plist
JConsole.app/Contents/MacOS/
JConsole.app/Contents/MacOS/Run.sh
JConsole.app/Contents/Resources/
JConsole.app/Contents/Resources/Application.icns

Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleExecutable</key>
    <string>Run.sh</string>
</dict>
</plist>

Run.sh

#!/bin/bash

APP_DIR=$(dirname $(dirname $(dirname $0)))
APP_NAME=$(basename $APP_DIR .app)

$(/usr/libexec/java_home -v 1.8)/bin/jconsole \
-J-Dapple.laf.useScreenMenuBar=true \
-J-Dapple.awt.antialiasing=true \
-J-Xdock:name="$APP_NAME" \
-J-Xdock:icon="$APP_DIR/Contents/Resources/Application.icns" \
-J-Dcom.apple.mrj.application.apple.menu.about.name="$APP_NAME"

How to Handle an InterruptedException

Good reminder about how to correctly handle InterruptedException and why, again, to not indiscriminately catch and ignore Exception.

How to Handle an InterruptedException

True is true and false is false.

If you send me to code examples containing either of the following you are not going to be hired, ever.

test ? true : false

if ( test ) { return true; } return false;

Jolokia is remote JMX with JSON over HTTP.

We use JMX a lot for low level, dynamic management of our systems. Even the most basic of MBean implementation that uses the "MBean" interface suffix and one concrete class implementation is far better than nothing. With our commitment to using JMX we have, over the years, developed a set of Java annotations and machinery to publish POJOs as MBeans.

We continue to use JConsole, over Java Visual VM, because of its utter simplicity. The tool does have some issues, but we know them well and they do not distract from the need to manage something quickly. The one area where this tool and other tools that I am aware of fall down is making the same change on several deployments.

For this we fall back to using the bash shell and Jolokia. Jolokia is remote JMX with JSON over HTTP. It is a servlet that is part of our standard Tomcat deployment. We have been using it for many years and it has not failed us yet. So, if I need to change some state on all our machines I run

for h in $HOST_DEPLOYMENTS
do
    curl "http://$h/jmx/write/object-name/attribute-name/new-value &
done

Where object name, etc are replaced as expected. (Curl is normally called with --silent and --fail command line options.)

Traversing, aka picking for scraping part 2

Extending the idea in the Picking for scraping posting is the traverse. A traverse safely traverses a (typically) hierarchical data structure where any of the intermediary traversal steps may encounter a missing node. For example, in an hypothetical application most data instances have a value at the end of the a traversal from A to B to C to D, however, this data instance is missing the B node. Traversing from A to B is safe, but traversing from B to C will result in an null pointer exception (NPE).
The common ways to avoid NPEs while traversing is to check each node for null before traversing it. And so you see code that looks like a ladder, eg

value = data
if ( value != null ) value = value.A
if ( value != null ) value = value.B
if ( value != null ) value = value.C
if ( value != null ) value = value.D

While this works it is very cumbersome and prone to copy-paste errors due to small variations found on some ladder rungs.

Some languages offer syntax support for traversing. This is called a null coalescing operator. Java, the language I mostly work in, does not have this operator and so a different solution is needed.
The solution I use allows for a safe traversing using data format specific classes. That is, XML has its own traverser, Json has its own traverser, etc. A fully general mechanism might be possible, but the technique is simple and so really does not warrant one. Here is the example of safely traversing some Json data

value = new Traverse(data).at('A').at('B').at('C').at('D').value()

Or, let's have B be an array and we want to traverse the first element

value = new Traverse(data).at('A').at('B').at(0).at('C').at('D').value()

The dictionary at() method is

public Traverse at( String key ) {
    if ( value != null ) {
        if ( value instanceof Map ) {
            value = ((Map)value).get(key);
            // data might be null indicating an unsuccessful, 
            // but safe from NPE, traversal
        }
        else {
            // an unsuccessful traversal always sets data to null
            value = null;
        }
    }
    return this;
}

While the array at() method is

public Traverse at( int index ) {
    if ( value != null ) {
        if ( value instanceof List ) {
            value = ((List)value).get(index);
            // data might be null indicating an unsuccessful, 
            // but safe from NPE, traversal
        }
        else {
            // an unsuccessful traversal always sets data to null
            value = null;
        }
    }
    return this;
}

As with the Builder pattern, the Traverse pattern always returns the traverse instance.

A common aspect is the check for type and this can be refactored out.

public Traverse at( int index ) {
    List l = cast(List.class);
    if ( l != null ) {
        data = l.get(index);
    }
    return this;
}

protected <T> T cast( Class expectedClass ) {
    if ( value != null &amp;&amp; ! expectedClass.isAssignableFrom(value.getClass()) {
        value = null;
    }
    return (T) value;
}

Expanded, working versions of XML traversal and Json traversal are at [TODO].

Oh, how do you get the traversed to value?

public <T> T value() {
    return (T) value;
}

END

Picking for scraping

One of our developers coded what is essentially a data scraper. The implementation is very brittle. It has to become more robust. Without rewriting it, one technique is to use "pickers" to get valid values from a poor quality input. The way a picker works is that you declare where to get the data, what type it is, and what a default value is. A different picker is created for each way a value is accessed. Don't worry, there tends to be only a handful of data access patterns so not a lot of code will be created.

For example, the following code caused an NPE this morning because result didn't have a "data" child:

Element data = result.getChild("data");
int count = new Integer(Strings.noNull(data.getAttributeValue("count"), "0")).intValue();

If a picker were used then the code would have returned zero, the default value.


int count = picker(result,"data","count",0);

The key aspect of the picker is that it checks every working value before using it. For the example above, the picker would be implemented as

int picker(Element tree, String elementName, String attributeName, int defaultValue) {
    if (tree == null) {
        return defaultValue;
    }
    Element element = tree.getChild(elementName);
    if (element == null) {
        return defaultValue;
    }
    String attributeValue = element.getAttributeValue(attributeName);
    if (attributeValue == null) {
        return defaultValue;
    }
    try {
        return Integer.parseInt(attributeValue);
    }
    catch (NumberFormatException e) {
        return defaultValue;
    }
}

Externalizable needs a serialVersionUID too.

Yesterday I learned that even if your Java class serializes via java.io.Externalizable, so giving the class complete control over the marshaling and unmarshaling of the data, you must still defined a serialVersionUID or Java will create one for you. As methods signatures are included in the calculation of serialVersionUID my newly added method caused the creation of a different version id and so, unexpectedly, the marshaled data on the wire was incompatible. To fix the problem I just needed to define serialVersionUID to be the calculated version prior to my addition of the method. Luckily, the log contained the version number I needed!