Don’t be fooled by javac -target 1.4
# 2006-09-15 11:43:52 -0400 | General / Java | 2 CommentsIn the last couple of weeks, I’ve been pinged a few times by cow orkers in the FishEye development team who were complaining about a mysterious JDK1.5 dependency in our code. Their development builds were breaking on a JDK1.4 JVM with an error like:
java.lang.NoSuchMethodError: java.lang.StringBuffer.append(Ljava/lang/CharSequence;)Ljava/lang/StringBuffer;
The sweat started pouring the first time I got that complaint; FishEye requires 1.4, we compile with -target 1.4 -source 1.4, and that method doesn’t exist in 1.4, it was added in 1.5.
Have production builds been going out like this?!
I knew the answer to that question was “no”, because we have a check like this in our Ant build (this uses some ant-contrib tasks):
<target name="check-prod-build">
<propertyregex property="build.jdk.version" input="${java.version}" regexp="^(1\.\d).*" select="\1"/>
<if><not><equals arg1="1.4" arg2="${build.jdk.version}"/></not>
<then>
<fail>Build aborting
====================================
A PROD BUILD REQUIRES JDK1.4.x ONLY!
====================================
You are using ${build.jdk.version} (${java.version}).
</fail>
</then>
</if>
</target></pre>
Then the reason for putting that check in came back to me: because -target 1.4 -source 1.4 can still produce class files that won’t run under 1.4. How? Because neither of those options change what rt.jar you are linking against.
This is best illustrated with a little example. Take this simple file:
public class Test {
public static void main(String[] args) {
StringBuffer b = new StringBuffer(”Hello, “);
CharSequence s = “world!”;
b.append(s);
}
}
When compiled with JDK1.4 it will run under both JDK1.4 and JDK1.5, but not when compiled under JDK1.5:
$ export JDK14_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/1.4/Home
$ export JDK15_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/1.5/Home
$ $JDK14_HOME/bin/javac -target 1.4 -source 1.4 Test.java
$ $JDK14_HOME/bin/java -cp . Test
$ $JDK15_HOME/bin/javac -target 1.4 -source 1.4 Test.java
$ $JDK15_HOME/bin/java -cp . Test
$ $JDK14_HOME/bin/java -cp . Test
Exception in thread "main" java.lang.NoSuchMethodError: java.lang.StringBuffer.append(Ljava/lang/CharSequence;)Ljava/lang/StringBuffer;
at Test.main(Test.java:5)
The culprit is the call to append(). In JDK1.4, the only call that matches is append(Object) and you end up with bytecode like:
invokevirtual #6; //Method java/lang/StringBuffer.append:(Ljava/lang/Object;)Ljava/lang/StringBuffer;
But with JDK1.5’s rt.jar it matches append(CharSequence):
invokevirtual #6; //Method java/lang/StringBuffer.append:(Ljava/lang/CharSequence;)Ljava/lang/StringBuffer;
Our Test.java is source-compatible with 1.4, the resulting Test.class as the correct class version for 1.4, but it is not link-compatible with 1.4 when compiled from 1.5. Have we learnt out lesson yet? Don’t be fooled by javac -target 1.4 -source 1.4: it can still produce class files that won’t run under 1.4.
Update Via Michael S and Chris N, you can also use the [code]-bootclasspath[/code], as described in the javac documentation.
I was searching this issue and found this info in the jdk docs that may solve the problem. Didn’t tried it.
http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/javac.html
http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/javac.html#crosscomp-example
adobni - That is correct, except that the example given by Sun is dated. The example only applies to older version of Java. For 1.4 compilation under 1.5 javac, the javac flag and value would look like this: -bootclasspath path-to-jdk1.4/lib/rt.jar