javac --release 8|11|17: compile for an older Java version (vs -source/-target)
- tool
- Java
- command
- javac
- flag
- --release <version>
- default
- the version of the JDK running javac
- since
- JDK 9
javac --release 11 Main.java compiles source code so that it runs on Java 11: it checks the language level, writes class files of the Java 11 format (major version 55), and, unlike -source/-target, only lets you use APIs that exist in Java 11. Without it, javac targets the JDK that runs it, and the resulting classes fail on older runtimes with UnsupportedClassVersionError.
One source file, three releases (JDK 17)
The program uses var (Java 10), List.of (Java 9), String.isBlank (Java 11) and Stream.toList (Java 16):
cat > New.java <<'EOF'
import java.util.List;
public class New {
public static void main(String[] args) {
var items = List.of(3, 1, 2);
String s = "abc";
System.out.println(items.stream().toList() + " " + s.isBlank());
}
}
EOF
mkdir -p r8 r11 r17 st8
echo "== --release 8"
javac --release 8 -d r8 New.java; echo "exit: $?"
echo "== --release 11"
javac --release 11 -d r11 New.java; echo "exit: $?"
echo "== --release 17"
javac --release 17 -d r17 New.java; echo "exit: $?"
javap -v r17/New.class | grep 'major version'
echo "== -source 8 -target 8"
javac -source 8 -target 8 -d st8 New.java; echo "exit: $?"
echo "== --release 21 on JDK 17"
javac --release 21 -d r17 New.java; echo "exit: $?"Output
== --release 8
New.java:4: warning: as of release 10, 'var' is a restricted type name and cannot be used for type declarations or as the element type of an array
var items = List.of(3, 1, 2);
^
New.java:4: error: cannot find symbol
var items = List.of(3, 1, 2);
^
symbol: class var
location: class New
New.java:4: error: cannot find symbol
var items = List.of(3, 1, 2);
^
symbol: method of(int,int,int)
location: interface List
New.java:6: error: cannot find symbol
System.out.println(items.stream().toList() + " " + s.isBlank());
^
symbol: method isBlank()
location: variable s of type String
3 errors
1 warning
exit: 1
== --release 11
New.java:6: error: cannot find symbol
System.out.println(items.stream().toList() + " " + s.isBlank());
^
symbol: method toList()
location: interface Stream<Integer>
1 error
exit: 1
== --release 17
exit: 0
major version: 61
== -source 8 -target 8
warning: [options] bootstrap class path not set in conjunction with -source 8
New.java:4: warning: as of release 10, 'var' is a restricted type name and cannot be used for type declarations or as the element type of an array
var items = List.of(3, 1, 2);
^
New.java:4: error: cannot find symbol
var items = List.of(3, 1, 2);
^
symbol: class var
location: class New
1 error
2 warnings
exit: 1
== --release 21 on JDK 17
error: release version 21 not supported
Usage: javac <options> <source files>
use --help for a list of possible options
exit: 2With --release 8 javac rejects var and reports that List.of and isBlank do not exist in Java 8. With --release 11 only Stream.toList() (added in Java 16) is missing. -source 8 -target 8 complains about var but accepts List.of because it compiles against the JDK 17 library; once var is fixed, that class would compile and then crash on a Java 8 runtime with NoSuchMethodError. A release newer than the compiler is refused.
Release, class file version, runtime
| --release | Class file major version | Runs on |
|---|---|---|
| 8 | 52 | Java 8 and later |
| 11 | 55 | Java 11 and later |
| 17 | 61 | Java 17 and later |
| 21 | 65 | Java 21 and later (needs JDK 21+ to compile) |
| 25 | 69 | Java 25 and later (needs JDK 25+ to compile) |
The class file version is what UnsupportedClassVersionError reports ("compiled by a more recent version of the Java Runtime (class file version 61.0)"); 61 means --release 17 or a JDK 17 default.
--release versus -source/-target
| Check | --release N | -source N -target N |
|---|---|---|
| Language features of version N | yes | yes |
| Class file version for N | yes | yes |
| Only APIs that exist in Java N | yes | no: compiled against the running JDK |
| Warning about missing bootstrap class path | no | yes: [options] bootstrap class path not set |
| Can be combined | not with -source, -target or --system | - |
Prefer --release whenever the compiling JDK is newer than the target. -source/-target are only needed for unusual setups such as cross-compiling against a custom boot class path.
In build tools
<properties>
<maven.compiler.release>17</maven.compiler.release>
</properties>tasks.withType<JavaCompile>().configureEach { options.release = 17 }Pitfalls
--releasedoes not make the code run on an older JVM that lacks a feature at the bytecode level; it simply refuses to compile such code, which is the point.- Set the same release for compilation and tests; a test compiled for a newer release than the main code hides API problems.
- Turn on javac -Xlint while migrating between releases: deprecation and removal warnings tell you what will break next.
FAQ
Is --release the same as -source and -target?
No. -source and -target only set the language level and the class file version; the code is still compiled against the newer JDK's class library, so calls to APIs that do not exist on the old Java version compile and then fail at run time with NoSuchMethodError. --release also checks the API, which -source/-target cannot do.
How do I set --release in Maven or Gradle?
Maven: set maven.compiler.release (or <release> in the compiler plugin, version 3.6+). Gradle: options.release = 17 on JavaCompile tasks, or a Java toolchain with languageVersion. Both pass --release to javac.
Which --release values does my JDK support?
Run javac --help and look at the --release line, or try the value: an unsupported one fails with error: release version N not supported. Each JDK supports its own version and a range of older ones; very old releases are removed over time.