December 13, 2024
Exploring the Key Features of Java 11
Java 11, released in September 2018, is a significant update to the Java ecosystem, especially as it is a Long-Term Support (LTS) release…

By Uma Charan Gorai
11 min read
Java 11, released in September 2018, is a significant update to the Java ecosystem, especially as it is a Long-Term Support (LTS) release, following Java 8. This version brought numerous enhancements to the language, libraries, and runtime environment, aiming to improve developer productivity, simplify coding tasks, and enhance application performance. From new language features to standardized APIs and deprecation, Java 11 continues the journey of modernizing the Java platform.
1. Local-Variable Syntax for Lambda Parameters
In Java 11, a new feature was introduced that allows local-variable syntax for lambda parameters. This feature enhances the consistency and readability of lambda expressions by allowing the use of the var keyword for declaring lambda parameters, similar to how it can be used for local variables introduced in Java 10.
Previously, in lambda expressions, you could either:
- Explicitly specify the parameter type:
(String s) -> s.length()(String s) -> s.length()- Rely on type inference:
(s) -> s.length()(s) -> s.length()With Java 11, you can now use var to declare the type of lambda parameters:
(var s) -> s.length()(var s) -> s.length()Benefits of Local-Variable Syntax for Lambda Parameters
- Uniformity: The
varkeyword provides consistency with local variable declarations, especially when using features likevarfor local variables introduced in Java 10. - Readability and Maintainability: Using
varcan make it easier to read and modify code. For example, you can annotate parameters without having to write the full type explicitly. - Annotations:
varallows annotations to be added to lambda parameters without specifying the full type. For example:
(@Nonnull var s) -> s.length()(@Nonnull var s) -> s.length()This is not possible when relying on type inference.
4**. Ease of Modification**: When working with code where annotations or type modifiers are added later, the use of var can reduce the need for rewriting lambda expressions.
Limitations
- No Mixed Syntax: If you use
varfor one parameter, you must usevarfor all parameters in the lambda expression. For example, this is invalid:
(var s, t) -> s + t // Invalid(var s, t) -> s + t // Invalid2**. Readability Concerns**: While var can improve uniformity, it might reduce clarity in some cases where the parameter type is not immediately obvious.
Example Code
Without Local-Variable Syntax (Before Java 11)
import java.util.Arrays;
import java.util.List;
public class LambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Explicit type
names.forEach((String name) -> System.out.println(name));
// Type inference
names.forEach(name -> System.out.println(name));
}
}import java.util.Arrays;
import java.util.List;
public class LambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Explicit type
names.forEach((String name) -> System.out.println(name));
// Type inference
names.forEach(name -> System.out.println(name));
}
}With Local-Variable Syntax (Java 11)
import java.util.Arrays;
import java.util.List;
public class LambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Using var for lambda parameters
names.forEach((var name) -> System.out.println(name));
// Using var with annotations
names.forEach((@Deprecated var name) -> System.out.println(name));
}
}import java.util.Arrays;
import java.util.List;
public class LambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Using var for lambda parameters
names.forEach((var name) -> System.out.println(name));
// Using var with annotations
names.forEach((@Deprecated var name) -> System.out.println(name));
}
}Explanation of the Example
- In the first case (without
var), the type ofnameis either explicitly declared (String) or inferred. - In the second case (with
var), thevarkeyword is used to indicate that the compiler should infer the type, but it allows annotations like@Deprecatedto be added conveniently.
2. String Enhancements
- isBlank(): Checks if a string is empty or contains only whitespace characters. More specific than isEmpty().
- strip(): Removes leading and trailing whitespace from a string. Unlike trim(), it uses Unicode whitespace definitions.
- stripLeading(): Removes only leading whitespace from a string.
- stripTrailing(): Removes only trailing whitespace from a string.
- lines(): Splits a string into a stream of lines, using line terminators (\n, \r\n, \r).
- repeat(int count): Repeats a string count times and returns the resulting string.
Benefits of String Enhancements
- Improved Readability: These methods make it easier to express common operations directly in code.
- Unicode Compliance: Methods like
strip()handle Unicode whitespace correctly, which is more robust thantrim(). - Stream Integration:
lines()integrates seamlessly with Java's Stream API for processing multi-line strings. - Conciseness: Simplifies tasks that previously required custom implementations, e.g., repeating a string.
- Better Intent: Methods like
isBlank()express the intent more clearly thanisEmpty()combined with whitespace checks.
Examples of String Enhancements
isBlank()
public class StringEnhancements {
public static void main(String[] args) {
String empty = "";
String blank = " ";
String text = "Hello";
System.out.println(empty.isBlank()); // true
System.out.println(blank.isBlank()); // true
System.out.println(text.isBlank()); // false
}
}public class StringEnhancements {
public static void main(String[] args) {
String empty = "";
String blank = " ";
String text = "Hello";
System.out.println(empty.isBlank()); // true
System.out.println(blank.isBlank()); // true
System.out.println(text.isBlank()); // false
}
}strip(),stripLeading(),stripTrailing()
public class StringEnhancements {
public static void main(String[] args) {
String str = " Hello World! ";
System.out.println("Original: [" + str + "]");
System.out.println("strip: [" + str.strip() + "]");
System.out.println("stripLeading: [" + str.stripLeading() + "]");
System.out.println("stripTrailing: [" + str.stripTrailing() + "]");
}
}public class StringEnhancements {
public static void main(String[] args) {
String str = " Hello World! ";
System.out.println("Original: [" + str + "]");
System.out.println("strip: [" + str.strip() + "]");
System.out.println("stripLeading: [" + str.stripLeading() + "]");
System.out.println("stripTrailing: [" + str.stripTrailing() + "]");
}
}lines()
import java.util.stream.Collectors;
public class StringEnhancements {
public static void main(String[] args) {
String multiLine = "Hello\nWorld\nJava 11";
multiLine.lines().forEach(System.out::println);
// Collect lines into a List
System.out.println(multiLine.lines().collect(Collectors.toList()));
}
}import java.util.stream.Collectors;
public class StringEnhancements {
public static void main(String[] args) {
String multiLine = "Hello\nWorld\nJava 11";
multiLine.lines().forEach(System.out::println);
// Collect lines into a List
System.out.println(multiLine.lines().collect(Collectors.toList()));
}
}repeat(int count)
public class StringEnhancements {
public static void main(String[] args) {
String str = "Hello ";
System.out.println(str.repeat(3)); // Output: Hello Hello Hello
}
}public class StringEnhancements {
public static void main(String[] args) {
String str = "Hello ";
System.out.println(str.repeat(3)); // Output: Hello Hello Hello
}
}Detailed Comparison
strip() vs. trim()
strip()uses Unicode-aware definitions for whitespace, whiletrim()only recognizes characters with values less than or equal to'\u0020'.- Example:
public class StringEnhancements {
public static void main(String[] args) {
String str = "\u2000Hello\u2000"; // \u2000 is a Unicode whitespace character
System.out.println("trim: [" + str.trim() + "]"); // Does not remove \u2000
System.out.println("strip: [" + str.strip() + "]"); // Removes \u2000
}
}public class StringEnhancements {
public static void main(String[] args) {
String str = "\u2000Hello\u2000"; // \u2000 is a Unicode whitespace character
System.out.println("trim: [" + str.trim() + "]"); // Does not remove \u2000
System.out.println("strip: [" + str.strip() + "]"); // Removes \u2000
}
}isBlank() vs. isEmpty()
isEmpty()checks if the string's length is 0.isBlank()also checks for whitespace-only strings.
Real-World Use Cases
- Processing Input Strings: Use
strip()to clean user input while respecting Unicode whitespace. Validate input withisBlank(). - Multi-line String Parsing: Use
lines()to process logs or configuration files line by line. - Dynamic Content Creation: Use
repeat()to generate repetitive patterns, e.g., padding or banners.
3. File Enhancements
In Java 11, file enhancements were introduced through methods added to the Files class in the java.nio.file package. These new methods simplify file handling, making common tasks like reading, writing, and manipulating files more concise and easier to implement.
File Enhancements in Java 11
Files.writeString(Path path, CharSequence csq, OpenOption... options):Writes aStringdirectly to a file, overwriting or appending content based on options.Files.readString(Path path, Charset cs):Reads the content of a file directly into aString.Files.writeString(Path path, CharSequence csq):A shorthand method for writing strings to a file, without needing to manage aBufferedWriterorFileWriter.Files.readString(Path path):A shorthand method for reading all the content from a file into aString, defaulting to UTF-8 encoding.
Benefits of File Enhancements
- Simplicity: Eliminates boilerplate code previously required for reading or writing files using streams or readers/writers.
- Readability: Makes the intent of file operations clearer with concise, expressive methods.
- Improved Developer Productivity: Reduces the amount of code required to perform common file I/O operations.
- Consistency: The new methods are part of the
java.nio.file.FilesAPI, aligning with modern Java practices. - Charset Support: Provides a straightforward way to specify character encoding when reading or writing files.
Examples of File Enhancements in Java 11
1. Writing to a File (Files.writeString)
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileEnhancements {
public static void main(String[] args) {
Path filePath = Path.of("example.txt");
try {
// Write string to file
Files.writeString(filePath, "Hello, Java 11 File Enhancements!");
System.out.println("File written successfully.");
} catch (IOException e) {
e.printStackTrace();
}
}
}import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileEnhancements {
public static void main(String[] args) {
Path filePath = Path.of("example.txt");
try {
// Write string to file
Files.writeString(filePath, "Hello, Java 11 File Enhancements!");
System.out.println("File written successfully.");
} catch (IOException e) {
e.printStackTrace();
}
}
}2. Reading from a File (Files.readString)
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileEnhancements {
public static void main(String[] args) {
Path filePath = Path.of("example.txt");
try {
// Read content from file
String content = Files.readString(filePath);
System.out.println("File content: " + content);
} catch (IOException e) {
e.printStackTrace();
}
}
}import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileEnhancements {
public static void main(String[] args) {
Path filePath = Path.of("example.txt");
try {
// Read content from file
String content = Files.readString(filePath);
System.out.println("File content: " + content);
} catch (IOException e) {
e.printStackTrace();
}
}
}3. Writing with Options (Append Mode)
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class FileEnhancements {
public static void main(String[] args) {
Path filePath = Path.of("example.txt");
try {
// Append content to the file
Files.writeString(filePath, "\nAppending new content!", StandardOpenOption.APPEND);
System.out.println("Content appended successfully.");
} catch (IOException e) {
e.printStackTrace();
}
}
}import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class FileEnhancements {
public static void main(String[] args) {
Path filePath = Path.of("example.txt");
try {
// Append content to the file
Files.writeString(filePath, "\nAppending new content!", StandardOpenOption.APPEND);
System.out.println("Content appended successfully.");
} catch (IOException e) {
e.printStackTrace();
}
}
}4. Handling Encoding
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileEnhancements {
public static void main(String[] args) {
Path filePath = Path.of("example-utf8.txt");
try {
// Write content with UTF-8 encoding
Files.writeString(filePath, "こんにちは, Java!", StandardCharsets.UTF_8);
// Read content with UTF-8 encoding
String content = Files.readString(filePath, StandardCharsets.UTF_8);
System.out.println("File content: " + content);
} catch (IOException e) {
e.printStackTrace();
}
}
}import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileEnhancements {
public static void main(String[] args) {
Path filePath = Path.of("example-utf8.txt");
try {
// Write content with UTF-8 encoding
Files.writeString(filePath, "こんにちは, Java!", StandardCharsets.UTF_8);
// Read content with UTF-8 encoding
String content = Files.readString(filePath, StandardCharsets.UTF_8);
System.out.println("File content: " + content);
} catch (IOException e) {
e.printStackTrace();
}
}
}Comparison with Pre-Java 11 Methods
Before Java 11: Writing a String to a File
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class FileExampleOld {
public static void main(String[] args) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("example.txt"))) {
writer.write("Hello, old way of writing to a file.");
} catch (IOException e) {
e.printStackTrace();
}
}
}import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class FileExampleOld {
public static void main(String[] args) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("example.txt"))) {
writer.write("Hello, old way of writing to a file.");
} catch (IOException e) {
e.printStackTrace();
}
}
}Java 11: Writing a String to a File
import java.nio.file.Files;
import java.nio.file.Path;
public class FileExampleNew {
public static void main(String[] args) {
try {
Files.writeString(Path.of("example.txt"), "Hello, Java 11 File Enhancements!");
} catch (IOException e) {
e.printStackTrace();
}
}
}import java.nio.file.Files;
import java.nio.file.Path;
public class FileExampleNew {
public static void main(String[] args) {
try {
Files.writeString(Path.of("example.txt"), "Hello, Java 11 File Enhancements!");
} catch (IOException e) {
e.printStackTrace();
}
}
}Real-World Use Cases
- Configuration Management: Quickly read or write configuration files.
- Logging: Append log messages to a file using
Files.writeString. - Data Export/Import: Easily read/write data files for applications.
- File-Based Templates: Create or populate template files using dynamic content.
4. Optional Enhancements
Java 11 introduced Optional Enhancements to the java.util.Optional class. These enhancements add new utility methods to simplify operations on Optional instances, making code more expressive, concise, and readable.
Optional Enhancements in Java 11
- isEmpty():
- Checks whether the
Optionalis empty. - Complementary to the existing
isPresent()method.
- Optional.ofNullable(value).orElseThrow():
- Similar to
orElseThrow(Supplier)but does not require an exception supplier. - Throws a
NoSuchElementExceptionif the value is absent.
Benefits of Optional Enhancements
- Improved Readability:
isEmpty()makes the code more intuitive and avoids the need for negatingisPresent(). - Reduced Boilerplate:
orElseThrow()without arguments eliminates the need to provide an exception supplier in common scenarios. - Enhanced Null Safety: Encourages safe handling of
nullvalues by leveragingOptionaleffectively. - Consistency and Expressiveness: These methods complement the existing API, providing more expressive and natural alternatives for common tasks.
Detailed Explanation of New Methods
1. isEmpty()
Previously, checking if an Optional was empty required negating isPresent():
if (!optional.isPresent()) {
// Handle empty case
}if (!optional.isPresent()) {
// Handle empty case
}With isEmpty(), the check becomes more intuitive:
if (optional.isEmpty()) {
// Handle empty case
}if (optional.isEmpty()) {
// Handle empty case
}2. orElseThrow()
- Simplifies the use of
Optionalwhen an exception should be thrown for an empty value.
Before Java 11:
value = optional.orElseThrow(() -> new NoSuchElementException());value = optional.orElseThrow(() -> new NoSuchElementException());In Java 11:
value = optional.orElseThrow(); // Automatically throws NoSuchElementExceptionvalue = optional.orElseThrow(); // Automatically throws NoSuchElementExceptionExamples of Optional Enhancements
Example 1: Using isEmpty()
import java.util.Optional;
public class OptionalEnhancements {
public static void main(String[] args) {
Optional<String> optional = Optional.ofNullable(null);
// Check if the Optional is empty
if (optional.isEmpty()) {
System.out.println("The Optional is empty.");
} else {
System.out.println("The Optional contains: " + optional.get());
}
}
}import java.util.Optional;
public class OptionalEnhancements {
public static void main(String[] args) {
Optional<String> optional = Optional.ofNullable(null);
// Check if the Optional is empty
if (optional.isEmpty()) {
System.out.println("The Optional is empty.");
} else {
System.out.println("The Optional contains: " + optional.get());
}
}
}Example 2: Using orElseThrow()
import java.util.Optional;
public class OptionalEnhancements {
public static void main(String[] args) {
Optional<String> optional = Optional.ofNullable(null);
// Use orElseThrow to handle empty Optional
try {
String value = optional.orElseThrow();
System.out.println("Value: " + value);
} catch (NoSuchElementException e) {
System.out.println("No value present in Optional.");
}
}
}import java.util.Optional;
public class OptionalEnhancements {
public static void main(String[] args) {
Optional<String> optional = Optional.ofNullable(null);
// Use orElseThrow to handle empty Optional
try {
String value = optional.orElseThrow();
System.out.println("Value: " + value);
} catch (NoSuchElementException e) {
System.out.println("No value present in Optional.");
}
}
}Example 3: Combining Enhancements
import java.util.Optional;
public class OptionalEnhancements {
public static void main(String[] args) {
Optional<String> optional = Optional.ofNullable("Java 11");
// Combine isEmpty and orElseThrow
if (optional.isEmpty()) {
System.out.println("The Optional is empty.");
} else {
System.out.println(optional.orElseThrow());
}
}
}import java.util.Optional;
public class OptionalEnhancements {
public static void main(String[] args) {
Optional<String> optional = Optional.ofNullable("Java 11");
// Combine isEmpty and orElseThrow
if (optional.isEmpty()) {
System.out.println("The Optional is empty.");
} else {
System.out.println(optional.orElseThrow());
}
}
}Comparison with Pre-Java 11 Code
Pre-Java 11 (Without Enhancements)
import java.util.Optional;
public class PreJava11 {
public static void main(String[] args) {
Optional<String> optional = Optional.ofNullable(null);
if (!optional.isPresent()) {
System.out.println("The Optional is empty.");
}
String value = optional.orElseThrow(() -> new NoSuchElementException());
}
}import java.util.Optional;
public class PreJava11 {
public static void main(String[] args) {
Optional<String> optional = Optional.ofNullable(null);
if (!optional.isPresent()) {
System.out.println("The Optional is empty.");
}
String value = optional.orElseThrow(() -> new NoSuchElementException());
}
}Java 11 Code with Enhancements
import java.util.Optional;
public class Java11 {
public static void main(String[] args) {
Optional<String> optional = Optional.ofNullable(null);
if (optional.isEmpty()) {
System.out.println("The Optional is empty.");
}
String value = optional.orElseThrow(); // Simplified
}
}import java.util.Optional;
public class Java11 {
public static void main(String[] args) {
Optional<String> optional = Optional.ofNullable(null);
if (optional.isEmpty()) {
System.out.println("The Optional is empty.");
}
String value = optional.orElseThrow(); // Simplified
}
}Real-World Use Cases
- Improved Null Handling in APIs:
- Use
isEmpty()to check for absent values in API responses. - Use
orElseThrow()to enforce mandatory fields.
2**. Data Validation:**
- Validate user input or configurations, throwing exceptions when required.
3**. Integration with Streams:**
- Combine
Optionalwith stream operations to handle missing elements gracefully.
5. Collection Enhancements
In Java 11, collection enhancements were introduced to make working with collections more convenient, concise, and expressive. These improvements build on the enhancements from Java 9 and Java 10, focusing on immutability and utility methods.
Collection Enhancements in Java 11
toArray(IntFunction<T[]>)(inCollectioninterface)
- Simplifies converting a
Collectionto an array of the desired type. - Introduced in Java 10 but widely adopted in Java 11 due to its utility.
- Immutable Collections with
of():
- Java 9 introduced
List.of(),Set.of(), andMap.of(). Java 11 continues to encourage their use for creating small, immutable collections.
3**. New Methods in Stream API (Java 11)**:
- Methods like
takeWhile()anddropWhile()in the Stream API enhance working with collections in a functional style.
- Optional and Collection Interoperability:
- The
Stream.ofNullable()method simplifies converting nullable values into collections or streams.
Benefits of Collection Enhancements
- Simplicity: Reduces boilerplate code for common tasks like converting collections to arrays.
- Improved Performance: Immutable collections (
of()) are more memory-efficient and safer for concurrent programming. - Better Null Handling:
Stream.ofNullable()prevents null pointer exceptions by wrapping nullable values in a safe stream. - Functional Programming Support: Enhancements to the Stream API allow for cleaner and more expressive code.
Detailed Examples of Collection Enhancements
1. Using toArray(IntFunction<T[]>)
Before Java 10, converting a List to an array required extra code:
List<String> list = List.of("A", "B", "C");
String[] array = list.toArray(new String[list.size()]);List<String> list = List.of("A", "B", "C");
String[] array = list.toArray(new String[list.size()]);In Java 10 and 11:
List<String> list = List.of("A", "B", "C");
String[] array = list.toArray(String[]::new);
System.out.println(Arrays.toString(array)); // Output: [A, B, C]List<String> list = List.of("A", "B", "C");
String[] array = list.toArray(String[]::new);
System.out.println(Arrays.toString(array)); // Output: [A, B, C]2. Creating Immutable Collections
Immutable List:
List<String> list = List.of("Java", "Python", "C++");
System.out.println(list); // Output: [Java, Python, C++]List<String> list = List.of("Java", "Python", "C++");
System.out.println(list); // Output: [Java, Python, C++]Immutable Set:
Set<String> set = Set.of("Apple", "Banana", "Cherry");
System.out.println(set); // Output: [Apple, Banana, Cherry]Set<String> set = Set.of("Apple", "Banana", "Cherry");
System.out.println(set); // Output: [Apple, Banana, Cherry]Immutable Map:
Map<Integer, String> map = Map.of(1, "One", 2, "Two", 3, "Three");
System.out.println(map); // Output: {1=One, 2=Two, 3=Three}Map<Integer, String> map = Map.of(1, "One", 2, "Two", 3, "Three");
System.out.println(map); // Output: {1=One, 2=Two, 3=Three}3. Stream API Enhancements
- Using
takeWhile()anddropWhile()with Collections:
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
// Take elements while condition is true
List<Integer> taken = numbers.stream()
.takeWhile(n -> n < 4)
.toList();
System.out.println(taken); // Output: [1, 2, 3]
// Drop elements while condition is true
List<Integer> dropped = numbers.stream()
.dropWhile(n -> n < 4)
.toList();
System.out.println(dropped); // Output: [4, 5, 6]List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
// Take elements while condition is true
List<Integer> taken = numbers.stream()
.takeWhile(n -> n < 4)
.toList();
System.out.println(taken); // Output: [1, 2, 3]
// Drop elements while condition is true
List<Integer> dropped = numbers.stream()
.dropWhile(n -> n < 4)
.toList();
System.out.println(dropped); // Output: [4, 5, 6]4. Handling Nullable Values with Stream.ofNullable()
Before Java 11:
String nullableValue = null;
Stream<String> stream = nullableValue == null ? Stream.empty() : Stream.of(nullableValue);String nullableValue = null;
Stream<String> stream = nullableValue == null ? Stream.empty() : Stream.of(nullableValue);In Java 11:
String nullableValue = null;
Stream<String> stream = Stream.ofNullable(nullableValue);
System.out.println(stream.count()); // Output: 0String nullableValue = null;
Stream<String> stream = Stream.ofNullable(nullableValue);
System.out.println(stream.count()); // Output: 0Real-World Use Cases
- Immutable Data Structures: Use
List.of()orSet.of()for constants or configurations. - Null Handling in Streams: Use
Stream.ofNullable()to safely process potentially null values without additional null checks. - Functional Data Processing: Use
takeWhile()anddropWhile()for filtering and slicing streams based on conditions. - Array Conversion: Simplify collection-to-array conversion using
toArray(String[]::new)for cleaner code.
Comparison with Pre-Java 11 Code
Before Java 11: Handling Nullable Streams
String value = null;
Stream<String> stream = value == null ? Stream.empty() : Stream.of(value);String value = null;
Stream<String> stream = value == null ? Stream.empty() : Stream.of(value);In Java 11: Handling Nullable Streams
String value = null;
Stream<String> stream = Stream.ofNullable(value);String value = null;
Stream<String> stream = Stream.ofNullable(value);Before Java 11: Creating Immutable Collections
List<String> list = Collections.unmodifiableList(Arrays.asList("A", "B", "C"));List<String> list = Collections.unmodifiableList(Arrays.asList("A", "B", "C"));In Java 11: Creating Immutable Collections
List<String> list = List.of("A", "B", "C");List<String> list = List.of("A", "B", "C");6. HTTP Client (Standardized)
The HTTP Client API provides a modern, asynchronous, and feature-rich way to handle HTTP communications in Java. It supports both synchronous and asynchronous programming models, HTTP/1.1 and HTTP/2, and allows developers to send HTTP requests and process responses with ease.
Key Features of HTTP Client API
- HTTP/2 Support: Built-in support for HTTP/2 improves performance with multiplexing and header compression.
- Asynchronous Programming: Uses
CompletableFuturefor non-blocking requests. - Built-in Security: Supports TLS/SSL for secure communications.
- WebSocket API: Enables real-time, full-duplex communication using WebSockets.
- Ease of Use: Fluent API design makes it intuitive and concise.
- Modern Features: Provides handling of request/response bodies using
String,InputStream, or customBodyHandler.
HTTP Client API Components
HttpClient: Represents an HTTP client to send requests and receive responses.HttpRequest: Represents an HTTP request with methods, headers, and body.HttpResponse: Represents the response received from an HTTP request.- WebSocket: Provides an interface for WebSocket communication.
Examples of HTTP Client API
1. Synchronous GET Request
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class HttpClientExample {
public static void main(String[] args) throws IOException, InterruptedException {
// Create HttpClient
HttpClient client = HttpClient.newHttpClient();
// Create HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
.GET()
.build();
// Send HttpRequest and get HttpResponse
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Print the response
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
}
}import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class HttpClientExample {
public static void main(String[] args) throws IOException, InterruptedException {
// Create HttpClient
HttpClient client = HttpClient.newHttpClient();
// Create HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
.GET()
.build();
// Send HttpRequest and get HttpResponse
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Print the response
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
}
}- Asynchronous GET Request
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
public class AsyncHttpClientExample {
public static void main(String[] args) {
// Create HttpClient
HttpClient client = HttpClient.newHttpClient();
// Create HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
.GET()
.build();
// Send async request
CompletableFuture<HttpResponse<String>> responseFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
// Process the response
responseFuture.thenAccept(response -> {
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
});
// Keep the program running to wait for async response
responseFuture.join();
}
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
public class AsyncHttpClientExample {
public static void main(String[] args) {
// Create HttpClient
HttpClient client = HttpClient.newHttpClient();
// Create HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
.GET()
.build();
// Send async request
CompletableFuture<HttpResponse<String>> responseFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
// Process the response
responseFuture.thenAccept(response -> {
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
});
// Keep the program running to wait for async response
responseFuture.join();
}
}3. POST Request
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class PostRequestExample {
public static void main(String[] args) throws IOException, InterruptedException {
// Create HttpClient
HttpClient client = HttpClient.newHttpClient();
// Create HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
.POST(HttpRequest.BodyPublishers.ofString("{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}"))
.header("Content-Type", "application/json")
.build();
// Send request and get response
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Print the response
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
}
}import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class PostRequestExample {
public static void main(String[] args) throws IOException, InterruptedException {
// Create HttpClient
HttpClient client = HttpClient.newHttpClient();
// Create HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
.POST(HttpRequest.BodyPublishers.ofString("{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}"))
.header("Content-Type", "application/json")
.build();
// Send request and get response
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Print the response
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
}
}4. WebSocket Communication
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.util.concurrent.CompletionStage;
public class WebSocketExample {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
WebSocket webSocket = client.newWebSocketBuilder()
.buildAsync(URI.create("wss://echo.websocket.org"), new WebSocket.Listener() {
@Override
public void onOpen(WebSocket webSocket) {
System.out.println("WebSocket opened");
webSocket.sendText("Hello, WebSocket!", true);
WebSocket.Listener.super.onOpen(webSocket);
}
@Override
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
System.out.println("Received: " + data);
return WebSocket.Listener.super.onText(webSocket, data, last);
}
})
.join();
}
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.util.concurrent.CompletionStage;
public class WebSocketExample {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
WebSocket webSocket = client.newWebSocketBuilder()
.buildAsync(URI.create("wss://echo.websocket.org"), new WebSocket.Listener() {
@Override
public void onOpen(WebSocket webSocket) {
System.out.println("WebSocket opened");
webSocket.sendText("Hello, WebSocket!", true);
WebSocket.Listener.super.onOpen(webSocket);
}
@Override
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
System.out.println("Received: " + data);
return WebSocket.Listener.super.onText(webSocket, data, last);
}
})
.join();
}
}Conclusion
Java 11 introduced a blend of innovative features, performance improvements, and practical enhancements to the core libraries, making it a versatile and robust release. With its focus on simplifying development tasks (e.g., the new String and file API methods), embracing modern HTTP standards, and supporting cutting-edge security protocols, Java 11 appeals to both new and experienced developers. As a Long-Term Support (LTS) version, it serves as a stable foundation for enterprise applications while offering forward-looking capabilities for modern development needs. For those migrating from Java 8 or earlier, Java 11 represents a compelling choice for balancing stability and modern features.