Java 18 UTF-8 by Default: What Changes and Why

Introduction Many Java APIs read and write text using the default charset when you do not name one - FileReader, FileWriter, InputStreamReader, Scanner, String.getBytes() and more. Until Java 17, the default charset came from the operating system: UTF-8 on most Linux and macOS systems, but a legacy code page such as Windows-1252 on Windows. So a program which wrote a file with an accented character on Windows could produce a file that the same program misread on Linux. Java 18Click to read tutorial: Overview of New Features in Java 18 (JEP 400) made UTF-8 the default charset on every platform. This tutorial explains UTF-8 by default with examples: what changed, which APIs are affected and which are not, how to find code that depends on the old behaviour, and how to restore it if you must. Seeing the default charset
The default charset and a file written with it
//DefaultCharsetDemo.java
import java.io.FileReader;
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 DefaultCharsetDemo {
  public static void main(String[] args) throws IOException {
    System.out.println("Default charset : " + Charset.defaultCharset());
    System.out.println("native.encoding : " + System.getProperty("native.encoding"));

    Path file = Files.createTempFile("cafe", ".txt");
    try (FileWriter writer = new FileWriter(file.toFile())) {     // no charset given: uses the default
      writer.write("café");
    }
    System.out.println("Bytes in file   : " + Files.size(file));
    try (FileReader reader = new FileReader(file.toFile())) {
      char[] buffer = new char[10];
      int n = reader.read(buffer);
      System.out.println("Read back       : " + n + " characters, last is U+" + String.format("%04X", (int) buffer[n - 1]));
    }
    Files.delete(file);
  }
}
 OUTPUT of the above code
Default charset : UTF-8
native.encoding : Cp1252
Bytes in file   : 5
Read back       : 4 characters, last is U+00E9
Explanation of the code
  • The default charset is UTF-8, even though this example ran on Windows. The new system property native.encoding (added in Java 17) still reports the operating system's own charset - here Windows-1252, reported as Cp1252 - for code which genuinely needs it.
  • FileWriter with no charset now writes UTF-8, where é takes 2 bytes, so the 4-character word is 5 bytes long. Reading it back with FileReader, also UTF-8, gives the same 4 characters - on every operating system.
  • The text is written as café so that the source file itself does not depend on the compiler's encoding. Since Java 18, javac also reads source files as UTF-8 by default.
Restoring the old behaviour: file.encoding=COMPAT For applications which cannot be fixed straight away, starting the JVM with -Dfile.encoding=COMPAT makes the default charset the operating system's again, exactly as in Java 17 -
java -Dfile.encoding=COMPAT DefaultCharsetDemo
 OUTPUT of the above command
Default charset : windows-1252
native.encoding : Cp1252
Bytes in file   : 4
Read back       : 4 characters, last is U+00E9
On Windows, the same FileWriter now writes Windows-1252, where é is a single byte - so the file is 4 bytes long, and a program reading it as UTF-8 elsewhere would see an invalid character. This is the inconsistency Java 18 removed. Use COMPAT only as a temporary measure while fixing the code. What is affected, and what is not
  • Affected: every API which uses the default charset when none is given - FileReader and FileWriter, InputStreamReader and OutputStreamWriter, PrintStream and PrintWriter created on files or streams, Formatter, Scanner, String(byte[]) and String.getBytes().
  • Already UTF-8: the NIO Files methods such as Files.readString() and Files.newBufferedReader() have always used UTF-8, so they are unaffected.
  • Not affected: System.out and System.err keep using the console's encoding, so text printed to a Windows terminal still displays correctly. Their encoding is reported by the properties stdout.encoding and stderr.encoding, added in Java 19.
  • File names are still converted using the operating system's encoding (sun.jnu.encoding), since they must match what the OS expects.
How to prepare your code
  1. Test on Java 17 with UTF-8: run your tests with -Dfile.encoding=UTF-8 on Java 17. If they pass, the switch to Java 18 or later will not affect you.
  2. Name the charset explicitly in code which must read or write a specific encoding, for example new InputStreamReader(in, StandardCharsets.UTF_8) or new FileWriter(file, StandardCharsets.ISO_8859_1) - the overloads taking a Charset were added in Java 10 and 11. Code which names its charsets does not depend on any default.
  3. Use native.encoding when you truly need the operating system's charset, for example to exchange files with a legacy Windows application.
  4. Check data files written by older versions of your application on Windows, which may be in Windows-1252 and need to be read with that charset explicitly.
Summary In this tutorial we saw how Java 18 made UTF-8 the default charset on every platform, ending differences between Windows and other systems. We printed the default charset and the new native.encoding property, wrote and read a file with the default charset, and saw the old behaviour come back with -Dfile.encoding=COMPAT. We went through which APIs are affected and which are not, such as System.out, and lastly how to prepare code for the change. Next, read about the Java 18 Simple Web ServerClick to read tutorial: Java 18 Simple Web Server (jwebserver) Tutorial.