Java's Evolution Explained In 10 Minutes: Key Features From Java 5 to 24
Are you preparing for a Java interview and wondering where to start? This article is packed with carefully selected questions, real code examples, and practical insights designed to help you understand what really matters in interviews, from fundamentals to real-world scenarios.
Let's get started with a 10,000-foot overview of some of the most important features released in different Java versions until now.
Java 5: The Dawn of Modern Features
Let's start with Java 5, or J2SE 5.0, which introduced several foundational enhancements.
Enhanced For-Loop
Before the enhanced for-loop, you needed to iterate through an array using a traditional index-based loop:
for (int i = 0; i < numbers.length; i++) {
// process numbers[i]
}
From Java 5, the enhanced for-loop simplifies this. If numbers
is an array or a collection, you can loop through it directly.
for (int number : numbers) {
// process number
}
Generics
Generics brought type safety to collections. You can create a generic class like HashMap
and specify the types for its key and value.
HashMap<String, Integer> map = new HashMap<>();
This enables the compiler to enforce type constraints, preventing runtime errors. Generics aren't limited to classes; you can also create generic methods and interfaces.
Enums
Enums provide a type-safe way to define a fixed set of constants. This is useful when a variable can only take one of a few possible values.
public enum Season {
WINTER, SPRING, SUMMER, FALL
}
Using enums avoids error-prone, hard-coded string or integer values.
Autoboxing
Autoboxing is the automatic conversion between primitive types and their corresponding wrapper classes.
Integer integer = 5; // autoboxing: int to Integer
int i = integer; // unboxing: Integer to int
This simplifies code by removing the need for manual conversions.
Java 7: Quality-of-Life Improvements
Java SE 7 introduced key features that improved resource management and code clarity.
Try-with-Resources
This feature automates resource management in try-catch
blocks. Any class that implements the AutoCloseable
interface can be used in a try-with-resources statement.
try (BufferedReader br = new BufferedReader(new FileReader("path/to/file"))) {
return br.readLine();
}
Java automatically calls br.close()
in a finally
block, preventing resource leaks even if you forget to close it manually.
Strings in Switch
Starting with Java 7, you can use String
objects in switch
statements, making the code more readable.
switch (day) {
case "MONDAY":
// ...
break;
case "TUESDAY":
// ...
break;
}
Java 8: The Functional Programming Revolution
Java 8 was a landmark release that introduced functional programming concepts to the language.
Lambda Expressions
Lambda expressions allow you to write concise, functional-style code by providing a shorthand for implementing functional interfaces.
Predicate<Course> reviewScoreGreaterThan95Predicate =
course -> course.getReviewScore() > 95;
This creates a function without the boilerplate of an anonymous class.
Streams API
The Streams API provides a powerful way to process collections using lambda functions. You can convert a collection into a stream and apply a chain of operations.
List<String> courses = list.stream()
.map(c -> c.getName().toUpperCase())
.collect(Collectors.toList());
Here, map
is an intermediate operation that returns another stream, while toList
is a terminal operation that produces a result. Streams and lambdas enable you to write immutable, declarative code.
Java 9: Modularization and Interactivity
Java 9 introduced the Java Platform Module System (JPMS) and an interactive tool for developers.
Modularization
The JDK was split into multiple modules for better scalability and smaller deployment packages. You can also build your own applications as modules, specifying which parts of your code are public and what dependencies they have.
JShell
JShell is an interactive command-line tool (REPL) for testing Java code snippets. You can type a statement and get immediate feedback without writing a full class.
jshell> int x = 10;
x ==> 10
Java 10: Local Variable Type Inference
Java 10 simplified variable declarations with the var
keyword.
Local Variable Type Inference (var
)
The var
keyword allows type inference for local variables, reducing verbosity.
// Before Java 10
ArrayList<String> list = new ArrayList<String>();
// With Java 10
var list = new ArrayList<String>();
The compiler infers the type ArrayList<String>
from the right-hand side of the assignment.
Java 14: Enhanced Switch Expressions
Java SE 14 improved switch
statements to return values, further reducing boilerplate.
String result = switch (day) {
case MONDAY, FRIDAY -> "Weekend is near";
default -> "Regular day";
};
This makes the code more concise and less error-prone compared to traditional switch
statements with break
clauses.
Java 15: Text Blocks
Text blocks were introduced to simplify the creation of multi-line strings, like JSON or HTML snippets.
String json = """
{
"name": "John",
"age": 30
}
""";
This eliminates the need for cumbersome escape characters and concatenation for new lines.
Java 16: Pattern Matching and Records
Java 16 brought two major features for writing more concise and readable data-oriented code.
Pattern Matching for instanceof
This feature combines the type check and cast into a single operation.
// Before Java 16
if (obj instanceof String) {
String s = (String) obj;
// use s
}
// With Java 16
if (obj instanceof String s) {
// use s directly
}
Record Classes
Records provide a compact syntax for creating immutable data objects. The compiler automatically generates a constructor, getters, hashCode()
, equals()
, and toString()
.
public record Person(String name, int age) {}
Java 17: Sealed Classes
Sealed classes and interfaces provide fine-grained control over inheritance, allowing you to specify which classes are permitted to extend or implement them.
public sealed class Vehicle permits Car, Truck, Bike { ... }
public final class Car extends Vehicle { ... } // No further subclassing
public non-sealed class Truck extends Vehicle { ... } // Can be extended freely
public sealed class Bike extends Vehicle permits ElectricBike { ... } // Still sealed
public final class ElectricBike extends Bike { ... }
This helps in designing more robust and secure class hierarchies.
Java 21: Virtual Threads and More
Java SE 21 introduced virtual threads, a major feature for improving concurrency.
Virtual Threads
Unlike traditional platform threads, which are mapped one-to-one with OS threads, virtual threads are lightweight and managed by the JVM. This allows you to create millions of virtual threads, dramatically improving the scalability of concurrent applications.
Sequenced Collections
The SequencedCollection
interface provides a unified API for collections with a defined encounter order, adding methods like addFirst()
, addLast()
, getFirst()
, and getLast()
.
Record Patterns & Pattern Matching for Switch
Record patterns support deconstructing record instances. This can be used in switch
statements for more powerful and declarative pattern matching.
// Record deconstruction
if (obj instanceof Transaction(var sender, var receiver, var amount)) {
// use sender, receiver, amount
}
// Pattern matching in switch
switch (message) {
case TextMessage(var text) -> System.out.println(text);
case Ticket(var ticketNumber) -> System.out.println(ticketNumber);
}
Java 22: Unnamed Variables
This feature allows you to use an underscore (_
) to denote unused variables in patterns and declarations, cleaning up the code.
switch (obj) {
case String _: // variable is not used
System.out.println("It's a string");
break;
// ...
}
Java 23: Markdown in Javadoc
Java 23 introduced support for Markdown in documentation comments, making it easier to write rich and readable Javadocs without using cumbersome HTML tags.
/**
* This is **bold** and this is *italic*.
*
* Here is a table:
* | Header 1 | Header 2 |
* |----------|----------|
* | Cell 1 | Cell 2 |
*/
Java 24: Stream Gatherers
Stream Gatherers introduce new ways to perform intermediate operations on streams. For example, you can use the gather()
method to group stream elements into fixed-size windows.
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.gather(Gatherers.windowFixed(3))
.forEach(System.out::println);
// Output: [1, 2, 3], [4, 5, 6], [7, 8, 9], [10]
Latest from new in the dev world
Bookmark This Article
Your browser doesn't support automatic bookmarking. You can:
- Press Ctrl+D (or Command+D on Mac) to bookmark this page
- Or drag this link to your bookmarks bar:
Clicking this bookmarklet when on any page of our site will bookmark the current page.
The Driving Force: Primary Goals of Recent Java Releases
The evolution of Java is guided by several key objectives.
- Conciseness and Readability: Features like records, switch expressions, and pattern matching aim to reduce boilerplate code, making it more readable and maintainable.
- Cloud and Container Readiness: With optimized JDK container images and GraalVM for native image compilation, Java is becoming more efficient for cloud-native environments, offering smaller footprints and faster startup times.
- Improved Concurrency: Virtual threads enable unparalleled parallelism, allowing applications to handle millions of concurrent tasks efficiently.
- Developer Productivity: Tools like JShell and simplified main methods help developers learn and test code more quickly.
- Backward Compatibility: A cornerstone of Java's philosophy is ensuring that code written for older versions runs on newer versions, providing a smooth migration path.
Debunking the Myth: Is Java Still Relevant?
There's a common myth that Java is old and unpopular. While it's true that Java is more than three decades old, it remains one of the most popular programming languages worldwide, consistently ranking in the top five.
Java has evolved faster than ever in the last decade, with a six-month release cycle ensuring fast updates and quick feedback. It is a top choice for building modern cloud applications, including web applications, REST APIs, and microservices, thanks to powerful frameworks like Spring Boot, Quarkus, and Micronaut.
With continuous innovation in functional programming, modularization, and concurrency, Java is not just staying modern—it's becoming more powerful and future-proof.