I'm a little confused by how Scala and Java interact. For example, I am building a Play application which is using version 2.11.6 of Scala, and I need an object to represent a date.
Ideally, I would like to use LocalDate from Java 8, but is this possible if Scala 2.11 targets Java JDK 6?
I would like to get a better understanding of how different versions of Java work with Scala and how the two are related.
To answer your specific question, if you run scalac with JDK8, then yes you can use LocalDate with Scala 2.11.
As for the more general question, there's four basic ways Scala and Java interact that are decoupled from each other version-wise (at least in theory).
At compile time, some version of java must be running scalac
At compile time, scalac reads some version of JVM bytecode
At compile time, scalac emits some version of JVM bytecode
At run time, java (via the scala script) runs the JVM bytecode scalac emitted.
So let's see how this stacks up for Scala 2.10.5's scalac, Scala 2.11.x's scalac, and the upcoming Scala 2.12.x's scalac and Java 6, 7, and 8.
Scala 2.10.5:
scalac can be run by Java 6, 7, or 8 (due to java's backwards compatibility)
scalac can read Java 6 or 7 bytecode
scalac emits Java 6 (default), or 7 (optional) bytecode for the Scala-specific parts but if it reads Java 7 bytecode (e.g. because you're using a Java 7 library), then your final product will have Java 7 bytecode in it as well which is called by Java 6 bytecode.
Depending on your behavior in your previous step you will either have pure Java 6 bytecode which can then be run by Java 6, 7, or 8's java or bytecode with Java 7-specific features which can then only be run by Java 7 or 8's java.
Scala 2.11.x:
scalac can be run by Java 6, 7, or 8
scalac can read Java 6, 7, or 8 bytecode (depending on the version x of 2.11.x more or fewer Java 8 bytecode features can be read and used)
scalac emits Java 6 (default), or Java 7 (optional) bytecode
Same story as 2.10.5 here
Scala 2.12.x:
scalac can only be run by Java 8
scalac can read Java 8 bytecode and 7 bytecode by virtue of Java's backwards compatibility (I'm not sure about the story here whether scalac makes any additional effort to be more backwards compatible than Java 8's java is, e.g. for Java 6 compatibility)
scalac emits Java 8 bytecode
Java 8's java must be used to run the resulting binary
Just to emphasize that in theory these steps are completely decoupled, Scala 2.12's scalac could hypothetically be compiled by Scala 2.11's scalac to result in a scalac which can be run by Java 6, but whose resulting output could only be run by a Java 8 java.
In the case of Scala 2.11, you can use Java 8, as long as you provide those classes to scalac. The only special thing about LocalDate is that it is bundled with the Java Platform so you implicitly get access to it if you run scalac with Java 8 and don't if you run scalac with Java 6 or 7. If you are using a third-party Java 8 library, you could provide the compiled JAR to a Scala 2.11.x scalac to use regardless of which Java version is running scalac. That bytecode though may find its way into your own emitted JAR, which can complicate things for downstream users.
This means for any non-purely Scala project, you must let your consumers know both the Scala version and Java version you used for any JARs you generate. If your project is a library, and your consumer uses a compatible Scala version but an incompatible Java version, her/his code may compile, but will not run.
Scala version and Java version are distinct: you can run the same Scala version (let say 2.10.5) on different Java versions (e.g. 1.6, 1.7, 1.8).
But if you use JSE types which are specific to a JVM version, your Scala code won't run with a previous Java version. As if you build your Scala app/lib with a provided dependency, without having the runtime actually providing it.
Moreover, if the bytecode generated, by scalac according the configured Java version, is more recent than the one of the JRE (compiled in Java 8 but executed with JRE 1.6), it cannot be interpreted, as any plain Java code.
Related
I'm migrating multiple projects from Java 8 to Java 11 and I was wondering if Java 8 is forward compatible with Java 11.
In other words, is it possible to use artifacts compiled against Java 11 in Java 8 projects?
No, you cannot execute Java 11 bytecode on an older (any older) JVM. This has always been the case. Each new major compiler release has a new bytecode format that won't execute on older runtimes.
You CAN however execute bytecode from an older version on a newer JVM, so executing Java 8 bytecode on a Java 11 runtime is quite possible and you can thus use Java 8 (or older) compiled libraries in Java 11 projects, if the dependency requirements are met.
Do however keep in mind that Java 9+ removed a number of packages from the core libraries that were in there with Java 8, so you may need to supply new dependencies from third parties to replace those. Most notable of those (but certainly not the only ones) are XML parsers.
You can run projects that were compiled for Java 8 in Java 11 runtime.
You can run projects that were compiled for Java 11 in Java 8 runtime if you properly set the --target during compilation to target JDK 8 or less. This will of course also limit the set of features supported in the source code.
Generally, earlier versions of JDK are supported on later runtimes (i.e. Java 11 should run on Java 17), but there are some caveats because some of the features might get deprecated or changed. Always read the release notes and test before you upgrade.
From the docs
-source release Specifies the version of source code accepted.
If I have a JDK version , say , 1.8, and I mention -source=1.6 , what does it mean ? Does this only mean that whatever code I have written can be compiled by javac of JDK 1.6 or above ?
If that be case , why pass -source=1.6 during javac command ? As this will generate .class files and hence there is no source code left to mark (the source code compatibility to 1.6 or above) ? After javac command, all we get is the bytecode and no .java files.
Does this only mean that whatever code I have written can be compiled by javac of JDK 1.6 or above ?
Nope.
The -source=1.6 option means that your code can only use Java language constructs that are part of the Java 6 and earlier versions of the Java language.
For example, any Java 8 lambdas, or Java 9 var declarations would be flagged as compilation errors.
Java8 introduced lambda expressions. If you compile your application with -source=1.6 the compiler will not allow lambda expressions despite it being supported with JDK8.
If I have a JDK version , say , 1.8, and I mention -source=1.6 , what does it mean ? Does this only mean that whatever code I have written can be compiled by javac of JDK 1.6 or above ?
If your code uses Java 8 features, it won't even compile with -source=1.6. Otherwise, not necessarily true, but generally, yes, it should work with Java 6 and above.
If that be case , why pass -source=1.6 during javac command ? As this will generate .class files and hence there is no source code left to mark (the source code compatibility to 1.6 or above) ? After javac command, all we get is the bytecode and no .java files.
Javac is the Java Compiler. Of course it will generate .class files, as that is the compiled form of a java program. Why would you pass it? Let's say you want to target a specific version, this is the easiest way to keep support at that level.
You can test this by using a Java 8 feature ( lambdas, streams, datetimeformatter ), then try to compile. Your compilation will fail.
By specifying the source argument on the compiler, you are telling the compiler that you want the source code you are submitting to comply with that version of Java and check against specific language features for the version you selected (The default is the newest version typically, even if you don't specify source version yourself). The docs are clear on what values are acceptable and what value is the default. This does not change your source code or transform your code to older versions, it merely alerts you if you are using features that are in later versions of Java. If you are not using newer features of the java language then this will simply compile your code and generate class files as usual.
javac MyProgram.java -source 1.6
The command above will tell the compiler to treat the source code as it was compatible with Java version 1.6.
Below are the allowable values for JDK 1.8 and the description from the docs.
1.3 The compiler does not support assertions, generics, or other language features introduced after Java SE 1.3.
1.4 The compiler accepts code containing assertions, which were introduced in Java SE 1.4.
1.5 The compiler accepts code containing generics and other language features introduced in Java SE 5.
5 Synonym for 1.5.
1.6 No language changes were introduced in Java SE 6. However, encoding errors in source files are now reported as errors instead of warnings as in earlier releases of Java Platform, Standard Edition.
6 Synonym for 1.6.
1.7 The compiler accepts code with features introduced in Java SE 7.
7 Synonym for 1.7.
1.8 This is the default value. The compiler accepts code with features introduced in Java SE 8.
8 Synonym for 1.8.
Does the bytecode depend on the version of Java it was created with?
If I compiled a java file in the newest JDK, would an older JVM be able to run the .class files?
That depends on three things:
The actual Java versions you are talking about. For instance, a 1.4.0 JVM can run code compiled by a 1.4.2 compiler, but a 1.3.x JVM cannot1.
The compilation flags used. There is a -target compiler flag that tells it to generate code that will run on an older (target) JVM. And the -source compiler flag tells it to only accept the older JVM's language features. (This approach won't always work, depending on the Java language features used by your code. But if the code compiles it should work.)
The library classes that the class file uses. If it uses library classes that don't exist in the older class libraries, then it won't run ... unless you can include a JAR that back-ports the classes2. You can avoid this problem by using the -bootclasspath option to compile your code against the APIs of the older version of Java.
Does the bytecode depend on the version of the java it was created with?
Yes, modulo the points above.
1 - The Java 8 JVMS states this: "Oracle's Java Virtual Machine implementation in JDK release 1.0.2 supports class file format versions 45.0 through 45.3 inclusive. JDK releases 1.1.* support class file format versions in the range 45.0 through 45.65535 inclusive. For k ≥ 2, JDK release 1.k supports class file format versions in the range 45.0 through 44+k.0 inclusive."
2 - A backport could be problematic too. For example: 1) Things which depend on native code support would most likely require you to implement that native code support. 2) You would most likely need to put any back-port JAR file onto the bootclasspath when you run the code on the older JVM.
Does the bytecode depend on the version of the java it was created with?
Normally yes. But by using the -source, -target and -bootclasspath options, a 1.7+ compiler can be used to create binaries that are compatible with Java 1.1
First and foremost all java files have a version byte in the class header. Older jvms won't load classes with newer versions, regardless of what features they have.
JVM bytecode is forward compatible between major JVM version, but not backward compatible. However, for the best information you will have to read the JVM release notes because they typically indicate how backward compatible the bytecode is.
Edit clarification since this caused discussion in the comments
JVM bytecode is forward compatible, such that bytecode from one JVM is compatible with with later JVM releases. For example, you can take bytecode from the 1.4 JVM and run it in Java 5 or Java 6 JVM (aside from any sort of regression issues as pointed out by Andrew).
JVM bytecode is not backward compatible between JVMs, such that bytecode from a JVM is not guaranteed to work in a previous release of the JVM, as would be the case if you were attempting to run code compiled for Java 6 in a 1.4.2 JVM.
Does the bytecode depend on the version of the java it was created with?
Yes.
If I compiled a java file in the newest JDK, would an older JVM be able to run the .class files?
No. But the opposite will work, most likely. You might like see this interesting thread, it talks about backporting Java.
No, unless you specify as target the old JVM.
Eg.with Java 6 you can compile and run in Java 1.4 using:
javac -target 1.4 SomeClass.java
Obviously the source code should be 1.4 compatible.
You can compile classes that are older-version JVMs compatible if you don't use features available in higher JVMs.
javac -target 1.5 MyJava.java
javac -target 1.4 MyJava.java
To what degree can I use java 8 in a mixed java / scala 2.10 sbt project?
Can I emit java 8 bytecode? Use java 8 language features? Or are there features in scala 2.11 that are necessary? Is there an interop story?
From the 2.10.4 Scala release notes:
New ByteCode emitter based on ASM
Can target JDK 1.5, 1.6 and 1.7
Emits 1.6 bytecode by default
Old 1.5 backend is deprecated
Also
The official Scala 2.12 distribution will be built for Java 8 (and thus require it).
More background info of when you can expect to fully use Java 8:
The Scala 2.11.x series targets Java 6, with (evolving) experimental >support for Java 8. In 2.11.1, Java 8 support is mostly limited to reading >Java 8 bytecode and parsing Java 8 source. Stay tuned for more complete >(experimental) Java 8 support. The next major release, 2.12, will most >likely target Java 8 by default.
And...
Planned future Java 8-centric additions for Scala 2.12 include Java 8 >style closures, which will allow the Scala compiler to emit closure >classes (lambdas) just as you can with Java 8. There’s also planned lambda >syntax for SAM types. Again, in Java 8 fashion, this lets you instantiate >any type with one single abstract method by passing a lambda, improving >the user experiences for libraries written for Java 8 in Scala.
Will a Java 8 codebase that is compiled with Java 8 work on a Java 6 VM?
Yes, but if you don't make use of features of java 7 and java 8 ...
If the codebase is written using features of newer java versions, then no there's no way to do it!
However, if you want to run them on java 6 you have to set java 6 compliance level(with -source 1.6 -target 1.6 javac parameters) when you compile source code files, to make them compatible with java 6. If you don't set the compliance as shown above, you will get an UnsupportedClassVersionError :
java.lang.UnsupportedClassVersionError:Unsupported major.minor version XX.X
It generally only works the other way around, so running a Java 6 compiled application should run on a JVM of a higher version. The other way around is not possible I'm afraid, unless you don't use any new features and tell javac that your source version is 1.6 (i.e. -source 1.6) which essentially means you're programming Java 6 :).
Depending on what Java 8 features the code is using, you may be able to run Retrolambda on it and use the results in a Java 6 JVM. Maybe.