New Features Introduced in Java 9-17 Versions

Vahram PapazyanVahram Papazyan
10 min read

Sun Microsystems introduced Java on May 21, 1991, and the language has faced many transformations since then. Until September 2017, it had only eight major releases. Starting from the Java 9 version, Oracle adopted a six-month update schedule to add new features.

In this article, we'll discuss new language features and cover the Java versions from 9 to 17, as of 2023 it is the current LTS version.

Additionally, I'll add references to the corresponding JEPs (Java Enhancement Proposal) and topics at JetBrains Academy on Hyperskill. If you haven’t heard of it yet, it’s a project-based learning platform with plenty of software development tracks to help everyone acquire market-ready skills with online education. It gives you not only theoretical knowledge, but enables you to put those skills into practice.

Modules (Java Platform Module System). Java 9

JEP 261. If you’re a Java developer, you know what the purpose of packages is. Java modules, introduced in Java 9 are similar to packages but way more powerful. They let you break your application into distinct components that include a group of related code and make it more manageable. Like packages, they let you make your code reusable but in the case of modules, you can run each of them as a standalone application.

A module can be defined in the module.info file. In the following example, we declare the module1 module that exports its package1 package and the MyClass class from the package2.util package making them accessible from the outside of the module. On the other hand, it imports the module2 and module3 modules.

module module1 {
   exports package1;
   exports package2.util.MyClass;


   requires module2;
   requires module3;
}

A good example of Java modularity is Java itself. If you explore its code, you’ll see that it’s divided into many modules:

The abovementioned image illustrates four modules: java.base, java.compiler, java.datastansfer, and java.desktop. Each of them has its module-info file that describes it.

If you want to read more about this topic and solve some exercises you can take a look at the Java Modules topic by JetBrains Academy on Hyperskill.

Local-Variable Type Inference. Java 10

JEP 286. If you’re familiar with Javascript, ask yourself whether Java becomes a dynamically typed language with this feature. The answer is NO! Java is still a statically typed language. Here, varis a reserved type name aimed at reducing the boilerplate code, making it more concise, and improving the readability of the code:

public static void main(String[] args) {
   Map<String, List<String>> map1= new HashMap<>();
   ByteArrayOutputStream stream1 = new ByteArrayOutputStream();

   /* VS */


   var map2 = new HashMap<>();
   var stream2 = new ByteArrayOutputStream();
}

In the example above, the compiler will infer the type of object based on its usage context, but you won’t be able to assign other type objects to such variables:

var map = new HashMap<>();
map = new ByteArrayOutputStream(); // Error

Note that var can’t be used everywhere. You can use it in cases such as local variables with initializers, lambda formal parameters (Java 11), indexes in the enhanced for-loop, and in a traditional for-loop. On the other hand, you can’t use them in cases such as method and constructor formal parameters, method return types, fields, catch formal parameters, or any other kind of variable declaration.

If you’re just starting to learn Java, you can find it useful to learn about Java data types, including the type reference, in the Types and variables topic by JetBrains Academy on Hyperskill.

HTTP Client API. Java 11

JEP 321. The release of Java 11 standardized the HTTP Client API that was incubated in Java 9. This API allows us to make HTTP requests (including asynchronous requests), using three main classes from the java.net.http package:

  • HttpClient

  • HttpRequest

  • HttpResponse

In this example, I perform a GET request, but we can also perform POST, PUT, and DELETE requests.

public static void main(String[] args) {
   HttpClient httpClient = HttpClient.newHttpClient();


   HttpRequest request = HttpRequest.newBuilder(URI.create("http://info.cern.ch/hypertext/WWW/TheProject.html"))
           .version(HttpClient.Version.HTTP_2)
           .timeout(Duration.ofSeconds(5))
           .GET()
           .build();


   try {
       HttpResponse<String> response = httpClient.send(
               request, HttpResponse.BodyHandlers.ofString());


       System.out.println(response.statusCode());
       System.out.println(response.body());
   } catch (Exception e) {
       System.out.println("We cannot access the site. Please, try later.");
   }
}

If you look at the code, it consists of three main logical parts:

  1. Creating the HttpClient object.

  2. Creating the HttpRequest object by specifying its HTTP version, the request timeout, and the request type.

  3. Sending the request with HttpClient and assigning its return value as a String to the HttpResponse object to print the response status code and body.

You can read more about this feature in the Java 11 HTTP client topic by JetBrains Academy on Hyperskill where we provide more info on this topic and tasks to practice. Additionally, if this topic is interesting to you, I suggest reading the Setting up a server with HttpServer topic where you’ll learn how to create a simple HTTP server without using any external dependency.

Switch Expressions. Java 12, Preview

JEP 361. Before the Java 12 version each switch case was a statement, meaning you couldn’t return any value. With this update as a preview feature, we can do it. Look at the example with the old switch statement syntax:

public static void main(String[] args) {
   String str = "";
   int num = new Scanner(System.in).nextInt();


   switch (num) {
       case 0:
           str = "ZERO";
           break;
       case 1:
       case 3:
       case 5:
           str = "ODD";
           break;
       case 2:
       case 4:
           str = "EVEN";
           break;
       default:
           throw new IllegalStateException("The input should be in a [0,5] range");
   }


   System.out.println("The number is: " + str);
}

Now, look at the code with the new syntax:

public static void main(String[] args) {
   int num = new Scanner(System.in).nextInt();


   String str = switch (num) {
       case 0 -> "ZERO";
       case 1, 3, 5 -> "ODD";
       case 2, 4 -> "EVEN";
       default -> throw new IllegalStateException("The input should be in a [0,5] range");
   };


   System.out.println("The number is: " + str);
}

Officially, the switch expression became part of Java in version 14. You can visit JetBrains Academy on Hyperskill and complete the topic about this feature if you’re interested to learn more about it.

Text Blocks. Java 13, Preview

JEP 378. Java 13 introduced a text blocks feature that was officially added to the language in Java 15 version. It gives us more convenience when working with strings that have multiple lines. Let’s look at how we could declare such Strings before this feature. For this purpose, I’ll use one of my favorite poems by Rudyard Kipling:

public static void main(String[] args) {
   String poetry = "If you can talk with crowds and keep your virtue,\n" +
           "    Or walk with Kings—nor lose the common touch,\n" +
           "If neither foes nor loving friends can hurt you,\n" +
           "    If all men count with you, but none too much;\n" +
           "If you can fill the unforgiving minute\n" +
           "    With sixty seconds’ worth of distance run,\n" +
           "Yours is the Earth and everything that’s in it,\n" +
           "    And—which is more—you’ll be a Man, my son!";


   System.out.println(poetry);
}

Let’s see how we can apply the text block:

public static void main(String[] args) {
   String poem = """
                      If you can talk with crowds and keep your virtue,
                          Or walk with Kings—nor lose the common touch,
                      If neither foes nor loving friends can hurt you,
                          If all men count with you, but none too much;
                      If you can fill the unforgiving minute
                          With sixty seconds’ worth of distance run,
                      Yours is the Earth and everything that’s in it,
                          And—which is more—you’ll be a Man, my son!""";


   System.out.println(poem);
}

Text blocks are very convenient when you want to save an SQL query or an HTML code block in a string. All you need to do is to use the triple double quotes to define a text block but remember, you need to have a line break after opening quotes.

Hyperskill has a topic about this feature. Feel free to check it.

Records. Java 14, Preview

JEP 359. Records are designed as a new approach to a type declaration that was introduced in Java 14 as a preview feature and finally added as an official feature in Java 16 version. Their purpose is to make data transfer more convenient by reducing the amount of boilerplate code and making it more concise. Using records we can declare classes that contain read-only state. Here is what it looks like a record with two fields:

record Person(String name, int birthDate) {

}

Note that you can’t use them as a base class or extend a record from another class or record because all records implicitly extend the java.lang.Record class, and we can’t have an abstract record. Additionally, all record fields are final. They can’t be modified once they’re set.

If you want to learn more about Java records, read the related topic by JetBrains Academy on Hyperskill.

Pattern Matching for instanceof. Java 14, Preview

JEP 305. Java has different approaches to performing type-checking operations. One of those is the instanceof operator. We've received enhancements in Java 14 as a preview feature that allows instanceof to operate with a type pattern:

public static void main(String[] args) {
   Object obj = "Hello, World.";


   // Before
   if (obj instanceof String) {
       String str = (String) obj;
       // Some code using str
   }


   // After
   if (obj instanceof String str) {
       // Some code using str
   }
}

This feature was officially added to Java in version 16. By the way, the code sample isn’t all you can do using this update. You can read more on this topic by JetBrains Academy on Hyperskill.

Sealed Classes and Interfaces. Java 15, Preview

JEP 360. The sealed feature allows us to control which class/interface can extend/implement another class/interface. This enhancement was first introduced in Java 15 and finally added to the language in the Java 17 version. Using this feature is very simple. You just add the sealed keyword when declaring the class or interface and define which class/interface can extend/implement it and specify permitted classes/interfaces with the permits keyword:

public sealed class Shape
        permits Triangle, Square { }

In the abovementioned example, only the Triangle and Square classes can extend the Shape class. It’s worth mentioning that you must mark permitted classes or interfaces with only final, sealed, or non-sealed modifiers or their combinations.

We cover this feature in more detail in the Sealed classes and interfaces topic by JetBrains Academy on Hyperskill. Feel free to check it.

Pattern Matching for Switch. Java 17, Preview

JEP 406. The pattern matching for switch is based on the pattern matching approach instanceof operator mentioned earlier and has a similar purpose to make the code more concise. Imagine, you have such a code:

public static void main(String[] args) {
   Object obj = "Java";


   if (obj instanceof Integer i) {
       System.out.printf("int: %d", i);
   } else if (obj instanceof String s) {
       System.out.printf("String: %s", s);
   } else {
       System.out.printf("No Match!");
   }
}

This can be modified to do the same operation using switch expressions:

public static void main(String[] args) {
   Object obj = "Java";


   switch (obj) {
       case Integer i -> System.out.printf("int: %d", i);
       case String s  -> System.out.printf("String: %s", s);
       default        -> System.out.println("No Match!");
   }
}

Unfortunately, the switch pattern matching feature is still in a preview stage as of Java 20, but let’s hope it will be added to the language in the upcoming Java versions.

As in the case of the previous features, Hyperskill has a topic about this feature; learn what else you can do with the pattern matching for switch.

Conclusion

We’ve explored language features that were added from Java 9 to Java 17. We've covered each of the features and provided links to the corresponding topics on Hyperskill if you want to learn more and practice.

This list of features isn't exhaustive, of course. There are also many other enhancements; this article focuses on core language features only.

If you’re an experienced Java developer and are familiar with basic Java syntax but would like to expand your knowledge, I recommend exploring JetBrains Academy’s Advanced Java track.

2
Subscribe to my newsletter

Read articles from Vahram Papazyan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vahram Papazyan
Vahram Papazyan

Junior Java Developer | IT Author 🚀 Hyperskill Team: https://hyperskill.hashnode.dev