How to compile Java 9 and subsequent releases code in JDK8? - java

I have a maven aggregate project that contains api, application, domain, infrastructure and common-util modules.
The aggregate project JDK version is 17, but the common-util module alone uses JDK8 instead of 17 (for more compatibility).
All source code in common-util modules does not use Java 9 and subsequent releases' syntax, but this module uses the JDK HTTP Client provided since Java 11. The HTTP interface class has two implementation classes, Java11Impl and ApacheImpl, and the latter should be used for versions lower than Java11.
Now the problem is that I can't compile the module with JDK8, the compiler tells me package java.net.http does not exist.
Can I get the compilation done by javac options like -bootclasspath? How should I do this?

Related

Can I build a java class in jdk14 but use java 11 for my own library?

My library (using maven/java 11) has a dependency to a class in another library which used java 14 to compile the class.
So to build the dependency, I need to use java 14, but I don't want consumers of my library to have to be forced to upgrade to java 14. The class I'm dependent on is a simple domain class that doesn't use any java 14 features.
Is this possible? Or would I need to bump to java 14 in my own library?
If you're going to depend on a third part lib that uses java14, your project must use at least java14.
If you can rebuild and distribute a custom version of the third party lib # java11, you can keep your project at java11.
I highly recommend only sticking on LTS versions of Java: 7,8,11,17 as this makes it much easier for the ecosystem to use your code and build it. Java14 is already sunset.

How to validate dependencies' java version when compiling using higher version JDK?

We're using java 8 for most modules/projects, but for some of the modules, we use java 6 (customer requirements).
The developers have java 8 installed and we compile the java 6 projects using these flags:
compileJava {
sourceCompatibility = 1.6
targetCompatibility = 1.6
}
We thought we're all good until we upgraded guava from v20 to latest - 28.1-jre.
To our surprise, the build was successful but failed at runtime.
We have a workaround for building for java 6 using a specific javac found in JDK 6. See more info here. This workaround wields the error class file has wrong version 52.0, should be 50.0 in compile time. The downside is that it requires a download+config+usage of JDK 6 for developers.
Is there a way to validate the dependencies' java version at compile time when using a higher java version? (without installing lower version java) Thanks.
Setting -source and -target values to 1.6 is insufficient to ensure that the resulting output is compatible with 1.6. The program itself must not have any library API dependencies on later versions, and the -source and -target options don't do that. (GhostCat said pretty much the same thing.)
For example, in Java 8, ConcurrentHashMap added a covariant override for the keySet method that returns a new type ConcurrentHashMap.KeySetView. This type didn't exist in earlier versions of Java. However, in the class binary, the return type is encoded at the call site. Thus, even if the source code is compiled with -source 1.6 -target 1.6, the resulting class file contains a dependency on the Java 8 class library API.
The only solution to this is to ensure that only Java 1.6 compatible libraries are in the classpath at compile time. This can be done using the -Xbootclasspath option to point to a JDK 1.6 class library, or it might be simpler just to use a JDK 1.6 installation in the first place.
This applies to external libraries in addition to the JDK, as you've discovered with Guava. The Animal Sniffer project provides plugins for Ant and Maven that checks library dependencies for version problems. Offhand I don't know if there is something similar for Gradle. There might be a way to get Animal Sniffer to work with Gradle, but I have no experience with doing that.
Is there a way to validate the dependencies' java version at compile time when using a higher java version? (without installing lower version java).
You specify your dependencies. When you tell your built system to explicitly use some library X in version Y, then you made a very clear statement.
And you see, it is not only about the class file version number. What if some person doesn't pay attention, and compiles something with Java8 ... with Java6 target, but forgets that the code bases uses Java8-only API calls?!
In other words: you are looking in the wrong place.
The person who makes updates to the build description, and changes a library version from Y to Y+8, that person needs to carefully assess that change. For example by reading release letters.
I agree that a really clever build system could check if libraries you are using come in with a matching class file version. But as said, that is only one aspect of the problem. So instead of looking into a technical solution, I think the real answer is: don't step version numbers because you can, but because you have to. And that manual step of changing that version number, that is something that requires due diligence (on the side of the human doing it).
Thus: I think the most sane approach here is to compile the Java6 deliverables within their own specific build setup. Which you only touch after careful inspection of such details. And sure: convince your customer to move on, and give up a long dead version of Java.

How to create jigsaw module in SBT?

I would like to use jlink for creating self-contained application packages for all platforms (darwin, linux, windows) from Scala source code. It seems that jlink only works with new (relatively) jigsaw modules - so I need to package my code as a module. In Java world it seems to be easily achieved by placing special module-info.java file to the package that will become a module.
I tried to follow intuition and just placed this module-info.java into src/main/java/my.package.name/module-info.java. Though this doesn't work. It seems that scalac is trying to read module-info.java as usual Java file (which is not the case), hence the error
module-info.java:1:8: illegal start of type declaration
[error] module my.package.name {
[error] ^
What do I need to do to package my Scala code as a module?
Open JDK: 11
Scala: 2.12.4
SBT: 1.1.6
In general seems like that scala doesn't completely support Java9+, at least their compatibility notes read so.
As of Scala 2.12.6 and 2.11.12, JDK 9+ support is incomplete. Notably,
scalac will not enforce the restrictions of the Java Platform Module
System, which means that code that typechecks may incur linkage errors
at runtime. Scala 2.13.x will provide rudimentary support for this,
but likely only in nightlies built on Java 11.
You can follow Support features of JDK 9+ and Java 11 testing for further updates.

Can I use a jar, compiled in Java 7 as a dependency in a project that is compiled against Java 6?

Can I use a jar, compiled in Java 7 as a dependency in a project that is compiled for compatibility with Java 6? I do not have the source code of the dependency. I have the compiled jar as a maven dependency.
Can I use a jar, compiled in Java 7 as a dependency in a project that is compiled for compatibility with Java 6?
Let's unpick this:
You have a project that is compiled so that will run on a Java 6 JRE. (Lets suppose that you only use Java 6 APIs in that project.) The .class files for this project must have a classfile format major version less or equal to 50 ... otherwise a Java 6 JRE won't be able to load them.
Then you have a dependency that is "compiled in Java 7". That could mean one of two things:
It could have been compiled using a Java 7 tool chain but with a target version of Java 6.
It could have been compiled using a Java 7 tool chain for Java 7.
In both subcases above above, you should be able to use the dependency in your Java 6 project if you run the project on a Java 7 JRE1. A Java 7 JRE can load and run classfiles compiled for Java 6. In one of the subcases, you will be loading classes with two (or more) class version numbers. But that is OK.
On the other hand, if you try to run the code on a Java 6 JRE, then:
Subcase 1 will work provided that the Java 7 dependency doesn't make use of any Java 7 (or later) APIs; i.e. it only uses Java standard classes, methods, etc that were present in Java 6 or earlier.
Subcase 2 will not work. The Java 6 JRE won't be able to load the dependency. Indeed, if the dependency is static (i.e. the project source code has compile time dependencies on the APIs of the dependent), then the project code won't build ... because the Java 6 compiler should refuse to read the dependency's newer version classfiles.
The most advisable approach is to migrate your project and your execution platform to Java 7. Or better still to Java 8 or Java 11, since Java 7 is EOL'd
If you can't do that, the next best thing would be to avoid using the Java 7 dependency ... until you can upgrade.
If you have customers who insist they you continue to support Java 6, then they are impeding your ability to progress your product line. They should be charged a premium for that.
If you have decided to avoid upgrading your Java platform for internal reasons, this decision is accumulating technical debt ... that your organization will need to "pay down" that debt in the long term.
1 - .... or JDK. A JDK is equivalent to a JRE for the purposes of running code.
In your case you actually ask if there is Forward Compatibility between Java 6 and Java 7. Generally speaking Java does not support Forward Compatibility as the 1.7 JVM cannot run code compiled with 1.6. This happens mainly because the version of 1.7 compiled Java bytecode is not known by the older version (1.6).

Maven/Retrolambda: how to detect dependencies on Java 8 classes

Background:
We have maven-based java project, which targets JRE 1.7, but the source code uses lambdas, so we use retrolambda for transforming Java 8 source code to Java 7. Also we use StreamSupport backport library when we need streams, function.*, Optional, etc.
Usage of retrolambda involves configuring the project's both source and target language level to 1.8.
Everything works fine if there are no dependencies on java8 classes or methods (like java.util.stream.*, java.util.Optional, or methods introduced in java8 like Collection.forEach). If there are such usages then build passes, but it fails in runtime, when running under JVM of Java 8.
Question:
My goal is to fail the build in case when such dependencies exist. Is there any way of detecting dependencies on new Java 8 classes/methods in build-time?
I thought about 2 possible options, but I'm not sure whether either of them is doable:
Some kind of bytecode analyzer for detecting depdencies on predefined classes and methods. Are there such tools/maven plugins?
Lint (lint4j) rules. Not sure whether it's possible to detect dependency on class/method using lint
You can use the Animal Sniffer Maven Plugin for this. It allows you to check that your code only uses APIs from a specified baseline (called the "signature"). In your case you'd use the org.codehaus.mojo.signature:java17:1.0 signature.
As others pointed out, you also could set up the bootstrap classpath, but that a) requires a JDK 7 to be set up and b) makes the build a bit more complex as you need to point to the JDK 7 install. Animal Sniffer is is much easier to work with in my experience.

Categories

Resources