JDK 18 Key Features — UTF-8 Default and Simple Web Server

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.

JEPFeatureStatus
JEP 400UTF-8 Default EncodingFinal
JEP 408Simple Web ServerFinal
JEP 413Javadoc Code SnippetsFinal
JEP 416Reflection Reimplemented with Method HandlesInternal
JEP 417Vector API (Third Incubator)Incubator
JEP 418Internet-Address Resolution SPIFinal
JEP 421Finalization Deprecation for RemovalDeprecated

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:

ScenarioJDK 17 and earlier (Windows)JDK 18 and later
new FileWriter("a.txt")Writes as MS949Writes as UTF-8
new String(bytes)Interprets as MS949Interprets as UTF-8
System.out.println("text")Console encoding dependentUTF-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:

FeatureSyntaxDescription
Inline snippet{@snippet : ... }Write code directly in Javadoc
External file reference{@snippet file="..." }Import from a separate file
Region selectionregion="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():

ApproachUse CaseCall Guarantee
try-with-resources + AutoCloseableExplicit resource releaseGuaranteed
Cleaner (JDK 9+)Safety net (fallback) cleanupGC dependent
PhantomReference + ReferenceQueueLow-level cleanup controlGC 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 jwebserver command. 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 Cleaner or try-with-resources instead of finalize()

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.

Was this article helpful?