Java Present , Past & Future -Detailed Explanation

Lalit Chaturvedi
8 min readJun 26, 2023

--

TBC from my last article https://medium.com/@lalitchaturvedi323/java-present-past-future-893f8bc61ae3

Detailed Explanation of Important Characteristics

Java Modularity

Java Platform Module System (JPMS) is the highlight of the Java 9 release. It is known as the Jigshaw Project. Modules are new structures, just like the packages we already have. An application developed with the new modular programming can be viewed as a collection of interacting modules with well-defined boundaries and dependencies between them.

JPMS supports writing modular applications and modularizing JDK source code. JDK 9 comes with about 92 modules (which can be changed in the GA version). The Java 9 Module System has a java.base module. It is called the basic module. It is an independent module and does not depend on any other modules. All other modules rely on java.base by default.

In Java modular programming:

  • A module is usually a jar file with a file module-info.class under the root directory
  • To use the module, include the jar file in the modulepath instead of the classpath. The modular jar file added to the classpath is a normal jar file, and the module-info.class file will be ignored.

A typical module-info.java class is shown below:

module helloworld {     
exports com.alibaba.eight;
}
module test {
requires helloworld;
}

Summary: The purpose of modularization is to enable JDK components to be split, reused, replaced, and rewritten. For example, if you are unsatisfied with Java’s GUI, you can implement a GUI yourself. If you are unsatisfied with Java syntax, you can replace javac with other languages and compilers for other languages, such as kotlin and kotlinc. Without modularization, it is almost difficult to implement. Each time you modify a module, it is unfeasible for you to recompile the entire JDK and release the entire SDK. Modularization can help realize effective customization and deployment.

Local Variable Type Inference

In versions prior to Java 10, when we wanted to define local variables, we need to provide an explicit type on the left side of the assignment and an implementation type on the right side of the assignment.

MyObject value = new MyObject();

In Java 10, local variable type inference is provided, and variables can be declared by var.

var value = new MyObject();

Local variable type inference will introduce the var keyword without explicitly specifying the type of the variable.

The local variable type inference is the syntactic sugar that Java 10 provides to developers.

Although we use var to define in the code, the virtual machine does not know this var. In the process of compiling java files into class files, the real type of variables will be used to replace var.

HTTP Client API — HttpClient for Responsive Streaming

Java has been using HttpURLConnection for HTTP communication for a long time. Over time, the requirements became complex, and the requirements of the application became demanding. Prior to Java 11, developers had to resort to feature-rich libraries, such as Apache HttpComponents or OkHttp.

We see that the Java 9 release includes a HttpClient implementation as an experimental feature. It has evolved to the final feature of Java 11. Now, Java applications can communicate over HTTP without any external dependencies.

As a new HTTP connector officially launched in JDK11, the supported functions are new. The main features are listed below:

  • Full Support for HTTP 2.0 or HTTP 1.1
  • Support for HTTPS and TLS
  • Simple blocking usage methods
  • Support asynchronous sending and asynchronous time notifications
  • Support for WebSocket
  • Support for Responsive Streaming

HTTP2.0 is also supported by other clients, while HttpClient uses CompletableFuture as asynchronous return data. WebSocket support is an advantage of HttpClient. The support of responsive streams is a major advantage of HttpClient.

The NIO model, functional programming, CompletableFuture asynchronous callbacks, and responsive streams in HttpClient equip HttpClient with strong concurrent processing capabilities, so its performance is high and its memory footprint is less.

Syntactic Sugar

Stream API Improvements

Collectors.teeing()

The teeling collector has been exposed as a static method Collectors::teeing. The collector forwards its input to the other two collectors and combines their results with the function.

Example:

List<Student> list = Arrays.asList(
new Student("A", 55),
new Student("B", 60),
new Student("C", 90));
// Average Score and Total Score
String result = list.stream().collect(Collectors.teeing(
Collectors.averagingInt(Student::getScore),
Collectors.summingInt(Student::getScore),
(s1, s2) -> s1 + ":" + s2));
// The Lowest Score and the Highest Score
String result2 = list.stream().collect(Collectors.teeing(
Collectors.minBy(Comparator.comparing(Student::getScore)),
Collectors.maxBy(Comparator.comparing(Student::getScore)),
(s1, s2) -> s1.orElseThrow() + ":" + s2.orElseThrow()
));
System.out.println(result);
System.out.println(result2);

Add Stream.toList Method (JDK16)

List<String> list = Arrays.asList("1", "2", "3");
// Wrote this before.
List<Integer> oneList = list.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
// Now write like this.
List<Integer> twoList = list.stream()
.map(Integer::parseInt)
.toList();

Switch Expression Improvements

Support for Arrow Expressions (JDK12 Preview JDK14 Standard)

This change extends the switch statement so it can be used as a statement or expression. Instead of defining a statement to break each case block, we can simply use the arrow syntax:

boolean isWeekend = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> false;
case SATURDAY, SUNDAY -> true;
default -> throw new IllegalStateException("Illegal day entry :: " + day);
};
int size = 3;
String cn = switch (size) {
case 1 -> "One";
case 2 -> "Two";
case 3, 4 -> "Three";
default -> "Unknown";
};
System.out.println(cn);
// To use this preview feature, we must explicitly instruct the JVM with the-enable-preview flag during application startup.

yield Keyword (JDK13)

With yield, we can efficiently return values from switch expressions and implement the strategy pattern easily.

public class SwitchTest {
public static void main(String[] args) {
var me = 4;
var operation = "Square";
var result = switch (operation) {
case "Double" -> {
yield me * 2;
}
case "Square" -> {
yield me * me;
}
default -> me;
};
System.out.println(result);
}
}

String

Text Block Improvements (jdk13)

Earlier, to embed JSON in our code, we declared it as a string literal.

String json  = "{\r\n" + "\"name\" : \"lingli\",\r\n" + "\"website\" : \"https://www.alibaba.com/\"\r\n" + "}";

Now, let’s write the same JSON.

String json = """ 
{
"name" : "Baeldung",
"website" : "https://www.alibaba.com/"
}
""";

There is no need to escape double quotes or add carriage returns. After using text blocks, it is easier to write, read, and maintain embedded JSON.

More APIs

  • isBlank(): Return true if the string is empty or if the string only contains spaces (including tabs). Note: isEmpty() only returns true if the length is 0.
  • lines(): Split a string into a stream of strings, each containing one line.
  • strip(): From the beginning and end
  • StripLeading()/stripTrailing() only removes spaces at the beginning and the end.
  • repeat(int times): Return a string that takes the original string and repeats the string a specified number of times.
  • readString(): It allows you to read strings directly from file paths.
  • writeString(Path path): Write a string to a file at a specified path
  • indent(int level): The Specified Amount of Indentation in the String Negative values only affect the leading whitespace.
  • transform(Function f): Apply the given lambda to a string

Pattern Matching of instanceof (JDK14 Preview, JDK16 Final Confirmation)

Before:
Object obj = "Dayang";
if (obj instanceof String) {
String t = (String) obj;
// TODO
}
Now:
Object obj = "Dayang";
if (obj instanceof String t) {
// TODO At this time, t is already of the String type.
}

Record Class (JDK16 Formal)

Traditional Java applications create a class, instantiate the class through this creating method, and access or set the value of a member variable through getter and setter methods. With the record keyword, your code becomes more concise.

/**
* record class
* You can overwrite equals() hashCode() toString() method without writing get and set.
* @author DAYANG
*/
record User(String name, Integer age) {

@Override
public String toString() {
return "User[" +
"name='" + name + '\'' +
", age=" + age +
']';
}
@Override
public boolean equals(Object obj) {
return false;
}
@Override
public int hashCode() {
return 0;
}
}

JVM

GC Changes

JDK9: Set G1 as JVM default garbage collector.

JDK10: Parallel full garbage collector G1, which improves the latency of G1 through parallel Full GC. The implementation of full GC for G1 uses the single-thread-purge-compression algorithm. JDK 10 began using the parallelize-purge-compression algorithm.

JDK11: A new generation of ZGC garbage collectors (experimental) is launched. The goal is that GC pause time will not exceed 10ms, which can handle both hundreds of megabytes and several terabytes.

JDK14: Remove the CMS garbage collector, deprecate the garbage collection algorithm combination of ParallelScavenge + SerialOld GC, and port the ZGC garbage collector to the macOS and Windows platforms.

JDK 15: ZGC (JEP 377) and Shenandoah (JEP 379) are no longer experimental features. The default GC is still G1.

JDK16: Enhance ZGC. ZGC gets 46 enhancements and 25 bug fixes with no more than 10ms of controlling stw time.

Metric Test

Throughput Comparison

In terms of throughput, there is not much difference between JDK 8 and JDK 11 in Parallel, and JDK 17 is about 15% higher than JDK 8. JDK 17 in G1 is 18% higher than JDK 8. ZGC was introduced in JDK 11, and JDK 17 improved by more than 20% compared with JDK 11.

Latency Comparison

In terms of GC latency, the improvement in JDK 17 is pronounced. We can see that efforts to reduce GC pauses have paid off, and many of the improvements are due to GC improvements.

In Parallel, JDK 17 improves by 40% compared with JDK 8 and JDK 11. In G1, JDK 11 improves by 26% compared with JDK 8, and JDK 17 improves by nearly 60% compared with JDK 8. Compared with JDK 11, JDK 17 in ZGC improves by more than 40%.

Comparison of Pause Time

We can see that the ZGC in JDK 17 is well below the target: sub-millisecond pause time. The G1’s goal is to maintain a balance between latency and throughput, far below its default target: 200 milliseconds of pause time. ZGC is designed to ensure that the pause time does not change with the size of the heap, and we can see what happens when the heap is expanded to 128GB. From a pause time perspective, G1 is better at handling larger heaps than Parallel since it can guarantee that the pause time meets a specific goal.

The figure above compares the peak native memory usage of three different collectors. Since both Parallel and ZGC are stable from this perspective, we should take a look at the raw numbers. We can see that G1 has improved in this area, mainly because all functions and enhancements have improved the efficiency of memory set management.

Summary: No matter which collector is used, the overall performance of the JDK 17 has been improved compared to the old version. In JDK 8, Parallel was the default setting, but it was changed to G1 in JDK 9. Since then, the G1 has improved faster than Parallel, but in some cases, Parallel may still be the best choice. The addition of ZGC (officially used by JDK 15) has become a third high-performance alternative.

Other

Sealed Classes and Interfaces (15 Preview 17 Formal)

Prior to Java 15, all classes that could inherit from other classes without restriction could implement a public interface unless the inherited class was declared final.

Now in Java 15, a class or interface can be declared as a sealed class or interface with the sealed modifier to restrict its inheriting classes.

/**
* Define an abstract sealed class Pet. Its implementation classes can only be Dog or Cat. Other implementation classes are not allowed.
*/
public abstract sealed class Pet
permits Dog, Cat {}
final class Dog extends Pet {
}
final class Cat extends Pet {

}
// Sealed classes and interfaces restrict other classes or interfaces from extending or implementing them.
public sealed interface Shape{
final class Planet implements Shape {}
final class Star implements Shape {}
final class Comet implements Shape {}
}
public abstract sealed class Test{
final class A extends Test {}
final class B extends Test {}
final class C extends Test {}
}

EdDSA Algorithm

Edwards-Curve Digital Signature Algorithm (EdDSA) is another additional digital signature scheme added in Java 15 with JEP 339. It provides better performance and secure signatures compared to other available signature schemes.

--

--