diff --git a/.github/native-tests.json b/.github/native-tests.json
index f7cdd076826dc..631bc2fa21730 100644
--- a/.github/native-tests.json
+++ b/.github/native-tests.json
@@ -105,7 +105,7 @@
{
"category": "Misc2",
"timeout": 75,
- "test-modules": "hibernate-validator, test-extension/tests, logging-gelf, mailer, native-config-profile, locales/all, locales/some",
+ "test-modules": "hibernate-validator, test-extension/tests, logging-gelf, mailer, native-config-profile, locales/all, locales/some, locales/default",
"os-name": "ubuntu-latest"
},
{
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java
index 12c4728038d71..d3fc8972f635e 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java
@@ -88,7 +88,8 @@ public interface NativeConfig {
/**
* Defines the user language used for building the native executable.
- * It also serves as the default Locale language for the native executable application runtime.
+ * With GraalVM versions prior to GraalVM for JDK 24 it also serves as the default Locale language for the native executable
+ * application runtime.
* e.g. en or cs as defined by IETF BCP 47 language tags.
*
*
@@ -100,7 +101,8 @@ public interface NativeConfig {
/**
* Defines the user country used for building the native executable.
- * It also serves as the default Locale country for the native executable application runtime.
+ * With GraalVM versions prior to GraalVM for JDK 24 it also serves as the default Locale country for the native executable
+ * application runtime.
* e.g. US or FR as defined by ISO 3166-1 alpha-2 codes.
*
*
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java
index 52b90f72ec07b..130991d72255c 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java
@@ -196,6 +196,7 @@ public static final class Version implements Comparable {
public static final Version VERSION_24_0_0 = new Version("GraalVM 24.0.0", "24.0.0", "22", Distribution.GRAALVM);
public static final Version VERSION_24_0_999 = new Version("GraalVM 24.0.999", "24.0.999", "22", Distribution.GRAALVM);
public static final Version VERSION_24_1_0 = new Version("GraalVM 24.1.0", "24.1.0", "23", Distribution.GRAALVM);
+ public static final Version VERSION_24_2_0 = new Version("GraalVM 24.2.0", "24.2.0", "24", Distribution.GRAALVM);
/**
* The minimum version of GraalVM supported by Quarkus.
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java
index c1f7f35c94252..7dd1ecdb2eaa8 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java
@@ -747,6 +747,14 @@ public NativeImageInvokerInfo build() {
}
}
+ if (!Locale.getDefault().equals(localesBuildTimeConfig.defaultLocale)
+ && graalVMVersion.compareTo(GraalVM.Version.VERSION_24_2_0) >= 0) {
+ log.warn(
+ "Your application is setting the 'quarkus.default-locale' configuration key. " +
+ "Starting with GraalVM/Mandrel for JDK 24 this configuration is being ignored and the " +
+ "default locale is always set at runtime based on the system default locale.");
+ }
+
final String userLanguage = LocaleProcessor.nativeImageUserLanguage(nativeConfig, localesBuildTimeConfig);
if (!userLanguage.isEmpty()) {
nativeImageArgs.add("-J-Duser.language=" + userLanguage);
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/LocaleProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/LocaleProcessor.java
index 8db1bafb7a109..608458d5fa505 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/steps/LocaleProcessor.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/LocaleProcessor.java
@@ -124,7 +124,7 @@ public static String nativeImageUserCountry(NativeConfig nativeConfig, LocalesBu
}
/**
- * Additional locales to be included in native-image executable.
+ * Locales to be included in native-image executable.
*
* @param nativeConfig
* @param localesBuildTimeConfig
@@ -139,17 +139,18 @@ public static String nativeImageIncludeLocales(NativeConfig nativeConfig, Locale
return "all";
}
- // We subtract what we already declare for native-image's user.language or user.country.
- // Note the deprecated options still count.
- additionalLocales.remove(localesBuildTimeConfig.defaultLocale);
+ // GraalVM for JDK 24 doesn't include the default locale used at build time. We must explicitly include the
+ // specified locales - including the build-time locale.
+ // Note the deprecated options still count and take precedence.
if (nativeConfig.userCountry().isPresent() && nativeConfig.userLanguage().isPresent()) {
- additionalLocales.remove(new Locale(nativeConfig.userLanguage().get(), nativeConfig.userCountry().get()));
+ additionalLocales.add(new Locale(nativeConfig.userLanguage().get(), nativeConfig.userCountry().get()));
} else if (nativeConfig.userLanguage().isPresent()) {
- additionalLocales.remove(new Locale(nativeConfig.userLanguage().get()));
+ additionalLocales.add(new Locale(nativeConfig.userLanguage().get()));
+ } else {
+ additionalLocales.add(localesBuildTimeConfig.defaultLocale);
}
return additionalLocales.stream()
- .filter(l -> !Locale.getDefault().equals(l))
.map(l -> l.getLanguage() + (l.getCountry().isEmpty() ? "" : "-" + l.getCountry()))
.collect(Collectors.joining(","));
}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/LocalesBuildTimeConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/LocalesBuildTimeConfig.java
index 7ca78d84ceeb0..56c0f62c007e7 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/LocalesBuildTimeConfig.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/LocalesBuildTimeConfig.java
@@ -32,8 +32,8 @@ public class LocalesBuildTimeConfig {
* A special string "all" is translated as ROOT Locale and then used in native-image
* to include all locales. Image size penalty applies.
*/
- @ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-"
- + DEFAULT_COUNTRY, defaultValueDocumentation = "Set containing the build system locale")
+ @ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-" + DEFAULT_COUNTRY,
+ defaultValueDocumentation = "Set containing the build system locale")
public Set locales;
/**
@@ -44,8 +44,11 @@ public class LocalesBuildTimeConfig {
* For instance, the Hibernate Validator extension makes use of it.
*
* Native-image build uses this property to derive {@code user.language} and {@code user.country} for the application's
- * runtime.
+ * runtime. Starting with GraalVM for JDK 24 this option will not result in setting the default runtime locale.
+ * Instead, the Java configuration {@code user.language} and {@code user.country} will be used as the
+ * default when running the native image.
*/
- @ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-" + DEFAULT_COUNTRY, defaultValueDocumentation = "Build system locale")
+ @ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-" + DEFAULT_COUNTRY,
+ defaultValueDocumentation = "Build system locale prior to GraalVM for JDK 24, or system default locale at image runtime with GraalVM for JDK 24 and better")
public Locale defaultLocale;
}
diff --git a/integration-tests/locales/default/pom.xml b/integration-tests/locales/default/pom.xml
new file mode 100644
index 0000000000000..2d635a1715b6b
--- /dev/null
+++ b/integration-tests/locales/default/pom.xml
@@ -0,0 +1,97 @@
+
+
+ 4.0.0
+
+ io.quarkus
+ quarkus-integration-test-locales-parent
+ 999-SNAPSHOT
+
+ quarkus-integration-test-locales-default
+ Quarkus - Integration Tests - Locales - Default
+
+
+ io.quarkus
+ quarkus-rest
+
+
+ io.quarkus
+ quarkus-hibernate-validator
+
+
+ io.quarkus
+ quarkus-integration-test-locales-app
+ ${project.version}
+
+
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+ io.quarkus
+ quarkus-rest-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+ io.quarkus
+ quarkus-hibernate-validator-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+
+
+
+
+ src/test/resources
+ true
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+
+
+
+ build
+
+
+
+
+
+ maven-surefire-plugin
+
+ 1
+ false
+
+
+
+
+
diff --git a/integration-tests/locales/default/src/main/java/io/quarkus/locales/it/DefaultLocaleResource.java b/integration-tests/locales/default/src/main/java/io/quarkus/locales/it/DefaultLocaleResource.java
new file mode 100644
index 0000000000000..3e01732b91e52
--- /dev/null
+++ b/integration-tests/locales/default/src/main/java/io/quarkus/locales/it/DefaultLocaleResource.java
@@ -0,0 +1,26 @@
+package io.quarkus.locales.it;
+
+import jakarta.validation.constraints.Pattern;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.logging.Logger;
+
+@Path("")
+public class DefaultLocaleResource extends LocalesResource {
+ private static final Logger LOG = Logger.getLogger(DefaultLocaleResource.class);
+
+ // @Pattern validation does nothing when placed in LocalesResource.
+ @GET
+ @Path("/hibernate-validator-test-validation-message-locale/{id}/")
+ @Produces(MediaType.TEXT_PLAIN)
+ public Response validationMessageLocale(
+ @Pattern(regexp = "A.*", message = "{pattern.message}") @PathParam("id") String id) {
+ LOG.infof("Triggering test: id: %s", id);
+ return Response.ok(id).build();
+ }
+}
diff --git a/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesIT.java b/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesIT.java
new file mode 100644
index 0000000000000..5f71f6d4e1c2c
--- /dev/null
+++ b/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesIT.java
@@ -0,0 +1,84 @@
+package io.quarkus.locales.it;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+
+import org.apache.http.HttpStatus;
+import org.jboss.logging.Logger;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import io.quarkus.test.junit.DisableIfBuiltWithGraalVMNewerThan;
+import io.quarkus.test.junit.DisableIfBuiltWithGraalVMOlderThan;
+import io.quarkus.test.junit.GraalVMVersion;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.restassured.RestAssured;
+
+/**
+ * For the Native test cases to function, the operating system has to have locales support installed. A barebone system with
+ * only C.UTF-8 default locale available won't be able to pass the tests.
+ *
+ * For example, this package satisfies the dependency on a RHEL 9 type of OS: glibc-all-langpacks
+ */
+@QuarkusIntegrationTest
+public class LocalesIT {
+
+ private static final Logger LOG = Logger.getLogger(LocalesIT.class);
+
+ @Test
+ @DisableIfBuiltWithGraalVMNewerThan(value = GraalVMVersion.GRAALVM_24_1_0)
+ public void testDefaultLocaleBefore24_2() {
+ RestAssured.given().when()
+ .get("/default/de-CH")
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ /*
+ * "l-Iżvizzera" is the correct name for Switzerland in Maltese language.
+ * Maltese is the default language as per quarkus.default-locale=mt-MT.
+ */
+ .body(is("l-Iżvizzera"))
+ .log().all();
+ }
+
+ @Test
+ @DisableIfBuiltWithGraalVMOlderThan(value = GraalVMVersion.GRAALVM_24_2_0)
+ public void testDefaultLocaleAfter24_1() {
+ RestAssured.given().when()
+ .get("/default/de-CH")
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ /*
+ * "l-Iżvizzera" is the correct name for Switzerland in Maltese language.
+ * Maltese is the default build-time language as per quarkus.default-locale=mt-MT, but not at run-time.
+ * Note that this test will fail if the default run-time language is Maltese on the test machine,
+ * this is unfortunate but also unlikely given the small population of Malta.
+ */
+ .body(not("l-Iżvizzera"))
+ .log().all();
+ }
+
+ /**
+ * @see integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java
+ */
+ @ParameterizedTest
+ @CsvSource(value = {
+ // French locale is included, so it's used, because Croatian locale is not included
+ // and thus its property file ValidationMessages_hr_HR.properties is ignored.
+ "en-US;q=0.25,hr-HR;q=0.9,fr-FR;q=0.5,uk-UA;q=0.1|La valeur ne correspond pas à l'échantillon",
+ // Silent fallback to lingua franca.
+ "invalid string|Value is not in line with the pattern",
+ // French locale is available and included.
+ "en-US;q=0.25,hr-HR;q=1,fr-FR;q=0.5|La valeur ne correspond pas à l'échantillon"
+ }, delimiter = '|')
+ public void testValidationMessageLocale(String acceptLanguage, String expectedMessage) {
+ RestAssured.given()
+ .header("Accept-Language", acceptLanguage)
+ .when()
+ .get("/hibernate-validator-test-validation-message-locale/1")
+ .then()
+ .body(containsString(expectedMessage));
+ }
+
+}
diff --git a/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesTest.java b/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesTest.java
new file mode 100644
index 0000000000000..bd42cd4ae7fe7
--- /dev/null
+++ b/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesTest.java
@@ -0,0 +1,7 @@
+package io.quarkus.locales.it;
+
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+public class LocalesTest {
+}
diff --git a/integration-tests/locales/default/src/test/resources/ValidationMessages.properties b/integration-tests/locales/default/src/test/resources/ValidationMessages.properties
new file mode 100644
index 0000000000000..48a3bbf23dce1
--- /dev/null
+++ b/integration-tests/locales/default/src/test/resources/ValidationMessages.properties
@@ -0,0 +1 @@
+pattern.message=Value is not in line with the pattern
diff --git a/integration-tests/locales/default/src/test/resources/ValidationMessages_fr_FR.properties b/integration-tests/locales/default/src/test/resources/ValidationMessages_fr_FR.properties
new file mode 100644
index 0000000000000..c9a6e5d71d6f0
--- /dev/null
+++ b/integration-tests/locales/default/src/test/resources/ValidationMessages_fr_FR.properties
@@ -0,0 +1 @@
+pattern.message=La valeur ne correspond pas à l'échantillon
diff --git a/integration-tests/locales/default/src/test/resources/ValidationMessages_hr_HR.properties b/integration-tests/locales/default/src/test/resources/ValidationMessages_hr_HR.properties
new file mode 100644
index 0000000000000..ae2e444a98105
--- /dev/null
+++ b/integration-tests/locales/default/src/test/resources/ValidationMessages_hr_HR.properties
@@ -0,0 +1 @@
+pattern.message=Vrijednost ne zadovoljava uzorak
diff --git a/integration-tests/locales/default/src/test/resources/application.properties b/integration-tests/locales/default/src/test/resources/application.properties
new file mode 100644
index 0000000000000..d0d560bcb8336
--- /dev/null
+++ b/integration-tests/locales/default/src/test/resources/application.properties
@@ -0,0 +1,4 @@
+quarkus.locales=de,fr-FR,ja,uk-UA
+# Note that quarkus.native.user-language is deprecated and solely quarkus.default-locale should be
+# used in your application properties. This test uses it only to verify compatibility.
+quarkus.default-locale=mt-MT
diff --git a/integration-tests/locales/pom.xml b/integration-tests/locales/pom.xml
index 0478b1166eef5..08dc1763e4ba0 100644
--- a/integration-tests/locales/pom.xml
+++ b/integration-tests/locales/pom.xml
@@ -14,6 +14,7 @@
app
all
+ default
some
diff --git a/integration-tests/locales/some/src/test/java/io/quarkus/locales/it/LocalesIT.java b/integration-tests/locales/some/src/test/java/io/quarkus/locales/it/LocalesIT.java
index c0958e0a6a98b..dab72bc4f499b 100644
--- a/integration-tests/locales/some/src/test/java/io/quarkus/locales/it/LocalesIT.java
+++ b/integration-tests/locales/some/src/test/java/io/quarkus/locales/it/LocalesIT.java
@@ -11,6 +11,7 @@
import org.junit.jupiter.params.provider.CsvSource;
import io.quarkus.test.junit.DisableIfBuiltWithGraalVMNewerThan;
+import io.quarkus.test.junit.DisableIfBuiltWithGraalVMOlderThan;
import io.quarkus.test.junit.GraalVMVersion;
import io.quarkus.test.junit.QuarkusIntegrationTest;
import io.restassured.RestAssured;
@@ -82,11 +83,9 @@ public void testTimeZones(String zone, String language, String name) {
.log().all();
}
- // Disable test with GraalVM 24.2 for JDK 24 and later till we reach a conclusion in
- // https://github.com/quarkusio/quarkus/discussions/43533
@Test
@DisableIfBuiltWithGraalVMNewerThan(value = GraalVMVersion.GRAALVM_24_1_0)
- public void testDefaultLocale() {
+ public void testDefaultLocaleBefore24_2() {
RestAssured.given().when()
.get("/default/de-CH")
.then()
@@ -99,6 +98,21 @@ public void testDefaultLocale() {
.log().all();
}
+ @Test
+ @DisableIfBuiltWithGraalVMOlderThan(value = GraalVMVersion.GRAALVM_24_2_0)
+ public void testDefaultLocaleAfter24_1() {
+ RestAssured.given().when()
+ .get("/default/de-CH")
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ /*
+ * "Schweiz" is the correct name for Switzerland in German.
+ * German is the default language as per the `quarkus.test.arg-line` in application.properties.
+ */
+ .body(is("Schweiz"))
+ .log().all();
+ }
+
@Test
public void testMissingLocaleSorryItaly() {
RestAssured.given().when()
diff --git a/integration-tests/locales/some/src/test/resources/application.properties b/integration-tests/locales/some/src/test/resources/application.properties
index 4d3a429e5788c..4617c6481b6d1 100644
--- a/integration-tests/locales/some/src/test/resources/application.properties
+++ b/integration-tests/locales/some/src/test/resources/application.properties
@@ -3,3 +3,4 @@ quarkus.locales=de,fr-FR,ja,uk-UA
# used in your application properties. This test uses it only to verify compatibility.
quarkus.native.user-language=cs
quarkus.default-locale=en-US
+quarkus.test.arg-line=-Duser.language=de
\ No newline at end of file
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/GraalVMVersion.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/GraalVMVersion.java
index 7110ce6c275b3..0b06c693126cc 100644
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/GraalVMVersion.java
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/GraalVMVersion.java
@@ -7,7 +7,8 @@ public enum GraalVMVersion {
GRAALVM_23_1_3(GraalVM.Version.VERSION_23_1_3),
GRAALVM_24_0_0(GraalVM.Version.VERSION_24_0_0),
GRAALVM_24_0_999(GraalVM.Version.VERSION_24_0_999),
- GRAALVM_24_1_0(GraalVM.Version.VERSION_24_1_0);
+ GRAALVM_24_1_0(GraalVM.Version.VERSION_24_1_0),
+ GRAALVM_24_2_0(GraalVM.Version.VERSION_24_2_0);
private final GraalVM.Version version;