JDK 18 Overview
Java 18, released in March 2022, is a non-LTS release. Rather than major new features, it focused on resolving longstanding inconveniences. UTF-8 default encoding, a simple web server, and Javadoc snippet tags are the highlights. As the release immediately following JDK 17 LTS, it has a strong stabilization character.
| JEP | Feature | Status |
|---|---|---|
| JEP 400 | UTF-8 Default Encoding | Final |
| JEP 408 | Simple Web Server | Final |
| JEP 413 | Javadoc Code Snippets | Final |
| JEP 416 | Reflection Reimplemented with Method Handles | Internal |
| JEP 417 | Vector API (Third Incubator) | Incubator |
| JEP 418 | Internet-Address Resolution SPI | Final |
| JEP 421 | Finalization Deprecation for Removal | Deprecated |
UTF-8 Default Encoding (JEP 400)
One of the most common pitfalls in Java was the platform default encoding issue. On Windows the default was MS949, on macOS/Linux it was UTF-8, so the same code behaved differently depending on the operating system.
Starting with JDK 18, UTF-8 is the default on all platforms.
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
public class Utf8DefaultExample {
public static void main(String[] args) throws IOException {
// From JDK 18, default encoding = UTF-8 (same on all OS)
System.out.println("Default encoding: " + Charset.defaultCharset());
// JDK 17 and earlier on Windows: MS949 or EUC-KR
// JDK 18 and later on Windows: UTF-8
// File write — UTF-8 even without specifying encoding
Path tempFile = Files.createTempFile("test", ".txt");
try (FileWriter writer = new FileWriter(tempFile.toFile())) {
writer.write("UTF-8 test — default encoding");
}
// File read — also UTF-8
String content = Files.readString(tempFile);
System.out.println("File content: " + content);
// File content: UTF-8 test — default encoding
// Byte array conversion also defaults to UTF-8
byte[] bytes = "Java 18".getBytes(); // UTF-8 even without explicit charset
System.out.println("Byte count: " + bytes.length);
// Byte count: 7
// Cleanup
Files.deleteIfExists(tempFile);
}
}
Here’s how this change affects existing code:
| Scenario | JDK 17 and earlier (Windows) | JDK 18 and later |
|---|---|---|
new FileWriter("a.txt") | Writes as MS949 | Writes as UTF-8 |
new String(bytes) | Interprets as MS949 | Interprets as UTF-8 |
System.out.println("text") | Console encoding dependent | UTF-8 |
Code that reads files written in legacy encodings like MS949/EUC-KR must explicitly specify the encoding. Assuming “the old default still applies” without specifying encoding may result in garbled characters.
Simple Web Server (JEP 408)
JDK 18 includes a built-in web server for serving static files. It serves a similar role to Python’s python -m http.server. It’s useful for prototyping, testing, and file sharing.
Command Line Usage
# Serve the current directory on port 8080
jwebserver -p 8080
# Specify a directory
jwebserver -d /path/to/docs -p 9000
# Specify bind address
jwebserver -b 0.0.0.0 -p 8080
Programmatic Usage
The web server can also be configured programmatically.
import java.net.InetSocketAddress;
import java.nio.file.Path;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.SimpleFileServer;
import com.sun.net.httpserver.SimpleFileServer.OutputLevel;
public class SimpleWebServerExample {
public static void main(String[] args) throws Exception {
// Create a static file serving server
Path root = Path.of(System.getProperty("user.dir"));
InetSocketAddress address = new InetSocketAddress(8080);
HttpServer server = SimpleFileServer.createFileServer(
address,
root,
OutputLevel.VERBOSE // Output request logs
);
server.start();
System.out.println("Server started: http://localhost:8080");
System.out.println("Serving directory: " + root);
System.out.println("Press Ctrl+C to stop");
// Server started: http://localhost:8080
// Serving directory: /home/user/project
// Press Ctrl+C to stop
}
}
The Simple Web Server is static files only. It doesn’t support CGI, servlets, or dynamic content. It should be used only for development and testing purposes, not production.
Javadoc Code Snippets (JEP 413)
The existing {@code ...} tag made it difficult to include complex code examples in Javadoc. JDK 18’s @snippet tag supports syntax highlighting, external file references, and region selection.
/**
* A Record for storing user information.
*
* Usage example:
* {@snippet :
* // Create a User instance
* User user = new User("John Doe", "john@example.com"); // @highlight substring="new User"
* System.out.println(user.name()); // John Doe
* System.out.println(user.email()); // john@example.com
* }
*
* Snippets can also be loaded from external files:
* {@snippet file="UserExample.java" region="creation"}
*/
public record User(String name, String email) {
// Compact constructor for validation
public User {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name is required");
}
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("A valid email is required");
}
}
}
class SnippetDemo {
public static void main(String[] args) {
User user = new User("John Doe", "john@example.com");
System.out.println(user);
// User[name=John Doe, email=john@example.com]
// Validation check
try {
new User("", "invalid");
} catch (IllegalArgumentException e) {
System.out.println("Validation failed: " + e.getMessage());
// Validation failed: Name is required
}
}
}
Key features of the @snippet tag:
| Feature | Syntax | Description |
|---|---|---|
| Inline snippet | {@snippet : ... } | Write code directly in Javadoc |
| External file reference | {@snippet file="..." } | Import from a separate file |
| Region selection | region="name" | Display only a specific section of the file |
| Highlighting | @highlight substring="..." | Highlight specific parts |
| Replacement | @replace regex="..." replacement="..." | Replace strings during display |
Internet-Address Resolution SPI (JEP 418)
The internal implementation used for hostname resolution in InetAddress.getByName() and others can now be replaced via SPI (Service Provider Interface). This is useful for mocking DNS responses in test environments or injecting custom DNS resolution logic.
import java.net.InetAddress;
import java.net.UnknownHostException;
public class AddressResolutionExample {
public static void main(String[] args) {
try {
// Default DNS resolution — can be replaced via SPI with custom implementation
InetAddress address = InetAddress.getByName("localhost");
System.out.println("Host: " + address.getHostName());
System.out.println("Address: " + address.getHostAddress());
// Host: localhost
// Address: 127.0.0.1
// Look up multiple addresses
InetAddress[] addresses = InetAddress.getAllByName("google.com");
System.out.println("google.com address count: " + addresses.length);
for (InetAddress addr : addresses) {
System.out.println(" " + addr.getHostAddress());
}
} catch (UnknownHostException e) {
System.out.println("Host not found: " + e.getMessage());
}
}
}
To create an actual SPI implementation, extend java.net.spi.InetAddressResolverProvider. Being able to control DNS responses without external dependencies makes writing integration tests more convenient.
Finalization Deprecation for Removal (JEP 421)
Object.finalize() was marked as forRemoval. finalize() never guaranteed when GC would call it, and it was a source of performance degradation and memory leaks.
import java.lang.ref.Cleaner;
public class CleanerExample {
// Use Cleaner instead of finalize() (JDK 9+)
private static final Cleaner cleaner = Cleaner.create();
static class Resource implements AutoCloseable {
private final Cleaner.Cleanable cleanable;
private final ResourceState state;
// Separate cleanup logic into a static class (prevents this reference)
private static class ResourceState implements Runnable {
private final String name;
ResourceState(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("Resource cleanup: " + name);
}
}
Resource(String name) {
this.state = new ResourceState(name);
this.cleanable = cleaner.register(this, state);
System.out.println("Resource created: " + name);
}
@Override
public void close() {
cleanable.clean(); // Explicit cleanup
}
}
public static void main(String[] args) {
// Explicit cleanup with try-with-resources (recommended)
try (Resource res = new Resource("DB Connection")) {
System.out.println("Using resource...");
}
// Resource created: DB Connection
// Using resource...
// Resource cleanup: DB Connection
System.out.println("Program terminated");
}
}
Alternatives to finalize():
| Approach | Use Case | Call Guarantee |
|---|---|---|
try-with-resources + AutoCloseable | Explicit resource release | Guaranteed |
Cleaner (JDK 9+) | Safety net (fallback) cleanup | GC dependent |
PhantomReference + ReferenceQueue | Low-level cleanup control | GC dependent |
Summary
JDK 18 focused on developer convenience improvements rather than flashy new features.
- UTF-8 Default Encoding: Resolves encoding inconsistencies between Windows/macOS/Linux. Especially welcome for international development environments
- Simple Web Server: Serve static files with a single
jwebservercommand. Useful for prototyping and testing - @snippet Tag: Insert code examples with syntax highlighting and external file references in Javadoc
- Internet-Address Resolution SPI: Extension point for customizing DNS resolution
- Finalization Deprecation: Use
Cleanerortry-with-resourcesinstead offinalize()
JDK 18 is a non-LTS release, so rather than using it directly in production, it’s best viewed as a stepping stone from JDK 17 LTS to the next LTS (JDK 21). However, the UTF-8 default change is a critical change to be aware of when migrating to JDK 21.