Introduction :
Java is one of the most popular programming languages, and it continues to improve with each new version. Over the past few years, Java has introduced many useful features that make it easier for developers to write clean, efficient code and build modern applications more quickly.
In this blog, we will focus on Java versions 9 through 21, highlighting the most important and valuable features that have been introduced during these releases. We’ll explore how these features make the language more efficient, secure, and easier to use, helping developers build better applications faster. Whether you are a beginner or an experienced Java developer, this blog will provide insights into how these features can improve your coding experience and keep your Java skills up to date.
Prerequisites :
- Basic Java Knowledge
- Java Development Environment
- Understanding of Java Syntax
- Previous Java Versions
- IntelliJ IDEA or Any other IDE
Java 9(September 2017) : Java Module System
Java 9 has one of the major changes in its features which is the Java module System. The fundamental idea of the system is to collect Java packages and code into a single unit known as a module. The reason for adding this functionality to Java is that previous versions before 9 did not have a system for creating modular applications. That is why the application’s size has grown. Even in earlier versions of Java, the JDK file was huge; only the rt.jar file was roughly 64 MB. To avoid this problem, Java 9 has divided the JDK into modules, allowing us to construct our application using only the module that is required.
Key Features :
- Monolithic JDK : Before Java 9, the JDK was like a single block; we had to include everything, even if we needed only a small part of it. Java 9 divided it into modules, which allow you to use only what is required, resulting in lighter programs.
- Encapsulation Issues : Java 9 introduced encapsulation issues by introducing strong encapsulation through the module system.This means that a module can indicate which of its packages are only available to other modules.
- Dependency Management : The module system in Java 9 allows for explicit declaration of dependencies between modules. This explicit dependency management improves the reliability and maintainability of applications.
- Performance Optimization : With Java 9, you can create a smaller, custom runtime that includes only the parts your application needs. This makes apps run faster and use less memory.
- Better Maintenance : The modular structure of Java 9 improves code maintainability by promoting better organization and separation of responsibilities. This modular approach also facilitates easier updates and upgrades.
Advantages:
- Java 9 adds additional functionality to the Stream, Optional, CompletableFuture, and Collections APIs, as well as a new Process API for improved system process control.
- A modern HTTP/2 client is introduced to replace the old HttpURLConnection.
- Java 9 prevents accidental use of internal APIs, which improves stability and security.
- The jlink tool helps create custom runtime images, reducing memory usage and startup time.
- Java 9 promotes modular development and better code organization through the Java Module System (Jigsaw).
DisAdvantages:
- Migrating existing programs to Java 9 can be difficult, particularly if the libraries are not yet modularized or have circular dependencies.
- Developers will need to put in more effort to learn and adapt to the new module system.
- Some IDEs and build tools provided limited support for Java 9 during its early adoption.
Java 10(March 2018) : Local Variable Type Inference
What is type interface?
Type inference means that the compiler may automatically identify the data type of a variable based on the value provided to it, avoiding the need to explicitly declare the type.
What is the local variable type interface?
Local Variable Type Inference, introduced in Java 10, allows the compiler to automatically determine the type of a local variable when the var keyword is used. This makes code cleaner and easier to write while remaining type-safe. It works only for variables declared inside methods, loops, or blocks. The variable must be initialized at the time of declaration because the compiler requires a value to determine the type.
//Old way of declaring local variable.
String name = "Welcome to IGNEK";
//New Way of declaring local variable.
var name = "Welcome to IGNEK";
//Example :
import java.util.List;
public class LocalVariableExample {
public static void main(String[] args){
var names = List.of("Apple", "Banana", "Cherry", "Mango");
for (var name : names) {
System.out.println(name);
}
System.out.println(" ");
}
}
//Output :
Apple
Banana
Cherry
Mango
Java 11(September 2018) : Local Variable Type in Lambda Expressions
What is the Local Variable Type in Lambda?
In Java 11, support for Local Variable Type Inference (var) was extended to lambda expressions, allowing us to use var to define lambda expression parameters.This feature improves code consistency and allows us to add annotations to lambda parameters.
Example :
import java.util.List;
import java.util.List;
import java.util.stream.Collectors;
@interface NonNull {}
public class LocalVariableInLambdaExample {
public static void main(String[] args) {
List fruitList = Arrays.asList("Apple", "Banana", "Cherry", "Mango");
String fruits = fruitList.stream()
.map((@NonNull var fruit) -> fruit.toUpperCase())
.collect(Collectors.joining(", "));
System.out.println(fruits);
}
}
//Output :
APPLE, BANANA, CHERRY, MANGO
Java11 Var In Lambdas: Rules To Follow
- If one of the lambda parameters uses var, all of the others must do the same. Using var with explicitly typed or untyped parameters is not permitted.
- The var keyword is especially useful for adding annotations to lambda parameters.
- We cannot use var for one argument and let the compiler decide the type for the others.
- The type of the lambda parameters must be based on the context. We cannot use var when the compiler cannot determine the type.
Java 12(March 2019) : String Indent and Transform
Java 12 introduced two notable methods for the String class: indent and transform.
String::indent
The indent method changes the indentation of each line in a string. It takes an integer argument that specifies the amount of spaces to add or remove.
- Positive value: Inserts spaces at the start of each line.
- Negative value: Removes spaces from the beginning of each line, up to the number indicated.
Example :
public class StringIndentExample {
public static void main(String[] args) {
String text = "Hello\nWorld";
text = text.indent(4); // Adds 4 spaces at the beginning of each line
System.out.println(text);
}
}
//Output :
Hello
World
String::transform
The transform method applies a function to a string and returns the result. This is useful for writing operations in a more readable format.
Example :
public class StringTransformExample {
public static void main(String[] args) {
String text = "Hello World";
String result = text.transform(value -> new
StringBuilder(value).reverse().toString());
System.out.println(result);
}
}
//Output :
dlroW olleH
Java 13(September 2019) : TextBlocks
Java 13 introduces Text Blocks, a feature that makes it easier to create multi-line string literals.
What are Text Blocks?
Text Blocks allows us to create multi-line strings in a more understandable and manageable format. They are enclosed in triple double quotes (“””) and can span multiple lines without using escape sequences.
Key Features :
- Simplified Syntax : No need for escape characters for newlines or quotes.
- Improved Readability : Easier to read and write multi-line strings, such as HTML, JSON, or SQL.
- Consistent Formatting : Maintains the indentation and formatting of the text within the block.
Example :
public class TextBlocksExample {
String html = """
Hello, World!
Welcome to the java world.
""";
System.out.println(html);
}
}
//Output :
Hello, World!
Welcome to the java world.
Benefits :
- Removes the need for concatenation for multi-line strings.
- Improves readability and maintainability.
- Suitable for embedding SQL queries, JSON, XML, or HTML.
Java 14(March 2020) : Yield Keyword
Java 14 added the yield keyword, which improves the functionality of switch statements.
What is the yield Keyword?
The yield keyword allows a switch expression to return a value. This makes switch expressions more powerful and expressive, enabling them to produce a result directly.
Key Features :
- Return Values from Switch Expressions : Instead of simply executing code blocks, switch expressions can now return values using yield.
- Improved Readability : Simplifies syntax, making code more brief and readable.
- Exhaustiveness Check : Ensures that every possible situation is covered, and returns a compile-time error if any are missing.
Example :
public class TextBlocksExample {
public static void main(String[] args) {
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
Day day = Day.WEDNESDAY;
String typeOfDay = switch (day) {
case SATURDAY, SUNDAY -> "Weekend";
default -> {
yield "Weekday";
}
};
System.out.println(typeOfDay);
}
}
//Output :
Weekday
Java 15(September 2020) : Garbage Collector Updates
Java 15 made major changes to garbage collection, focusing on improving performance and reducing pause times.
Z Garbage Collector (ZGC)
- Scalable and Low-Latency : ZGC is designed to manage large heaps with minimal pause times, making it suitable for applications that require both high performance and low latency.
- Concurrent Collection : Most of the garbage collection work is done concurrently with the application, reducing the impact on performance.
Shenandoah Garbage Collector
- Low-Pause-Time : Shenandoah aims to minimize pause times by performing garbage collection concurrently with the application.
- Responsive Performance : This is ideal for applications that require consistent and responsive performance, even when memory utilization is high.
Java 16(March 2021) : Pattern Matching for instanceof
Java 16 introduces Pattern Matching for instanceof, a feature that makes type-checking and casting easier.
What is Pattern Matching for instanceof?
Pattern Matching for instanceof allows you to combine the type check and cast into a single operation. This makes the code more concise and readable.
Key Features :
- Simplified Syntax : Automatically casts the object to the specified type if the instanceof check is true.
- Improved Readability : Removes the requirement for explicit casting, resulting in less boilerplate code.
- Scoped Variables : The variable defined in the pattern is only in scope within the block when the instanceof check is true.
Example :
public class InstanceOfExample {
public static void main(String[] args) {
Object name = "Hello World!";
if (name instanceof String str) {
System.out.println(str.toLowerCase());
}
}
//Output :
hello world!
Java 17(September 2021) : Sealed Class
Java 17 introduced Sealed Classes, a feature that gives developers more flexibility over class hierarchies and helps enforce stronger encapsulation.
What are Sealed Classes?
Sealed classes limit which other classes or interfaces can extend or implement them. This enables developers to specify a predefined set of subclasses, preventing unauthorized extensions and ensuring a more controlled and predictable class hierarchy.
Key Features :
- Controlled Extensibility : Only defined classes may extend a sealed class.
- Enhanced Security : Prevents unintended or unauthorized subclassing.
- Improved Maintainability : The class hierarchy becomes easier to understand and maintain.
Example:
sealed class Bank permits Payment, Credit, Debit
{
public void printName()
{
System.out.println("This is a Bank class.");
}
}
non-sealed class Payment extends Bank
{
public void printName()
{
System.out.println("This is a Payment class.");
}
}
non-sealed class Credit extends Bank
{
public void printName()
{
System.out.println("This is a credit class.");
}
}
final class Debit extends Bank
{
public void printName()
{
System.out.println("This is a debit class.");
}
}
public class SealedClassExample {
public static void main(String[] args) {
Bank h1 = new Debit();
Bank h2 = new Credit();
Bank h3 = new Payment();
h1.printName();
h2.printName();
h3.printName();
}
}
//Output :
This is a Payment class.
This is a credit class.
This is a debit class.
Java 21(September 2023) :
Java 21, released in September 2023, introduced several significant features and enhancements. Here are some of the key updates:
1. Virtual Threads
Virtual threads are lightweight and maintained by the Java runtime, allowing for efficient handling of a large number of threads with minimum overhead. This feature simplifies concurrency and improves performance.
//Syntax :
Thread virtualThread = Thread.startVirtualThread(() -> {
});
//Example :
public class VirtualThreadExample {
public static void main(String[] args) {
Thread virtualThread = Thread.startVirtualThread(() -> {
System.out.println("Hello world from a virtual thread.");
});
try {
virtualThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//Output :
Hello world from a virtual thread.
2. String Templates
String Templates allow us to insert expressions directly into string literals, making our code clearer and more expressive. This feature improves while reducing the complexity of string manipulation.
Example :
public class StringTemplateExample {
public static void main(String[] args){
String firstName = "Raj";
String lastName = "Patel";
String fullName = STR."${firstName} ${lastName}";
System.out.println(fullName);
}
}
//Output :
Raj Patel
3. Record Patterns
Record Patterns allow us to match a record’s type and extract its components in one step. This makes it easier to interact with data encapsulated in records by accessing direct access to fields.
Example :
record Point(int x, int y) {}
public class RecordPatternExample {
public static void main(String[] args) {
Object obj = new Point(3, 4);
if (obj instanceof Point(int x, int y)) {
System.out.println("Point coordinates: " + x + ", " + y);
}
}
}
//Output :
Point coordinates: 3, 4
4. Pattern Matching for Switch
Java 21 introduced Pattern Matching for Switch, which improved the flexibility and expressiveness of switch statements and expressions. This feature allows you to match specific patterns within switch cases, which makes your code more concise and clear.
Example :
public class PatternMatchingExample{
public static String getType(Object obj) {
return switch (obj) {
case String s -> "String: " + s;
case Integer i -> "Integer: " + i;
case Double d -> "Double: " + d;
default -> "Unknown type";
};
}
public static void main(String[] args) {
System.out.println(getType("Hello"));
System.out.println(getType(42));
System.out.println(getType(3.14));
}
}
//Output :
String: Hello
Integer: 42
Double: 3.14
Conclusion:
Java has changed significantly between versions 9 and 21, with features that improve modularity, readability, and performance. Key updates include the Java Module System in Java 9, Local Variable Type Inference using var in Java 10, and Text Blocks in Java 13. Java 14 introduced the yield keyword for switch statements, and Java 15 improved garbage collection. Java 16 introduced Pattern Matching for instanceof, and Java 17 added Sealed Classes to provide developers more control over class hierarchies. The highlights of Java 21 are Virtual Threads for fast concurrency, String Templates for improved string processing, Record Patterns for concise data manipulation, and Pattern Matching for Switch for more expressive control flow. These properties combine to make Java a strong and versatile language for modern software development.