Write Once, Compile Twice, Run Anywhere

Many Java developers use a development environment different from the target deployment environment.  At Flurry, we have developers running OS X, Windows, and Linux, and everyone is able to contribute without thinking much about the differences of their particular operating system, or the fact that the code will be deployed on a different OS.

The secret behind this flexibility is how developed the Java toolchain has become. One tool (Eclipse)  in particular has Eclipsed the rest and become the dominant IDE for Java developers. Eclipse is free, with integrations like JUnit support, and a host of really great plugins making it the de facto standard in Java development, displacing IntelliJ and other options.  In fact, entry level developers rarely even think about the compilation step, because Eclipse's autocompilation keeps your code compiled every time you save a file.

There's Always a Catch

Unfortunately no technology is magical and while this set up rarely causes issues, it can. One interesting case arises when the developer is using the Eclipse 1.6 compiler compliance and the target environment uses Sun's 1.6 JDK compiler.  For example at Flurry, during development we rely on Eclipse's JDT Compiler, but the code we ship gets compiled for deployment on a Jenkins server by Ant using Sun's JDK compiler. Note that both the developer and continuous integration environment are building for Java 6, but using different compilers. 

Until recently this never came up as an issue as the Eclipse and Sun compilers, even when running on different operating systems, behave almost identically.  However, we have been running into some interesting (and frustrating) compiler issues that are essentially changing "Write Once, Run Anywhere" into "Write Once, Compile Using Your Target JDK, Run Anywhere."  We have valid 1.6 Java code using generics, which compiles fine under Eclipse, but won't compile using Sun's javac.

Let's See an Example

An example of the code in question is below. Note that it meets the Java specification and should be a valid program. In fact, in Eclipse using Java 1.6 compiler compliance the code compiles, but won't compile using Sun's 1.6 JDK javac.

Compiling this code using javac in the Sun 1.6 JDK gives this compiler error:

"Write Once, Run Anywhere" never made any promises about using different compilers, but the fact that our toolchain was using a different compiler than our build server never bore much thought until now.

Possible Solutions

The obvious solution is to have all developers work on the same environment as where the code will be deployed, but this would defer developers from using their preferred environment and impact productivity by constraining our development options. Possible solutions we have kicked around :

  1. Have ant compile using the Eclipse incremental compiler, (using flags  -Dbuild.compiler=org.eclipse.jdt.core.JDTCompilerAdapter and of course -Dant.build.javac.target=1.6). This side steps the problem by forcing the continuous integration system to use the same compiler as developer laptops, but is not ideal as this was never an intended use of the Eclipse compiler. 
  2. Move to the 1.7 JDK for compilation, using a target 1.6 bytecode. This solves this particular issue, but what happens in the future?
  3. Change the code to compile under Sun's JDK. This is not a bad option but will cost some speed of development found in the simplicity of Eclipse's built in system. 

My experience has been that Eclipse is a well worn path in the Java world, and its a little surprising that this hasn't come up before for us given the heavy use of generics (although there are lots of generics issues which have been logged over at bugs.sun.com, like http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6302954 which has come up for us as a related issue - the "no unique maximal instance exists" error message). 

Switching to use the Eclipse compiler for our deployable artifacts would be an unorthodox move, and I'm curious if anyone out there reading this has done that, and if so, with what results.

We had a discussion internally and the consensus was that moving to 1.7 for compilation using a target 1.6 bytecode (#2) should work, but would potentially open us up to bugs in 1.7 (and would require upgrading some internal utility machines to support this).  We aren't quite ready to make the leap to Java 7, although its probably time to start considering the move. 

For now, we coded around the compiler issue, but its coming up for us regularly now, and we are kicking around the ideas on how to resolve.  In the near term, for the projects that run into this generics compile issue, developers are back to using good ole ant to check if their code will "really" compile.  Its easy to forget how convenient autocompilation has become, and the fact that it isn't really the same as the build server's compiler.
1 response
I've run into this before. There are corner cases where code that is tolerated by the Eclipse VM runs into trouble either compiling or running under Sun/Oracle JDK.

The solution is what you're doing- use the same toolchain (ant) to do "external" compilation and automated testing during development, otherwise you have divergence between your development and release environment. If you don't run the standard build on your dev box, you can get tripped up by any number of things that may be specific to an individual developer's environment. In my standard build process, I freeze my dependencies and resolve from a local repo (no automatically resolving dependencies from the internet) and reset the compilation and test environment automatically to avoid any stray variables.

You don't have to give up using Eclipse for for its code intelligence capabilities and on occasion for basic smoke testing during development. Just make sure Eclipse and the ant build don't use the same "bin" directory for compilation output.

I don't miss not having incremental compilation because my projects are split up into modules - the largest one takes about 20 seconds max to build, make a 2MB jar, and run a suite of tests. If your builds take so long that incremental compilation is required to maintain productivity, you might want to consider modularizing things some more.

I wouldn't use the Eclipse compiler for deployable artifacts. Nothing against Eclipse per-se, but the Oracle/Sun VM is the reference for standard behavior and I've found it to be stricter than Eclipse.