Key New Features in JDK 14
JDK 14 introduced 16 JDK Enhancement Proposals (JEPs). Below are the most notable and practical new features:
- 343: Packaging Tool (Incubator)
- 345: G1 Garbage Collector NUMA Memory Allocation Optimization
- 349: JFR Event Streaming
- 352: Non-volatile Byte Buffer Mappings
- 358: Helpful NullPointerExceptions
- 359: Records (Preview Feature)
- 361: Switch Expressions (Standardized)
- 362: Deprecate Solaris and SPARC Ports
- 363: Remove Concurrent Mark Sweep (CMS) Garbage Collector
- 364: ZGC on macOS
- 365: ZGC on Windows
- 366: Deprecate ParallelScavenge + SerialOld GC Combination
- 367: Remove Pack200 Tools and API
- 368: Text Blocks (Second Preview Feature)
- 370: External Memory API (Incubator)
Pattern Matching for instanceof
This JEP adds pattern matching capabilities to the instanceof operator, making type checking and conversion more concise, readable, and safer.
Design Goals
When writing code that handles different object types, you typically need to check an object's type then cast it to the target type. The traditional approach has several pain points.
Traditional Type Check and Cast
public void legacyTypeCheck(Object inputObj) {
if (inputObj instanceof String) {
String strValue = (String) inputObj;
System.out.println(strValue.length());
}
}
This approach requires repeating the type name multiple times, has redundant syntax, and separates the type check and cast steps.
The new pattern matching syntax simplifies this by declaring the target variable directly after the type in the instanceof check:
public void patternMatchingInstanceof(Object inputObj) {
if (inputObj instanceof String strValue) {
System.out.println(strValue.length());
} else {
// strValue is not accessible here
}
}
If the check passes, inputObj is automatically cast to String and assigned to strValue, with the variable scoped exclusively to the if block.
Simplified equals() Method
You can also use this feature to simplify the implementation of the equals() method:
import java.util.Objects;
public class Student {
private final String studentName;
public Student(String name) {
this.studentName = name;
}
@Override
public boolean equals(Object obj) {
return (obj instanceof Student otherStudent) && Objects.equals(this.studentName, otherStudent.studentName);
}
@Override
public int hashCode() {
return Objects.hash(studentName);
}
}
Switch Expressions (JEP 361)
This feature standardizes the enhanced switch syntax first introduced in JDK 12 and refined in JDK 14, allowing switch to be used both as a statement and an expression.
Evolution of Java Switch
- Java 5: Added support for enums
- Java 7: Added support for String literals
- Java 11: Added buillt-in fallthrough warnings for missing
breakstatements - JDK 12+: Major syntax enhancements to support arrow notation and expression usage
Traditional Switch Syntax
Before JDK 12, switch required expilcit break statements to prevent fallthrough:
public class LegacySwitchDemo {
public static void main(String[] args) {
char grade = 'C';
switch (grade) {
case 'A':
System.out.println("Excellent");
break;
case 'B':
System.out.println("Good");
break;
case 'C':
System.out.println("Fair");
break;
case 'D':
System.out.println("Pass");
break;
case 'E':
System.out.println("Fail");
break;
default:
System.out.println("Invalid grade");
}
}
}
Arrow Notation Without break
JDK 12 replaced the colon syntax with arrow notation, eliminating the need for break statements:
public class ArrowSwitchDemo {
public static void main(String[] args) {
char grade = 'C';
switch (grade) {
case 'A' -> System.out.println("Excellent");
case 'B' -> System.out.println("Good");
case 'C' -> System.out.println("Fair");
case 'D' -> System.out.println("Pass");
case 'E' -> System.out.println("Fail");
default -> System.out.println("Invalid grade");
}
}
}
Note that new and old switch syntax cannot be mixed in the same switch block.
Switch as an Expression
Switch can now be used as an expression that returns a value, assigned directly to a variable:
public class SwitchExpressionDemo {
public static void main(String[] args) {
char grade = 'C';
String gradeLabel = switch (grade) {
case 'A' -> "Excellent";
case 'B' -> "Good";
case 'C' -> "Fair";
case 'D' -> "Pass";
case 'E' -> "Fail";
default -> "Invalid grade";
};
System.out.println(gradeLabel);
}
}
Multi-Case Matching
You can group multiple cases together using comma separation for shared logic:
public class MultiCaseSwitchDemo {
public static void main(String[] args) {
char grade = 'B';
String performance = switch (grade) {
case 'A', 'B' -> "Top tier";
case 'C' -> "Middle tier";
case 'D', 'E' -> "Bottom tier";
default -> "Invalid grade";
};
System.out.println(performance);
}
}
Yielding Values from Switch Blocks
If your switch case uses a code block instead of a single expression, use the yield keyword to return a value:
public void getGradeResult(int rawScore) {
char grade = rawScore > 90 ? 'A' : rawScore > 80 ? 'B' : rawScore >70 ? 'C' : rawScore >60 ? 'D' : 'E';
String result = switch (grade) {
case 'A', 'B' -> "Top tier";
case 'C' -> "Middle tier";
case 'D', 'E' -> "Bottom tier";
default -> {
if (rawScore < 0 || rawScore > 100) {
yield "Score out of valid range (0-100)";
}
yield "Unknown grade";
}
};
System.out.println(result);
}
Switch expressions must cover all possible input cases, or include a default block. For enum types, the compiler will automatically add a default block if all enum values are covered.
Text Blocks (JEP 368)
This is the second preview of text blocks, first introduced in JDK 13, designed to simplify writing multi-line string literals by improving readability and reducing boilerplate.
Core Syntax
Text blocks use three double quotes (""") as delimiters. The opening delimiter must be followed by a line break, and the closing delimiter defines the end of the string.
String multiLineText = """
Line 1
Line 2
Line 3
""";
This is equivalent to the traditional concatenated string:
"Line 1\nLine 2\nLine 3\n"
Automatic Indentation Handling
The compiler automatically trims consistent leading whitespace from all lines in the text block, based on the position of the closing delimiter. For example:
String xmlConfig = """
<root>
<user>
<name>Alice</name>
<id>1001</id>
</user>
</root>
""";
The leading 4 spaces of each line will be removed, prdoucing a properly indented XML string without manual whitespace management. If the closing delimiter is placed at the end of the last content line, no leading whitespace is trimmed:
String compactXml = """
<root><user><name>Alice</name></user></root>
""";
Special Escape Sequences
Text blocks support two additional escape sequences beyond standard string literals:
\: Prevents the insertion of a line break at the current position\s: Represents a single space, to avoid trailing whitespace being automatically trimmed
// Single line string with no line breaks
String singleLineText = """
Hello \
World \
Java 14
""";
// Fixed-width lines with trailing spaces preserved
String fixedWidthNames = """
Alice \s
Bob \s
Charlie \s
""";
Common Use Cases
Text blocks excel at writing multi-line content like HTML, SQL queries, and script code:
// Traditional HTML string
String traditionalHtml = "<html>\n" +
" <body>\n" +
" <p>Hello, World</p>\n" +
" </body>\n" +
"</html>";
// Text block HTML
String modernHtml = """
<html>
<body>
<p>Hello, World</p>
</body>
</html>
""";
// Traditional SQL query
String traditionalSql = "SELECT emp_id, last_name FROM employee_tb\n" +
"WHERE city = 'INDIANAPOLIS'\n" +
"ORDER BY emp_id, last_name;";
// Text block SQL
String modernSql = """
SELECT emp_id, last_name FROM employee_tb
WHERE city = 'INDIANAPOLIS'
ORDER BY emp_id, last_name;
""";
// Traditional JavaScript script
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
ScriptEngine jsEngine = new ScriptEngineManager().getEngineByName("js");
Object scriptResult = jsEngine.eval("function hello() {\n" +
" print('\"Hello, World\"');\n" +
"}\n" +
"\n" +
"hello();");
// Text block JavaScript script
ScriptEngine modernJsEngine = new ScriptEngineManager().getEngineByName("js");
Object modernScriptResult = modernJsEngine.eval("""
function hello() {
print('"Hello, World"');
}
hello();
""");