Java Release Cycle and Version Context
Cadence Shifts and LTS Strategy
JDK 8 (March 2014) marked the last feature-driven major release before Oracle shifted to a strict 6-month time-based cadence starting with JDK 9 (September 2017). This change unblocks Java/JVM evolution by decoupling releases from large, high-risk feature bundles. For enterprise stability, Oracle and OpenJDK maintain Long-Term Support (LTS) versions, updated for 3+ years (with extended paid options available). Current recommended LTS releases for production are JDK 8, 11, and 17.
| LTS Version | GA Date | Public Support End | Extended Support End |
|---|---|---|---|
| 8 | 2014-03-18 | 2022-03 | 2030-12 |
| 11 | 2018-09-25 | 2026-09 | 2031-09 |
| 17 | 2021-09-14 | 2029-09 | 2036-09 |
| 21 | 2023-09-19 | 2031-09 | 2038-09 |
Oracle JDK vs OpenJDK
Both distributions share identical language and core API behavior, differing primarily in licensing:
- Oracle JDK: JDK 17+ is free for all users (personal, commercial, production). Earlier versions have usage restrictions for commercial deployments.
- OpenJDK: GPLv2-licensed, fully free for all use cases since September 2017, maintained by Oracle and the broader OpenJDK community.
JEP: Driving Java Evolution
All new Java features start as a JDK Enhancement Proposal (JEP), a formal specification outlining goals, design, risks, and testing requirements. Approved JEPs are assigned to a specific release and tracked on the OpenJDK website.
JDK 8: The Revolutionary Release
JDK 8 introduced foundational changes that modernized Java development: Lambda expressions, Stream API, functional interfaces, and Otpional. Below is a deep dive into core features.
Lambda Expressions
Lambda expressions are concise anonymous functions that replace verbose anonymous inner classes for single-abstract-method (SAM) interfaces. They follow the syntax (parameters) -> expression/block.
Syntax Examples
import java.util.function.Consumer;
import java.util.function.Supplier;
public class LambdaDemo {
public static void main(String[] args) {
// 1. No args, no return
Runnable greet = () -> System.out.println("Exploring Java 8 Lambdas!");
greet.run();
// 2. Single arg (type inferred, parentheses optional)
Consumer<String> printMessage = message -> System.out.println("Received: " + message);
printMessage.accept("What's the difference between a joke and a commitment?");
// 3. Multiple args, block body
Supplier<Integer> randomIntSupplier = () -> {
int min = 10;
int max = 100;
return (int) (Math.random() * (max - min + 1)) + min;
};
System.out.println("Random integer: " + randomIntSupplier.get());
}
}
Functional Interfaces
A functional interface has exactly one abstract method (SAM), though it can include default/static methods. Use @FunctionalInterface to enforce this constraint at compile time.
Core Built-In Functional Interfaces
JDK 8 ships with java.util.function providing reusable functional interfaces:
| Interface | SAM Signature | Purpose |
|---|---|---|
Consumer<T> |
void accept(T t) |
Consume an input without returning value |
Supplier<T> |
T get() |
Supply an output without input |
Function<T, R> |
R apply(T t) |
Transform input T to output R |
Predicate<T> |
boolean test(T t) |
Test a condition on input T |
Extended Functional Interfaces
The package also includes specialized variants for primitives (e.g., IntConsumer, DoubleSupplier) and multi-argument operations (e.g., BiFunction<T, U, R>, BiPredicate<T, U>).
Practical Exercises
Exercise 1: Filter and Transform Strings
Create a list of mixed-case strings, filter those longer than 5 characters, convert to uppercase, and print:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FunctionalExercise1 {
public static void main(String[] args) {
List<String> phrases = Arrays.asList(
"hello lambda",
"stream api",
"functional",
"java",
"openjdk",
"code"
);
List<String> transformed = phrases.stream()
.filter(s -> s.length() > 5)
.map(String::toUpperCase)
.collect(Collectors.toList());
transformed.forEach(System.out::println);
}
}
Exercise 2: Staff Salary Adjustment
Define a Staff class with id, name, and salary. Use BiFunction to create a salary adjustment logic, then apply it to all staff earning less than 12000:
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
class Staff {
private int id;
private String name;
private double salary;
public Staff(int id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
// Getters and Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
@Override
public String toString() {
return String.format("Staff{id=%d, name='%s', salary=%.2f}", id, name, salary);
}
}
public class FunctionalExercise2 {
public static void main(String[] args) {
List<Staff> techGuild = new ArrayList<>();
techGuild.add(new Staff(101, "Liu Yifei", 11500.0));
techGuild.add(new Staff(102, "Chen Kun", 14000.0));
techGuild.add(new Staff(103, "Zhou Xun", 10800.0));
techGuild.add(new Staff(104, "Deng Chao", 15500.0));
BiFunction<Staff, Double, Staff> adjustSalary = (staff, percentage) -> {
staff.setSalary(staff.getSalary() * (1 + percentage / 100));
return staff;
};
techGuild.stream()
.filter(s -> s.getSalary() < 12000)
.forEach(s -> adjustSalary.apply(s, 10.0));
techGuild.forEach(System.out::println);
}
}
Method, Constructor, and Array References
Method/constructor references simplify lambdas by reusing existing methods/constructors. They are written with the :: operator.
Reference Types
- Object::instanceMethod: Reuse an instance method of an existing object
- Class::staticMethod: Reuse a static method of a class
- Class::instanceMethod: Reuse an instance method where the first lambda parameter is the method caller
- Class::new: Constructor reference
- Type[]::new: Array constructor reference
Examples
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
public class ReferenceDemo {
public static void main(String[] args) {
// 1. Class::staticMethod (Function<Double, Long>: Math::round)
Function<Double, Long> rounder = Math::round;
System.out.println("Rounded 12.7: " + rounder.apply(12.7));
// 2. Object::instanceMethod (Consumer<String>: System.out::println)
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
fruits.forEach(System.out::println);
// 3. Class::instanceMethod (BiPredicate<String, String>: String::equals)
Function<String, String> toLowerCase = String::toLowerCase;
System.out.println("Lowercase 'HELLO': " + toLowerCase.apply("HELLO"));
// 4. Class::new (Supplier<Staff>: Staff::new)
Supplier<Staff> emptyStaffSupplier = Staff::new;
System.out.println("Empty staff: " + emptyStaffSupplier.get());
// 5. Type[]::new (Function<Integer, String[]>: String[]::new)
Function<Integer, String[]> arrayCreator = String[]::new;
String[] colors = arrayCreator.apply(3);
colors[0] = "red";
colors[1] = "green";
colors[2] = "blue";
System.out.println("Colors array: " + Arrays.toString(colors));
}
}
// Add no-arg constructor to Staff for Supplier usage
class Staff {
private int id;
private String name;
private double salary;
public Staff() {}
public Staff(int id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
// Getters, Setters, toString (same as before)
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
@Override
public String toString() {
return String.format("Staff{id=%d, name='%s', salary=%.2f}", id, name, salary);
}
}
Stream API
Stream API (java.util.stream) is a declarative, functional approach to processing collections/arrays. It supports parallel execution and lazy evaluation (intermediate operations are only executed when a terminal operation is triggered).
Stream Lifecycle
- Create: Generate a stream from a source (collection, array,
Stream.of(), etc.) - Intermediate: Transform the stream (returns a new stream; lazy)
- Terminal: Produce a result/side effect (closes the stream; eager)
Creation Examples
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class StreamCreation {
public static void main(String[] args) {
// 1. Collection source
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
Stream<Integer> listStream = numbers.stream();
// 2. Array source
IntStream intArrayStream = Arrays.stream(new int[]{2, 7, 1, 8});
// 3. Stream.of()
Stream<String> wordStream = Stream.of("java", "lambda", "stream");
// 4. Infinite stream (limit to 5 elements)
Stream<Integer> infiniteEven = Stream.iterate(0, n -> n + 2);
infiniteEven.limit(5).forEach(System.out::println);
}
}
Intermediate Operations
Common intermediate operations include:
filter(Predicate<T>): Exclude elementsdistinct(): Remove duplicates (usesequals()/hashCode())limit(long n): Truncate to first n elementsskip(long n): Skip first n elementsmap(Function<T, R>): Transform each elementflatMap(Function<T, Stream<R>>): Flatten nested streamssorted()/sorted(Comparator<T>): Sort elemenst
Terminal Operations
Common terminal operations include:
forEach(Consumer<T>): Iterate and process each elementcount(): Return number of elementsallMatch()/anyMatch()/noneMatch(): Test conditionsfindFirst()/findAny(): Retrieve elementsmax(Comparator<T>)/min(Comparator<T>): Find extreme valuesreduce(identity, BinaryOperator<T>): Aggregate elementscollect(Collector<T, A, R>): Collect elements into a container (e.g., List, Map)
Full Stream Example: Team Member Processing
Process two teams of members:
- TechGuild: Keep members with 3-character names, take first 3
- DevCircle: Keep members surnamed Zhang, skip first 2
- Merge teams, create
Memberobjects, print
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
class Member {
private String fullName;
public Member(String fullName) {
this.fullName = fullName;
}
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
@Override
public String toString() {
return "Member{fullName='" + fullName + "'}";
}
}
public class StreamExercise {
public static void main(String[] args) {
List<String> techGuild = new ArrayList<>();
techGuild.add("Liu Yifei");
techGuild.add("Chen Kun");
techGuild.add("Zhou Xun");
techGuild.add("Deng Chao");
techGuild.add("Sun Li");
techGuild.add("Ma");
techGuild.add("Wu");
techGuild.add("Zheng");
List<String> devCircle = new ArrayList<>();
devCircle.add("Gulnazar");
devCircle.add("Zhang Wuji");
devCircle.add("Zhao Liying");
devCircle.add("Zhang Sanfeng");
devCircle.add("Nicholas Zhang");
devCircle.add("Zhang Tianai");
devCircle.add("Zhang Ergou");
Stream<String> filteredTech = techGuild.stream()
.filter(name -> name.split(" ")[0].length() == 3)
.limit(3);
Stream<String> filteredDev = devCircle.stream()
.filter(name -> name.startsWith("Zhang"))
.skip(2);
List<Member> mergedMembers = Stream.concat(filteredTech, filteredDev)
.map(Member::new)
.collect(Collectors.toList());
mergedMembers.forEach(System.out::println);
}
}
JDK 9–17 Quick Highlights
Here’s a snapshot of key non-JDK-8 features from subsequent releases:
JDK 9
- Project Jigsaw: Modular system for better encapsulation and smaller runtime images
- JShell: Interactive REPL for quick Java testing
- Stream API additions:
Stream.iterate(predicate, generator)(terminating infinite streams) andStream.ofNullable(T)(safe single-element/null streams) - Private interface methods: Allow reusable helper code in interfaces
JDK 10
- Local Variable Type Inference (
var): Compiler infers type for local variables - G1 Parallel Full GC: Improves G1 garbage collector’s full GC performance
JDK 11
- HTTP Client (Standard): Modern, async-capable HTTP/2 client replaces
HttpURLConnection varfor Lambda Parameters: Allows type annotations on lambda params- Removals: Java EE/CORBA modules,
javahtool - Epsilon GC: No-op garbage collector for performance testing
JDK 12–16
- Switch Expressions: Standardized in JDK 14; use
->andyieldfor concise, expression-like switches - Text Blocks: Standardized in JDK 15; multi-line strings without escape characters
- Records: Standardized in JDK 16; immutable data classes with auto-generated
equals(),hashCode(), andtoString() - Sealed Classes/Interfaces: Previewed in 15/16, restrict which classes can extend/implement them
JDK 17 (LTS)
- Sealed Classes/Interfaces Standardized: Full production support
- Pattern Matching for
switch(Preview): Test switch cases with patterns - Strong Encapsulation by Default: No longer allows illegal access to JDK internal APIs
- Removals: Experimental AOT/JIT compiler, Applet API deprecated for removal
- Context-Specific Deserialization Filters: Improve security against deserialization attacks