Review of Java Features From 11 to 17

Ömer Naci Soydemir
7 min readApr 1, 2023

From version 11 to 17, Java has introduced several major features, including local variable type inference, text blocks, records, and sealed classes. In this blog post, we will introduce each of these features.

New methods of String class (Java SE 11)

Java SE 11 introduced several new methods to the String class. Here are some of the most notable ones:

  1. isBlank(): This method returns true if the string is empty or contains only white space, and false otherwise.
  2. strip(): This method returns a copy of the string with all leading and trailing white space removed.
  3. stripLeading(): This method returns a copy of the string with all leading white space removed.
  4. stripTrailing(): This method returns a copy of the string with all trailing white space removed.
  5. repeat(int count): This method returns a new string composed of the original string repeated count times.

These new methods can be useful for performing common string operations more easily and efficiently. Here’s an example:

   
public class NewString {

public static void main(String[] args) {
// isBlank() method
System.out.println(" ".isBlank()); // true

String string = " omernaci ";
System.out.println(string.isBlank()); // false

String string1 = "";
System.out.println(string1.isBlank()); // true

// repeat(int count) method
String repeatedString = "omer".repeat(2);
System.out.println(repeatedString); // prints omeromer

// strip(), stripLeading() and stripTrailing() methods
String str = " JD ";
System.out.print("Start");
System.out.print(str.strip());
System.out.println("End");

System.out.print("Start");
System.out.print(str.stripLeading());
System.out.println("End");

System.out.print("Start");
System.out.print(str.stripTrailing());
System.out.println("End");

}

}

Switch expressions (Java SE 14, JEP-361)

Switch expressions are a new feature introduced in Java SE 14 that allow you to write more concise and expressive switch statements. In traditional switch statements, you could only use constants or enumerated values as cases, and you had to use a break statement to avoid falling through to subsequent cases. With switch expressions, you can use any data type as a case, and you can use a yield statement to specify the value that the expression should return.

public class SwitchExpressions {

public static void main(String[] args) {
var month = 4;

oldStyleSwitchExpression(month);

newStyleSwitchExpression(month);
}

private static void newStyleSwitchExpression(int month) {
String season = switch (month) {
case 12, 1, 2 -> "Winter";
case 3, 4, 5 -> "Spring";
case 6, 7, 8 -> "Summer";
case 9, 10, 11 -> "Autumn";
default -> "Invalid Month";
};
System.out.println("April is in the " + season + ".");
}

private static void oldStyleSwitchExpression(int month) {
String season;
switch (month) {
case 12:
case 1:
case 2:
season = "Winter";
break;
case 3:
case 4:
case 5:
season = "Spring";
break;
case 6:
case 7:
case 8:
season = "Summer";
break;
case 9:
case 10:
case 11:
season = "Autumn";
break;
default:
season = "Invalid Month";
}
System.out.println("April is in the " + season + ".");
}

}

In this example, we use a switch expression to assign a month number to the month variable. We use the -> operator to separate each case from its value, and we use the default keyword to specify the default value if no case matches.

Text Blocks (Java SE 15, JEP-378)

Text blocks are a new feature introduced in Java SE 15, as part of JEP-378, that allow you to write multi-line strings and blocks of text without needing to escape special characters or use concatenation.

In Java SE 15 and later versions, you can create a text block by enclosing a string of text in triple quotes ("""). Here's an example:

public class TextBlocks {

public static void main(String[] args) {

// Using a literal string
String literalString = "omernaci";

// Using a text block
String textBlockString = """
omernaci""";

System.out.println(literalString.equals(textBlockString)); // true

// ERROR
/* String name = """Omer Soydemir"""; */

// ERROR
/* String colors = """red
green
blue
"""; */

// Multiline example
String oldMultiline = "In the sad moonlight, \n" +
"she clasped him by the neck, \n" +
"and laid her face upon his breast. ";
System.out.println(oldMultiline);

String newMultiline = """
In the sad moonlight,
she clasped him by the neck,
and laid her face upon his breast.
""";
System.out.println(newMultiline);

// JSON object with text blocks
System.out.println(getResultAsJson());

// SQL blocks
System.out.println(getSQLQuery());

}

private static String getSQLQuery() {
return """
SELECT *
FROM customers
WHERE first_name = 'Joe'
ORDER BY last_name ASC;
""";
}

private static String getResultAsJson() {
return """
{
"id": 1235,
"title": "Example Product",
"description": "A product which is nothing like apple",
"price": 549
}
""";
}

}

Pattern matching for instance of (Java SE 16, JEP-394)

Pattern matching for instanceof is a feature introduced in Java 16 that allows you to use pattern matching syntax to extract a castable variable from an object and perform an action based on its type.

Here’s an example that demonstrates how to use pattern matching for instanceof:

public class InstanceOfSample {

public static void main(String[] args) {
Object obj1 = "omernaci";
Object obj2 = 42;
Object obj3 = new Object();

processObject(obj1); // Output: Input is a string: Hello
processObject(obj2); // Output: Input is an integer: 42
processObject(obj3); // Output: Input is of unknown type
}

public static void processObject(Object obj) {
if (obj instanceof String str) {
System.out.println("Input is a string: " + str);
} else if (obj instanceof Integer i) {
System.out.println("Input is an integer: " + i);
} else {
System.out.println("Input is of unknown type");
}
}

}

In this example, the processObject() method takes an Object parameter obj and uses pattern matching for instanceof to extract a variable of type String or Integer if obj is an instance of either of those types.

If obj is an instance of String, the pattern variable str is created and assigned the value of obj, and the code block within the if statement is executed. Similarly, if obj is an instance of Integer, the pattern variable i is created and assigned the value of obj, and the code block within the second if statement is executed. If obj is not an instance of either String or Integer, the else block is executed.

Using pattern matching for instanceof allows you to extract a castable variable from an object and perform an action based on its type, without having to cast the object to a specific type manually. This can make your code more concise and readable, especially when dealing with complex object hierarchies.

Records (Java SE 16, JEP-395)

Records are a new feature introduced in Java 16 that provide a concise way to declare simple classes that are primarily used to store data. A record is similar to a regular Java class, but with some key differences.

When you declare a record in Java, you get a compact class that provides default implementations of common methods like constructors, equals(), hashCode(), and toString(). These methods are generated based on the record's state description, which is defined by its record components.

Here’s an example of a simple record definition in Java:

public class RecordSample {

public static class Employee {
private Long id;
private String department;
private BigDecimal salary;

public Employee(Long id, String department, BigDecimal salary) {
this.id = id;
this.department = department;
this.salary = salary;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getDepartment() {
return department;
}

public void setDepartment(String department) {
this.department = department;
}

public BigDecimal getSalary() {
return salary;
}

public void setSalary(BigDecimal salary) {
this.salary = salary;
}
}

public record EmployeeRecord(Long id, String department, BigDecimal salary) {
}

public static void main(String[] args) {

Employee employee = new Employee(1L, "Data Science", BigDecimal.ZERO);

EmployeeRecord employeeRecord = new EmployeeRecord(2L, "IT", BigDecimal.ZERO);

Arrays.stream(EmployeeRecord.class.getRecordComponents()).forEach(System.out::println);

}

}

Sealed classes (Java SE 17, JEP-409)

The sealed keyword in Java is used to declare a class or an interface as sealed, which means that it restricts the hierarchy of inheritance for its subclasses or implementations.

When a class or an interface is declared as sealed, you can specify a finite set of permitted subclasses or implementations using the permits keyword. Only those subclasses or implementations explicitly listed after permits can extend or implement the sealed class or interface.

Here’s an example of a sealed class in Java:

public sealed class Shape permits Circle, Rectangle {
// common properties and methods of shapes
}

final class Circle extends Shape {
// properties and methods specific to circles
}

final class Rectangle extends Shape {
// properties and methods specific to rectangles
}

In this example, the Shape class is declared as sealed using the permits keyword, which means that only the Circle and Rectangle classes are allowed to extend it. Both Circle and Rectangle are declared as final, which means they cannot be further subclassed.

By using sealed classes, you can restrict the hierarchy of inheritance for your classes, ensuring that only specific subclasses can be created to represent different types of shapes. This can help prevent the creation of unexpected subclasses that could violate the design constraints of your code.

Github repo : https://github.com/omernaci/medium-samples/tree/master/features

--

--