I've just stumbled across the following ( http://docs.oracle.com/javase/8/docs/technotes/guides/language/assert.html#compatibility ):
Unless you specifically request source mode 1.4 with the -source 1.4
flag, the compiler operates in source mode 1.3. If you forget to use
this flag, programs that use the new assert statement will not
compile. Having the compiler use the old semantics as its default
behavior (that is, allowing assert to be used as an identifier) was
done for maximal source compatibility. Source mode 1.3 is likely to be
phased out over time.
Please note that this is from the official Oracle documentation for Java 8 --- but I have never run into this particular compile-time error before. More than that,
class Test{
public static void main(String[] args){
int a = 0;
assert a < 0: "a is not negative!";
}
}
compiles without a hitch not only under Java 8 but also by 'flagless' invocation of
javac Test.class
with JDK 1.6.0_24 (the oldest I happen to have; ~ March, 2011).
Apparently, the transition to assert as a new keyword is well over.
So these are my questions: when did it happen, specifically? and why on earth Oracle keeps adding this admonishment ("If you forget to use this flag, programs won't compile"; look, it's italicized, even! ) to the docs for each new version?
It's not just idle curiosity that makes me wonder 'when' and 'why': in four days I'm going to write my OCP exam for Java 8 (1Z0-809), and I expect all sorts of nasties jump at me from dark corners...
This official Java guide : http://docs.oracle.com/javase/8/docs/technotes/guides/language/assert.html#compatibility was written for the Java 1.4 release that has introduced the assert statement that was not previously available (in the 1.3 version).
In fact, this guide comes from this page that refers some enhancements of each released Java version :
http://docs.oracle.com/javase/8/docs/technotes/guides/language/enhancements.html
It comes from this particular section :
Enhancements in J2SE 1.4
Assertion Facility - Assertions are boolean expressions that the
programmer believes to be true concerning the state of a computer
program. For example, after sorting a list, the programmer might
assert that the list is in ascending order. Evaluating assertions at
runtime to confirm their validity is one of the most powerful tools
for improving code quality, as it quickly uncovers the programmer's
misconceptions concerning a program's behavior.
So, in fact it is not really a problem of not updated documentation.
You have to consider this information as a guide for the specified version : J2SE 1.4.
Having this kind of history is useful to have an overview of the Java language evolution through the versions.
But your tests are accurate and you can also get a validation from the javac official documentation that is updated at each release that javac with a JDK 8 doesn't use the 1.3 source by default.
When you compile a class, the javac command may take as argument a source that specifies the major JDK release to accept in the source code.
From the official Java 8 documentation :
-source release
Specifies the version of source code accepted. The following values
for release are allowed :
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.
If this argument is not specified, it has a default value.
And you can read that 1.8 is the default value.
Consequently, the compiler accepts source code with features introduced in Java SE 8 (assert from 1.4 included of course).
Related
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.
I try to find a use case when it is necessary to use a special target version of the compiler javac using the target parameter.
Java is backward compatible, isn't it? So, if I compile a hello world program with version 11, it could run on a JVM with version 8, or?
The only use case I could imagine is, when you have dependencies (other jars) which are compiled in a certain version and you have to match this special version when compiling the own code.
Java is backward compatible, isn't it? So, if I compile a hello world program with version 11, it could run on a JVM with version 8
That is exactly backward. If you have a version of a Java class compiled with version 8, Java 11 is backwards compatible and can run it. The reverse is not backwards compatibility, and is the purpose of the --target command line flag. Specifically so a class compiled by the Java 11 compiler can run on Java 8. Without that, you would get an java.lang.UnsupportedClassVersionError: Unsupported major.minor version
By default, classes are compiled against the bootstrap and extension classes of the platform that javac shipped with. But javac also supports cross-compiling.
-target version
Generate class files that target a specified version of the VM. Class
files will run on the specified target and on later versions, but not
on earlier versions of the VM. Valid targets are 1.1, 1.2, 1.3, 1.4,
1.5 (also 5), 1.6 (also 6), and 1.7 (also 7) ... . The default for -target depends on the value of -source:
If -source is not specified, the value of -target is 1.7
If -source is 1.2, the value of -target is 1.4
If -source is 1.3, the value of -target is 1.4
If -source is 1.5, the value of -target is 1.7
If -source is 1.6, the value of -target is 1.7
For all other values of -source, the value of -target is the value of -source.
Refer javadoc for more details here.
I try to find a use case when it is necessary to use a special target version of the compiler javac using the target parameter.
A common scenario is roughly:
production system runs your Java program
individual engineers write/maintain that Java program
JDK versions on engineers' laptops is newer than JDK on production system
Using the "target" flag, it's possible for the engineers to write software using newer/later JDK versions, while still being able to run it on an older JDK (on a production system).
And "older" really doesn't need to be all that old... for example, a production system might be running in Amazon using the latest version of Corretto (which is currently 18, with all other available versions being 8, 11, and 17), and individual engineers could have JDK 19 or 20 on their machines.
The Java Language Spec discusses this topic in Chapter 13, Binary Compatibility:
This chapter specifies minimum standards for binary compatibility guaranteed by all implementations. The Java programming language guarantees compatibility when binaries of classes and interfaces are mixed that are not known to be from compatible sources, but whose sources have been modified in the compatible ways described here.
And various more specific comments in JLS 13.2, What Binary Compatibility Is and Is Not:
A change to a type is binary compatible with (equivalently, does not break binary compatibility with) pre-existing binaries if pre-existing binaries that previously linked without error will continue to link without error.
Note: I posted links specifically from JLS version 1.8 as it's relevant to versions in this question (8 and 11), but Java's treatment of binary compatibility hasn't changed
in prior or subsequent versions.
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
I am compiling my web app in Netbeans against Java EE 5. I know that the String.isEmpty() function is only supported in Java 6. Having said that, I can still compile my project using the .isEmpty() in my code.
How come Netbeans is allowing my web app to compile if I am compiling against Java EE 5?
The compiler level is not equal to the JDK level you use for compiling. The compiler level only checks for the syntax and of course creates a different output. But the compiler itself will use the jdk on your classpath so if you compiled with java 5 option but with the java 6 jars on your classpath the code will compile without an error.
You should check your classpath.
As many have pointed out, Java EE versions are not strictly tied with Java SE (JDK) versions. Mostly, they require a minimal Java SE version but are compatible with later versions.
Java EE 5 specification (downloadable PDF here) says:
This specification requires that containers provide a Java Compatible™ runtime
environment, as defined by the Java 2 Platform, Standard Edition, v5.0 specification
(J2SE)
Since JSE versions are backwards compatible, you can take a container compatible with Java 5 and run it on top of Java SE 6 or Java SE 7.
You can check the compatibility level that Netbeans is using by checking the project "Properties > Source > Source/Binary Format"
If you still have doubts about "Java vs Java EE" you can look for several questions here on SO about the difference between Java SE and Java EE.
May I know if there are any issues running a Java 5-compiled code (with Java 1.3 source/target compliance) on a Java 1.3 JVM?
I know this is quite odd, but most of our customers are on Java 5 but we are restricted by a few still on Java 1.3 due to conditions beyond our control. Our CI tool uses Java 5 compiler but we set our source and target compliance to Java 1.3 for backward compatibility. We are not using Java 1.3 for CI because there are unit tests that does not compile on 1.3.
Will this be an issue?
Thank you in advance!
Cheers,
- Paul
The only way to get the compiler to check that source in your app. only uses 1.3 methods is to specify a -bootclasspath pointing to a 1.3 rt.jar. Note you can get that from a 1.3 JRE, it does not require a 1.3 SDK/compiler. Of course, specify a -target of 1.3.
The biggest problem you will have is that while the JVM generated byte code will be compliant with 1.3, the JDK you're compiling against is Java 5. It is very easy to use a class, or more likely, a method that exists in Java 5, but not in the 1.3 JDK. This code will compile fine but will fail to run on a 1.3 runtime.