Beware the Siren's -Target Call

Written by: Stephen Connolly
3 min read
Stay connected

By and large, Java has been a great place for backwards compatibility. You can use a dependency compiled against an old version of Java and run it in the latest shiny new version of Java and have a very reasonable expectation that it will “just work™”. Of course there are always gotchas, but those have tended to be the exception but not the rule.

Java 8 changes things. This may or may not be a good thing… certainly anything that lets us all move towards being able to use Lambdas everywhere is a good thing… but if that puts a world of pain onto those a sizeable number of us who are not able to make the jump now… well that would be a bad thing.

So how does Java 8 change things?

Let’s start with the humble java.util.Iterator. In Java 8 they added a helpful default implementation of remove().

What’s wrong with that you ask? Seems reasonable alright… the default cannot hurt any old code as that will already have an implementation of remove() and it makes life easier for people going forward.

Well I was working on some code and somehow or other my IDE got switched to JDK 8 (probably to do with me trying to switch to running the tests with Java 8) and then I created a new Iterator implementation. The IDE helpfully created placeholders for all the methods to be implemented

public class IteratorImpl<e> implements Iterator<e> {
    public boolean hasNext() {
    public E next() {
}</e> </e> 

My build is set to compile with -source 7 and -target 7. I have animal sniffer checking to ensure that the methods referenced were only those methods that exist in JDK 7. I have another check to ensure that all the byte code from dependencies is compatible with JDK 7’s class version… and only for Jenkins catching the compile error because it compiles with JDK 7… I could have ended up releasing a jar file that looks to be JDK 7 compatible but actually isn’t… all because of that default method.

This is not a new problem by the way. Java 5 to Java 6 has some hidden cases where an argument type was widened from String to CharSequence… so you can end up with the bytecode containing a reference to StringBuilder.append(CharSequence) even though you are only ever passing a String… all because you compiled with Java 6, -source 1.5 and -target 1.5

The StringBuilder.append(CharSequence) type problem can be caught by animal sniffer, but things like default methods can only be caught by compiling against the actual target JDK… of course you have Jenkins doing that already for you, don’t you?

So that’s you covered… but what about all your dependencies?

How do we know that those dependencies are being built with the actual JDK that they use as a minimum version? How do we know that those dependencies actually have an implementation of Iterator.remove(). Jenkins can only catch that if you have a test case that covers the code path...

I sense an enhancement for animal sniffer in the future… but for now, be careful and beware the siren’s call that is using a -target to compile bytecode for an older JDK

Stephen Connolly
Elite Developer and Architect

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.