From 872b1addeb1a4a45ba73ed530f4dd6820996611d Mon Sep 17 00:00:00 2001 From: junhyeong9812 Date: Sat, 27 Jun 2026 15:49:40 +0900 Subject: [PATCH 1/2] Write native configuration files as UTF-8 Prior to this commit, FileNativeConfigurationWriter wrote native-image configuration files using a plain FileWriter, which encodes with the JVM platform default charset. On a non-UTF-8 platform (for example a Windows JVM, where the default charset is not UTF-8 prior to JDK 18) non-ASCII characters in resource patterns or bundle names were written with the wrong encoding, while GraalVM expects the configuration files to be UTF-8. This commit specifies StandardCharsets.UTF_8 explicitly so the files are always written as UTF-8, consistent with the UTF-8 usage already present in the aot.generate package. Closes gh-36972 Signed-off-by: junhyeong9812 --- .../aot/nativex/FileNativeConfigurationWriter.java | 3 ++- .../nativex/FileNativeConfigurationWriterTests.java | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/FileNativeConfigurationWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/FileNativeConfigurationWriter.java index 4822734106b..3a0e364701e 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/FileNativeConfigurationWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/FileNativeConfigurationWriter.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.function.Consumer; @@ -59,7 +60,7 @@ public class FileNativeConfigurationWriter extends NativeConfigurationWriter { protected void writeTo(String fileName, Consumer writer) { try { File file = createIfNecessary(fileName); - try (FileWriter out = new FileWriter(file)) { + try (FileWriter out = new FileWriter(file, StandardCharsets.UTF_8)) { writer.accept(createJsonWriter(out)); } } diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java index 8406cba5330..3a0ad7cecd7 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java @@ -18,6 +18,7 @@ package org.springframework.aot.nativex; import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.function.Consumer; @@ -176,6 +177,17 @@ class FileNativeConfigurationWriterTests { }"""); } + @Test + void resourceConfigWithNonAsciiPatternIsWrittenAsUtf8() throws IOException { + FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir); + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern("com/example/café/**"); + generator.write(hints); + Path jsonFile = tempDir.resolve("META-INF").resolve("native-image").resolve("reachability-metadata.json"); + byte[] content = Files.readAllBytes(jsonFile); + assertThat(content).containsSequence("café".getBytes(StandardCharsets.UTF_8)); + } + @Test void namespace() { String groupId = "foo.bar"; From 78dcdab3fc86365c77477e666600bb175e118f9a Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:19:42 +0200 Subject: [PATCH 2/2] Polishing See gh-36972 --- .../FileNativeConfigurationWriterTests.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java index 3a0ad7cecd7..834c2c962b7 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java @@ -24,7 +24,6 @@ import java.nio.file.Path; import java.util.function.Consumer; import java.util.function.Function; -import org.json.JSONException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.skyscreamer.jsonassert.JSONAssert; @@ -64,7 +63,7 @@ class FileNativeConfigurationWriterTests { } @Test - void serializationConfig() throws IOException, JSONException { + void serializationConfig() throws Exception { FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir); RuntimeHints hints = new RuntimeHints(); ReflectionHints reflectionHints = hints.reflection(); @@ -82,7 +81,7 @@ class FileNativeConfigurationWriterTests { } @Test - void proxyConfig() throws IOException, JSONException { + void proxyConfig() throws Exception { FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir); RuntimeHints hints = new RuntimeHints(); ProxyHints proxyHints = hints.proxies(); @@ -100,7 +99,7 @@ class FileNativeConfigurationWriterTests { } @Test - void reflectionConfig() throws IOException, JSONException { + void reflectionConfig() throws Exception { FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir); RuntimeHints hints = new RuntimeHints(); ReflectionHints reflectionHints = hints.reflection(); @@ -139,7 +138,7 @@ class FileNativeConfigurationWriterTests { } @Test - void jniConfig() throws IOException, JSONException { + void jniConfig() throws Exception { // same format as reflection so just test basic file generation FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir); RuntimeHints hints = new RuntimeHints(); @@ -158,7 +157,7 @@ class FileNativeConfigurationWriterTests { } @Test - void resourceConfig() throws IOException, JSONException { + void resourceConfig() throws Exception { FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir); RuntimeHints hints = new RuntimeHints(); ResourceHints resourceHints = hints.resources(); @@ -177,7 +176,7 @@ class FileNativeConfigurationWriterTests { }"""); } - @Test + @Test // gh-36972 void resourceConfigWithNonAsciiPatternIsWrittenAsUtf8() throws IOException { FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir); RuntimeHints hints = new RuntimeHints(); @@ -202,7 +201,8 @@ class FileNativeConfigurationWriterTests { assertThat(jsonFile.toFile()).exists(); } - private void assertEquals(String expectedString) throws IOException, JSONException { + + private static void assertEquals(String expectedString) throws Exception { Path jsonFile = tempDir.resolve("META-INF").resolve("native-image").resolve("reachability-metadata.json"); String content = Files.readString(jsonFile); JSONAssert.assertEquals(expectedString, content, JSONCompareMode.NON_EXTENSIBLE);