JDK 11 Key Features — HTTP Client, String Enhancements, and the Second LTS

JDK 11 — The Second LTS

Released in September 2018, JDK 11 is the second LTS (Long-Term Support) release after JDK 8. It is the most commonly chosen version when migrating from JDK 8 to modern Java, and many enterprise environments use it as their production baseline.

JDK 11 includes all features added in JDK 9 and 10 — such as the module system and var — while adding practical improvements like the official HTTP Client API and enhanced String/Files utilities.

HTTP Client API

The HTTP Client, first introduced as an incubator module in JDK 9, became a standard API (java.net.http) in JDK 11. It replaces the complex, low-level HttpURLConnection API and natively supports HTTP/2, asynchronous requests, and WebSocket.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class HttpClientExample {
    public static void main(String[] args) throws Exception {
        // Create HttpClient — HTTP/2 by default, with timeout configuration
        var client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_2)
            .connectTimeout(Duration.ofSeconds(10))
            .build();

        // Create a GET request
        var request = HttpRequest.newBuilder()
            .uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
            .header("Accept", "application/json")
            .GET()
            .build();

        // Synchronous request: send() — blocks until the response arrives
        HttpResponse<String> response = client.send(
            request,
            HttpResponse.BodyHandlers.ofString()
        );

        System.out.println("Status code: " + response.statusCode());
        System.out.println("Protocol: " + response.version());
        System.out.println("Body length: " + response.body().length() + " chars");
        // Output:
        // Status code: 200
        // Protocol: HTTP_2
        // Body length: 292 chars

        // Asynchronous request: sendAsync() — returns a CompletableFuture
        client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAccept(body -> System.out.println("Async response received: " + body.length() + " chars"))
            .join();  // Wait for completion on the main thread
        // Output: Async response received: 292 chars

        // POST request
        var postRequest = HttpRequest.newBuilder()
            .uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(
                "{\"title\": \"Test\", \"body\": \"Content\", \"userId\": 1}"
            ))
            .build();

        var postResponse = client.send(postRequest, HttpResponse.BodyHandlers.ofString());
        System.out.println("POST status code: " + postResponse.statusCode());
        // Output: POST status code: 201
    }
}

HttpClient is thread-safe, so a single instance can be shared across the entire application. There is no need to create a new one for each request.

New String Methods

JDK 11 added utility methods to the String class that are frequently needed in everyday development.

import java.util.stream.Collectors;

public class StringMethods {
    public static void main(String[] args) {
        // isBlank(): check if the string is empty or contains only whitespace
        System.out.println("  ".isBlank());    // true
        System.out.println("  ".isEmpty());    // false — contains whitespace characters
        System.out.println("hello".isBlank()); // false
        // isBlank() treats whitespace, tabs, and newlines as "blank", unlike isEmpty()

        // strip(): remove leading and trailing whitespace (Unicode-aware)
        String text = "  \u2000Hello Java\u2000  ";
        System.out.println("trim: [" + text.trim() + "]");
        // Output: trim: [ Hello Java   ] — does not handle Unicode whitespace
        System.out.println("strip: [" + text.strip() + "]");
        // Output: strip: [Hello Java] — removes Unicode whitespace as well
        System.out.println("stripLeading: [" + text.stripLeading() + "]");
        // Output: stripLeading: [Hello Java   ]
        System.out.println("stripTrailing: [" + text.stripTrailing() + "]");
        // Output: stripTrailing: [   Hello Java]

        // lines(): convert a string into a Stream of lines
        String multiline = "First line\nSecond line\nThird line";
        long lineCount = multiline.lines().count();
        System.out.println("Line count: " + lineCount);
        // Output: Line count: 3

        // Filtering blank lines example
        String withBlanks = "Hello\n\n  \nWorld\n";
        var nonBlank = withBlanks.lines()
            .filter(line -> !line.isBlank())
            .collect(Collectors.toList());
        System.out.println("Non-blank lines: " + nonBlank);
        // Output: Non-blank lines: [Hello, World]

        // repeat(): repeat a string
        String separator = "-".repeat(30);
        System.out.println(separator);
        // Output: ------------------------------
        System.out.println("*".repeat(5));
        // Output: *****
    }
}

The difference between strip() and trim() lies in Unicode whitespace handling. trim() only removes ASCII whitespace (code point 32 and below), while strip() removes all characters for which Character.isWhitespace() returns true. In multilingual environments, strip() is the safer choice.

Files Utility Methods

Reading or writing an entire file as a string is now possible in a single line.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class FilesUtility {
    public static void main(String[] args) throws IOException {
        // Create a temporary file
        Path tempFile = Files.createTempFile("jdk11-demo", ".txt");

        // writeString(): write a string directly to a file
        Files.writeString(tempFile, "File writing got simpler in JDK 11.\nThis is the second line.");

        // readString(): read the entire file as a string
        String content = Files.readString(tempFile);
        System.out.println("File content:");
        System.out.println(content);
        // Output:
        // File content:
        // File writing got simpler in JDK 11.
        // This is the second line.

        System.out.println("File size: " + Files.size(tempFile) + " bytes");
        // Output: File size: 59 bytes

        // Cleanup: delete the temporary file
        Files.delete(tempFile);
        System.out.println("Temporary file deleted");
        // Output: Temporary file deleted
    }
}

Tasks that previously required reading line by line with BufferedReader and assembling with StringBuilder, or using external libraries (Apache Commons IO, Guava), can now be done in a single line with the standard API.

Using var in Lambdas

The var keyword introduced in JDK 10 can now be used for lambda parameters in JDK 11 (JEP 323). The reason for explicitly using var when type inference is already possible is to enable annotations on lambda parameters.

import java.util.List;
import java.util.stream.Collectors;

public class LambdaVar {
    // Annotation definition (example)
    @interface NonNull {}

    public static void main(String[] args) {
        var names = List.of("Java", "Kotlin", "Scala", "Groovy");

        // Before JDK 10: explicit type or omitted
        var result1 = names.stream()
            .filter(name -> name.length() > 4)
            .collect(Collectors.toList());

        // JDK 11: use var to enable annotations on lambda parameters
        var result2 = names.stream()
            .filter((@NonNull var name) -> name.length() > 4)
            .collect(Collectors.toList());

        System.out.println("Longer than 5 chars: " + result2);
        // Output: Longer than 5 chars: [Kotlin, Scala, Groovy]
    }
}

Running Source Files Directly with java

Starting with JDK 11, .java source files can be run directly without compilation (JEP 330). This lets you skip the javac step when running simple scripts or test code.

# Before JDK 10: compile then run (2 steps)
javac Hello.java
java Hello

# JDK 11: run source file directly (1 step)
java Hello.java

This only works for single source files. For projects consisting of multiple files, you still need to use javac. On Unix/Linux, adding a shebang (#!/usr/bin/java --source 11) allows the file to be run like a shell script.

ZGC (experimental): ZGC (JEP 333) was experimentally introduced in JDK 11. It is an ultra-low-latency GC designed to keep Stop-the-World pauses below 10ms. It guarantees short pause times even with terabyte-scale heaps. It later reached Production Ready status in JDK 15.

Epsilon GC: A “no-op” GC that does nothing. It only allocates memory without reclaiming it, and the JVM terminates when the heap is full. Its use cases include measuring GC overhead in performance benchmarks, or completely eliminating GC costs in short-lived applications (such as batch jobs).

Nest-Based Access Control: When inner classes accessed private members of their enclosing class, the compiler previously generated synthetic bridge methods. Starting with JDK 11, the JVM directly recognizes “nest” relationships, enabling direct access without bridge methods. This also works consistently with reflection.

Migration Checkpoints: JDK 8 to 11

Here are the key items to verify when upgrading from JDK 8 to 11.

ItemWhat to Check
Module SystemDetermine if --add-opens or --add-modules options are needed
Removed APIsAdd separate dependencies for Java EE modules (javax.xml.bind, javax.annotation, etc.)
Internal APIsCheck for warnings or errors when using internal APIs like sun.misc.Unsafe
Build ToolsUpdate Maven/Gradle plugin versions
LibrariesVerify JDK 11 compatible versions of major frameworks (Spring, Hibernate, etc.)
GC OptionsRemove PermGen-related options (-XX:PermSize) and replace with Metaspace options

The javax to jakarta namespace change is particularly the biggest hurdle. javax.xml.bind (JAXB), javax.annotation, and others have been removed from the JDK, so they must be added as Maven/Gradle dependencies separately.

Summary

FeatureCore Value
HTTP Client APIModern HTTP communication with HTTP/2 and async support built in
String MethodsSimplified string handling with isBlank, strip, lines, repeat
Files UtilitySingle-line file I/O with readString/writeString
Lambda varEnables annotations on lambda parameters
Direct Source ExecutionRun without compilation via java Hello.java
ZGCUltra-low-latency GC under 10ms (experimental)
Epsilon GCNo-op GC for benchmarks and short-lived applications

JDK 11 is an LTS release that combines the stability of JDK 8 with the innovations of JDK 9 and 10. When migrating from JDK 8, start by checking module system compatibility and removed Java EE APIs, then actively adopt the HTTP Client API and new String methods. The next migration target would be JDK 17 (the third LTS) or JDK 21 (the fourth LTS, featuring Virtual Threads).

Was this article helpful?