Java 11 is the first long-term support (LTS) release after Java 8.
Starting with Java 11, there is no free long-term support (LTS) from Oracle.
Java 11 contains the following major new features compared to previous versions :
- New String Methods;
- New File Methods;
- Collection to an Array;
- The Not Predicate Method;
- Local-Variable Syntax for Lambda;
- Standardization of HTTP Client API;
- Running Java Files;
- A No-Op Garbage Collector.
1. New String Methods
Java 11 adds a few new methods to the String class : isBlank, lines, strip, stripLeading, stripTrailing, and repeat.
public class Java11NewFeaturesTest1 {
public static void main(String[] args) {
System.out.println("".isBlank());
System.out.println("\t".isBlank());
System.out.println("\n".isBlank());
System.out.println("**********");
System.out.println(" hello world ".strip() + "!");
System.out.println("\thello world\t".strip() + "!");
System.out.println("\nhello world\n".strip() + "!");
System.out.println("**********");
System.out.println(" hello world ".stripLeading() + "!");
System.out.println("\thello world\t".stripLeading() + "!");
System.out.println("\nhello world\n".stripLeading() + "!");
System.out.println("**********");
System.out.println(" hello world ".stripTrailing() + "!");
System.out.println("\thello world\t".stripTrailing() + "!");
System.out.println("\nhello world\n".stripTrailing() + "!");
System.out.println("**********");
System.out.println("hello world".repeat(2));
System.out.println("hello world".repeat(0));
System.out.println("hello world".repeat(1));
System.out.println("**********");
"hello\nworld\n".lines().forEach(System.out::println);
}
}
/**
* Output:
true
true
true
**********
hello world!
hello world!
hello world!
**********
hello world !
hello world !
hello world
!
**********
hello world!
hello world!
hello world!
**********
hello worldhello world
hello world
**********
hello
world
*/
2. New File Methods
In Java 11, it is easier to read and write Strings from files by using the new readString and writeString static methods from the Files class.
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class Java11NewFeaturesTest2 {
public static void main(String[] args) {
try {
File tempFile = File.createTempFile("greeting", ".txt");
tempFile.deleteOnExit();
Path filePath = Files.writeString(tempFile.toPath(), "hello world");
String fileContent = Files.readString(filePath);
System.out.println(fileContent);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Output:
hello world
*/
3. Collection to an Array
In Java 11, the java.util.Collection interface contains a new default toArray method which takes an IntFunction argument.
import java.util.ArrayList;
import java.util.List;
public class Java11NewFeaturesTest3 {
public static void main(String[] args) {
List<String> l = new ArrayList<>();
l.add("hello");
l.add("world");
String[] a = l.toArray(new String[l.size()]);
display(a);
String[] a2 = l.toArray(size -> {
return new String[size];
});
display(a2);
String[] a3 = l.toArray(String[]::new);
display(a3);
}
private static void display(String[] a) {
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
System.out.println();
}
}
/**
* Output:
hello
world
hello
world
hello
world
*/
4. The Not Predicate Method
In Java 11, a static not method has been added to the Predicate interface.
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class Java11NewFeaturesTest4 {
public static void main(String[] args) {
List<Person> users = new ArrayList<>();
users.add(new Person("tom", 18));
users.add(new Person("jerry", 16));
users.add(new Person("spike", 28));
users.stream().filter(p -> !p.isAdult()).forEach(System.out::println);
System.out.println();
users.stream().filter(Predicate.not(Person::isAdult)).forEach(System.out::println);
}
private static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
boolean isAdult() {
return this.age > 18;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
}
/**
* Output:
Person [name=tom, age=18]
Person [name=jerry, age=16]
Person [name=tom, age=18]
Person [name=jerry, age=16]
*/
5. Local Variable Syntax for Lambda
Java 11 add the support for using the local variable syntax (var keyword) in lambda parameters.
import java.util.ArrayList;
import java.util.List;
public class Java11NewFeaturesTest5 {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>();
nums.add(1);
nums.add(2);
nums.add(3);
nums.stream().map(num -> num * 2).forEach(System.out::println);
System.out.println();
nums.stream().map((Integer num) -> num * 2).forEach(System.out::println);
System.out.println();
nums.stream().map((var num) -> num * 2).forEach(System.out::println);
}
}
/**
* Output:
2
4
6
2
4
6
2
4
6
*/
Why use var for lambda parameters when we could skip the types?
Because without specifying the types, annotations such as below can not be used :
(@Nonnull var p1, @Nullable var p2) -> p1 + p2
6. Standardization of HTTP Client API
Java 11 introduces a standardization of HTTP Client API that implements HTTP/2 and Web Socket.
It aims to replace the legacy HttpURLConnection API.
It is quite feature rich and user-friendly so that java based applications can make HTTP requests without using any third-party libraries, such as Apache HttpClient, Jetty and Spring RestTemplate.
It supports HTTP/1.1 and HTTP/2, both synchronous and asynchronous programming models, handles request and response bodies as reactive-streams, and follows the familiar builder pattern.
6.1 Simple Http Server
Below is an example of a simple http server being used to demonstrate the way of using New HttpClient API of Java 11.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
public class SimpleHttpServer {
public static void main(String[] args) {
ServerSocket server = null;
try {
server = new ServerSocket(8080);
System.out.println("Listening for connection on port 8080 ....");
while (true) {
try (Socket clientSocket = server.accept()) {
InputStreamReader isr = new InputStreamReader(clientSocket.getInputStream());
BufferedReader reader = new BufferedReader(isr);
// show header info and get content length
int len = 0;
String line = reader.readLine();
while (!line.isEmpty()) {
System.out.println(line);
if (line.startsWith("Content-Length")) {
len = Integer.parseInt(line.split(":")[1].trim());
}
line = reader.readLine();
}
// show request body
if (len != 0) {
char[] body = new char[len];
reader.read(body, 0, len);
System.out.println(new String(body));
}
System.out.println();
// response
Date today = new Date();
String httpResponse = "HTTP/1.1 200 OK\r\n\r\n" + today;
clientSocket.getOutputStream().write(httpResponse.getBytes("UTF-8"));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (server != null) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* Output:
Listening for connection on port 8080 ....
*/
6.2 Synchronous Model
New HttpClient API provides send(…) method for sending synchronously a request to a server (blocks until the response comes).
import java.io.IOException;
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 Java11NewFeaturesTest6 {
public static void main(String[] args) {
try {
HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)
.connectTimeout(Duration.ofSeconds(10)).build();
HttpRequest request = HttpRequest.newBuilder().GET().uri(URI.create("http://localhost:8080")).build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println();
System.out.println(response.body());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Output:
[ Client ]
200
Wed Jun 12 14:30:00 CST 2024
[ Server ]
Listening for connection on port 8080 ....
GET / HTTP/1.1
Host: localhost:8080
User-Agent: Java-http-client/11.0.1
*/
6.3 Asynchronous Model
New HttpClient API provides sendAsync(…) method for sending asynchronously a request to a server (not wait for the response, non-blocking).
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
public class Java11NewFeaturesTest7 {
public static void main(String[] args) {
HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)
.connectTimeout(Duration.ofSeconds(10)).build();
HttpRequest request = HttpRequest.newBuilder().GET().uri(URI.create("http://localhost:8080")).build();
CompletableFuture<HttpResponse<String>> responseFuture = httpClient.sendAsync(request,
HttpResponse.BodyHandlers.ofString());
responseFuture.thenAccept(response -> {
System.out.println(response.statusCode());
System.out.println();
System.out.println(response.body());
}).join();
}
}
/**
* Output:
[ Client ]
200
Wed Jun 12 14:30:00 CST 2024
[ Server ]
Listening for connection on port 8080 ....
GET / HTTP/1.1
Host: localhost:8080
User-Agent: Java-http-client/11.0.1
*/
6.4 Custom Executor
It is also possible to define an Executor that provides threads to be used by asynchronous calls.
By default, the HttpClient uses executor java.util.concurrent.Executors.newCachedThreadPool().
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Java11NewFeaturesTest8 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
try {
HttpClient httpClient = HttpClient.newBuilder().executor(executorService)
.version(HttpClient.Version.HTTP_1_1)
.connectTimeout(Duration.ofSeconds(10)).build();
HttpRequest request = HttpRequest.newBuilder().GET().uri(URI.create("http://localhost:8080")).build();
CompletableFuture<HttpResponse<String>> responseFuture = httpClient.sendAsync(request,
HttpResponse.BodyHandlers.ofString());
responseFuture.thenAccept(response -> {
System.out.println(response.statusCode());
System.out.println();
System.out.println(response.body());
}).join();
} finally {
executorService.shutdown();
}
}
}
/**
* Output:
[ Client ]
200
Wed Jun 12 14:30:00 CST 2024
[ Server ]
Listening for connection on port 8080 ....
GET / HTTP/1.1
Host: localhost:8080
User-Agent: Java-http-client/11.0.1
*/
6.5 Setting a Request Body
To add a body to a request, we can use the request builder methods : POST(BodyPublisher body), PUT(BodyPublisher body) and DELETE().
Below code snippet is an example of POST form data.
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
public class Java11NewFeaturesTest9 {
public static void main(String[] args) throws IOException, InterruptedException {
Map<Object, Object> data = new HashMap<>();
data.put("name", "tom");
data.put("age", "18");
HttpRequest request = HttpRequest.newBuilder()
.POST(ofFormData(data))
.uri(URI.create("http://localhost:8080"))
.header("Content-Type", "application/x-www-form-urlencoded")
.build();
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
public static HttpRequest.BodyPublisher ofFormData(Map<Object, Object> data) {
var builder = new StringBuilder();
for (Map.Entry<Object, Object> entry : data.entrySet()) {
if (builder.length() > 0) {
builder.append("&");
}
builder.append(URLEncoder.encode(entry.getKey().toString(), StandardCharsets.UTF_8));
builder.append("=");
builder.append(URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8));
}
return HttpRequest.BodyPublishers.ofString(builder.toString());
}
}
/**
* Output:
[ Client ]
200
Wed Jun 12 14:30:00 CST 2024
[ Server ]
Listening for connection on port 8080 ....
POST / HTTP/1.1
Content-Length: 15
Host: localhost:8080
User-Agent: Java-http-client/11.0.1
Content-Type: application/x-www-form-urlencoded
name=tom&age=18
*/
6.6 POST JSON
The BodyPublishers utility implements various useful publishers, such as publishing the request body from a String or a file.
Below code snippet is an example of publish JSON data as String.
import java.io.IOException;
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 Java11NewFeaturesTest10 {
public static void main(String[] args) throws IOException, InterruptedException {
String json = new StringBuilder()
.append("{")
.append("\"name\":\"tom\",")
.append("\"age\":\"18\"")
.append("}").toString();
HttpRequest request = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(json))
.uri(URI.create("http://localhost:8080"))
.header("Content-Type", "application/json")
.build();
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}
/**
* Output:
[ Client ]
200
Wed Jun 12 14:30:00 CST 2024
[ Server ]
Listening for connection on port 8080 ....
POST / HTTP/1.1
Content-Length: 25
Host: localhost:8080
User-Agent: Java-http-client/11.0.1
Content-Type: application/json
{"name":"tom","age":"18"}
*/
7. Running Java Files
In Java 11, it is not needed to compile the Java source files with javac explicitly anymore.
It is possible to directly run the file using the java command.
public class Java11NewFeaturesTest11 {
public static void main(String[] args) {
System.out.println("hello world");
}
}
/**
* java Java11NewFeaturesTest11.java
*
* Output:
hello world
*/
It has to pay attention that only the first class is taken into account when running directly using the java command.
class Screen {
public static void main(String[] args) {
System.out.println("I am a screen");
}
}
public class Computer {
public static void main(String[] args) {
System.out.println("I am a computer");
}
}
/**
* java Computer.java
*
* Output:
I am a screen
*/
Even if the first class does not have a main method.
class Engine {
/*
* public static void main(String[] args) {
* System.out.println("I am an engine");
* }
*/
}
public class Car {
public static void main(String[] args) {
System.out.println("I am a car");
}
}
/**
* java Car.java
*
* Output:
error: can't find main(String[]) method in class : Engine
*/
8. A No-Op Garbage Collector
In Java 11, a new garbage collector called Epsilon is available for use as an experimental feature.
We can use the -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC flag to enable it.
It is called a No-Op (no operations) because it allocates memory but does not actually collect any garbage.
So it will not be suitable for a typical production Java application.
But it is applicable for simulating out of memory errors.
Below code snippet creates one-megabyte-arrays in a loop.
Since we repeat the loop 2048 times, it means we allocate 2 gigabytes of memory, which is higher than the available maximum heap size 1 gigabyte.
When we run it with the standard VM options, it completes fine.
However, when we run it with the EpsilonGC options, it ends up with OutOfMemoryError.
public class Java11NewFeaturesTest12 {
static final int MEGABYTE_IN_BYTES = 1024 * 1024;
static final int ITERATION_COUNT = 1024 * 2;
public static void main(String[] args) {
System.out.println("Start");
for (int i = 0; i < ITERATION_COUNT; i++) {
byte[] array = new byte[MEGABYTE_IN_BYTES];
}
System.out.println("End");
}
}
// java -Xms1024m -Xmx1024m
/*
Output:
Start
End
*/
// java -Xms1024m -Xmx1024m -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
/*
Output:
Start
Terminating due to java.lang.OutOfMemoryError: Java heap space
*/
There are a few specific use-cases where this No-Op Garbage Collector could be useful :
- Performance testing;
- Memory pressure testing.