Keep one "master" source root that builds under JDK 5. Add a second parallel source root that has to build under JDK 6 or higher. (There should be no overlap, i.e. no classes present in both.) Use an interface to define the entry point between the two, and a tiny bit of reflection.
For example:
---%<--- main/RandomClass.java
// ...
if (...is JDK 6+...) {
try {
JDK6Interface i = (JDK6Interface)
Class.forName("JDK6Impl").newInstance();
i.browseDesktop(...);
} catch (Exception x) {
// fall back...
}
}
---%<--- main/JDK6Interface.java
public interface JDK6Interface {
void browseDesktop(URI uri);
}
---%<--- jdk6/JDK6Impl.java
public class JDK6Impl implements JDK6Interface {
public void browseDesktop(URI uri) {
java.awt.Desktop.getDesktop().browse(uri);
}
}
---%<---
You could configure these as separate projects in an IDE using different JDKs, etc. The point is that the main root can be compiled independently and it is very clear what you can use in which root, whereas if you try to compile different parts of a single root separately it is too easy to accidentally "leak" usage of JDK 6 into the wrong files.
Rather than using Class.forName like this, you can also use some kind of service registration system - java.util.ServiceLoader (if main could use JDK 6 and you wanted optional support for JDK 7!), NetBeans Lookup, Spring, etc. etc.
The same technique can be used to create support for an optional library rather than a newer JDK.