Understanding Source and Target
When compiling Java code, you can control which language features are allowed (source) and which JVM version the bytecode should be compatible with (target).
Key Concepts
- Source - Which Java language version syntax to accept
- Target - Which JVM version the bytecode should run on
- Release (Java 9+) - Combined source, target, and API compatibility
Compiler Options
Using javac Directly
# Compile for Java 11 source, targeting Java 11 bytecode
javac --source 11 --target 11 MyClass.java
# Short form
javac -source 11 -target 11 MyClass.java
# Using --release (recommended for Java 9+)
javac --release 11 MyClass.java
Source vs Target Explained
// With --source 8, this code FAILS (var not available in Java 8)
var list = new ArrayList<String>(); // Error!
// With --source 11, this code compiles
var list = new ArrayList<String>(); // OK
// --target controls the bytecode version
// Compiled with --target 11, runs on Java 11+ JVM only
The --release Option (Recommended)
Java 9 introduced the --release option which is the preferred way to specify compatibility. It sets source, target, AND restricts the available APIs to that version.
# Using --release ensures API compatibility
javac --release 11 MyClass.java
# Equivalent to (but safer than):
javac --source 11 --target 11 -bootclasspath <path-to-jdk11-rt.jar> MyClass.java
Why --release is Better
Using only --source and --target doesn't prevent you from using APIs that don't exist in the target version. The --release flag ensures you only use APIs available in that Java version.
Example: API Compatibility Issue
// Compiling with JDK 17: --source 8 --target 8
public class Example {
public void test() {
// This compiles! But String.isBlank() doesn't exist in Java 8!
boolean blank = "test".isBlank(); // Compiles but fails at runtime on Java 8
}
}
// Compiling with JDK 17: --release 8
// This correctly FAILS at compile time:
// error: cannot find symbol - method isBlank()
Maven Configuration
Using maven-compiler-plugin
<!-- Recommended: Using release (Java 9+) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<!-- Alternative: Using source and target -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
Using Properties (Shorthand)
<!-- In pom.xml properties section -->
<properties>
<!-- Recommended for Java 9+ -->
<maven.compiler.release>17</maven.compiler.release>
<!-- OR for older approach -->
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
Gradle Configuration
Kotlin DSL (build.gradle.kts)
// Using toolchain (recommended)
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
// OR using sourceCompatibility/targetCompatibility
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
// OR using release option
tasks.withType<JavaCompile> {
options.release.set(17)
}
Groovy DSL (build.gradle)
// Using toolchain
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
// Using source/target compatibility
sourceCompatibility = '17'
targetCompatibility = '17'
// Using release option
compileJava {
options.release = 17
}
Cross-Compilation Scenarios
Compiling for Older JVM
// Scenario: Develop on JDK 21, deploy on Java 11
// Use --release to ensure compatibility
<!-- Maven -->
<properties>
<maven.compiler.release>11</maven.compiler.release>
</properties>
// This prevents using Java 17+ features like:
// - Records
// - Sealed classes
// - Pattern matching
// - Text blocks
Feature Availability by Version
// Features require minimum source version:
// Java 8: Lambda, Streams, Optional
names.stream().filter(n -> n.startsWith("A"));
// Java 10: var for local variables
var list = new ArrayList<String>();
// Java 14+: Records (preview in 14-15, standard in 16+)
record Person(String name, int age) {}
// Java 15+: Text blocks
String json = """
{"name": "test"}
""";
// Java 17+: Sealed classes
sealed class Shape permits Circle, Rectangle {}
Troubleshooting
Common Errors
// Error: "source release X requires target release X or later"
// Cause: Target version is lower than source version
javac --source 17 --target 11 MyClass.java // Error!
// Error: "class file has wrong version X.0, should be Y.0"
// Cause: Running bytecode on older JVM than it was compiled for
// Error: "cannot find symbol" for newer APIs
// Cause: Using --release restricts available APIs
// Solution: Use APIs available in target version
Checking Effective Settings
# Maven - show effective compiler settings
mvn help:effective-pom | grep -A5 "maven-compiler-plugin"
# Gradle - show Java configuration
./gradlew properties | grep -i java
# Check compiled class version
javap -verbose MyClass.class | grep "major version"
Best Practices
Recommendations
- Always use
--releaseinstead of separate source/target for Java 9+ - Match your development JDK with the minimum supported runtime version when possible
- Test on the actual JVM versions you plan to support
- Use Java Toolchains in Gradle for consistent builds across environments
- Document the minimum Java version in your README