Wednesday, April 05, 2006

Compiling for Older JVMs

I've been doing some development on WebLearn from home today and had to perform build to be deployed on our test server. Now normally I develop on the same software as we run on the production machines but I just happened to only have the Java 1.5 SDK installed on my home machine. Not really thinking I set the target to 1.4 so that the outputted class files were compatible with the 1.4 JVM that we use on our testing and production machines. This is done through our ant build file with something like: <javac srcdir="src" destdir="build" source="1.4" target="1.4"/> The cruital bit I thought was the target attibute. However after shipping the build to the sysadmin guys they come back saying they're getting a stack trace when deploying the application: java.lang.NoSuchMethodError: java.lang.StringBuffer.insert (ILjava/lang/CharSequence;) Ljava/lang/StringBuffer; org.bodington.servlet.BuildingServlet.init (Unknown Source) which shows that although the class files could be run by the 1.4 JVM they used API calls that only exist in the 1.5 JVM. Now at first this is comfusing because the code compiles fine with a 1.4 compiler and classes so what has changed as all the 1.4 API calls should exist in 1.5 (apart from the deprecated ones that were removed). The problem comes from some code similar to this: StringBuffer str1 = new StringBuffer("123456"); StringBuffer str2 = new StringBuffer(" "); str1.insert(2, str2); Under the 1.4 StringBuffer API there isn't the method insert(int offset, StringBuffer str) so the compiler uses the method insert(int offset, Object str). In the 1.5 API again there isn't a perfect match but there is a better one than the Object one, it is the method insert(int offset, CharSequence str) and as StringBuffer implements CharSequence this closer API call is used. We could have fixed the code by changing it to: str1.insert(2, (Object)str2); but this just fixes one call and there maybe others that we didn't find. The real solution to this is to compile against the 1.4 classes when you are using the 1.5 compiler. Todo this you can use something like the following: <javac srcdir="src" destdir="build" source="1.4" target="1.4" bootclasspath="/home/buckett/j2sdk1.4.2_11/jre/lib/rt.jar" extdirs=""/> But of course for this you need to have downloaded the 1.4 SDK so I might as well have used the 1.4 compiler. However it is very likely that the 1.5 compiler contains better optimisations so WebLearn should run faster. However in future I think I'll just use the 1.4 compiler as it isn't worth the hassel. Sun does have a document on this this topic but labels it Cross Compiling. Why both the Sun and the ant documentation don't mention that when using the target option you probably also want to use the bootclasspath option. It seems obvious looking back but it wasn't at the time.