Discussion: Best way to target multiple java versions

I'm writing a library/app that is using some features available in 1.4 that are not available in 1.3. The library and app will still work under 1.3, but our customers will get extra "goodies" and flexibility with 1.4.

As a simple example, 1.4 has support for KeyEvent locations, whereas 1.3 does not, so keyboard mappings are more flexible when running under 1.4.

Right now I check the vm version and use reflection to see what's avaiable to use, and if the 1.4 features are around I use them via reflection, otherwise I resort to 1.3 compatible code.

e.g.

if (Float.parseFloat(NumberPreParser.parseDecimal(System.getProperties().getProperty("java.vm.version")))>1.3f)

try

{

this.getClass().getMethod("somethingOnlyAvailableIn14",new Class[]{Boolean.TYPE}).invoke(this,new Object[]{new Boolean(true)});

}catch (Exception ex)

{

logger.warn("Tried calling somethingOnlyAvailableIn14(), and failed, resorting to 1.3 method", ex);

doSomethingThe13Way();

}

This got me thinking that there must be a lot of this sort of stuff going on. So what are some other "tricks of the trade" to make this process a little cleaner and more maintanable?

[1748 byte] By [mgbolusm] at [2007-9-27 17:22:54]
# 1

A technique I've used is based around using classloaders.

A core jar contains the common code. JVM specific jars contain the JVM specific implementations of the classes and configuration information for the application. I try make sure that all the JVM specific classes are coded to well defined interfaces and if necessary use factories (or abstract factories) to control their instances.

Initially only the bootstrap class needs to be on the classpath. When the bootstrap class in run, it checks the JVM and constructs a classloader containing the JVM specific jar and then the core jar (the order is important).

The classloader is used to initialise and run a core application class from the core jar that inherently uses the classloader to create jvm specific instances of the various classes or if necessary uses the factories.

It's a little effort to set up, but once you're there it is very easy to maintain as long as you've got decent source control. It also can greatly simplify patching of JVM version specific bugs.

This all works because the classes are loaded from the first jar that contains them, if a version of the class also exists in a jar lower in the sequence in the classloader then it gets ignored.

You can go further with this approach and add the JVM specific jars for older JVMs (in descending sequence) to the classloader before the core jar. This then means you can have an incremental approach to features, say there is a feature in 1.3 and 1.4 but not in 1.2, you then only need the class to be in the 1.3 specific jar, the 1.4 version will automatically pick up the class from the 1.3 jar.

You can go further still with this and, using the bootstrap class to follow a process of discovery and classloader creation, deliver an application completely customised to the platform on which it is running. I've successfully used this to deliver an application that would use Java3D for the UI if available.

There can be issues around compatibility of different versions of the same class with the core classes if you are not using interfaces and factories, but there are ways of controlling it.

This approach can be overkill, but for the right application...

Bob Boothby.

bob_boothby at 2007-7-6 12:18:31 > top of Java-index,Core,Core APIs...