Source/Target Compatibility

Controlling Java Compilation Version Settings

← Back to Index

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 --release instead 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