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 21113760e3c4b..89c620d6f7c45 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
@@ -191,6 +191,8 @@ public static final class Version extends io.quarkus.runtime.graal.GraalVM.Versi
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_1_999 = new Version("GraalVM 24.1.999", "24.1.999", "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 287b61399b985..29a287fac687f 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
@@ -746,15 +746,6 @@ public NativeImageInvokerInfo build() {
}
}
}
-
- final String userLanguage = LocaleProcessor.nativeImageUserLanguage(nativeConfig, localesBuildTimeConfig);
- if (!userLanguage.isEmpty()) {
- nativeImageArgs.add("-J-Duser.language=" + userLanguage);
- }
- final String userCountry = LocaleProcessor.nativeImageUserCountry(nativeConfig, localesBuildTimeConfig);
- if (!userCountry.isEmpty()) {
- nativeImageArgs.add("-J-Duser.country=" + userCountry);
- }
final String includeLocales = LocaleProcessor.nativeImageIncludeLocales(nativeConfig, localesBuildTimeConfig);
if (!includeLocales.isEmpty()) {
if ("all".equals(includeLocales)) {
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..ba3062e7ae52e 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
@@ -14,6 +14,7 @@
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.pkg.NativeConfig;
import io.quarkus.deployment.pkg.steps.NativeBuild;
@@ -59,6 +60,23 @@ void servicesResource(BuildProducer nativeImageRes
"sun.util.resources.provider.LocaleDataProvider".getBytes(StandardCharsets.UTF_8)));
}
+ /**
+ * These exports are only required for GraalVM for JDK < 24, but don't cause any issues for newer versions.
+ * To be removed once we drop support for GraalVM for JDK < 24.
+ */
+ @BuildStep(onlyIf = NativeBuild.class)
+ void setDefaults(BuildProducer buildtimeSystemProperties,
+ NativeConfig nativeConfig, LocalesBuildTimeConfig localesBuildTimeConfig) {
+ String language = nativeImageUserLanguage(nativeConfig, localesBuildTimeConfig);
+ if (!language.isEmpty()) {
+ buildtimeSystemProperties.produce(new NativeImageSystemPropertyBuildItem("user.language", language));
+ }
+ String country = nativeImageUserCountry(nativeConfig, localesBuildTimeConfig);
+ if (!country.isEmpty()) {
+ buildtimeSystemProperties.produce(new NativeImageSystemPropertyBuildItem("user.country", country));
+ }
+ }
+
/**
* We activate additional resources in native-image executable only if user opts
* for anything else than what is already the system default.
@@ -80,7 +98,8 @@ public boolean getAsBoolean() {
(nativeConfig.userCountry().isPresent()
&& !Locale.getDefault().getCountry().equals(nativeConfig.userCountry().get()))
||
- !Locale.getDefault().equals(localesBuildTimeConfig.defaultLocale)
+ (localesBuildTimeConfig.defaultLocale.isPresent() &&
+ !Locale.getDefault().equals(localesBuildTimeConfig.defaultLocale.get()))
||
localesBuildTimeConfig.locales.stream().anyMatch(l -> !Locale.getDefault().equals(l));
}
@@ -93,9 +112,14 @@ public boolean getAsBoolean() {
* @param localesBuildTimeConfig
* @return User language set by 'quarkus.default-locale' or by deprecated 'quarkus.native.user-language' or
* effectively LocalesBuildTimeConfig.DEFAULT_LANGUAGE if none of the aforementioned is set.
+ * @Deprecated
*/
+ @Deprecated
public static String nativeImageUserLanguage(NativeConfig nativeConfig, LocalesBuildTimeConfig localesBuildTimeConfig) {
- String language = localesBuildTimeConfig.defaultLocale.getLanguage();
+ String language = System.getProperty("user.language", "en");
+ if (localesBuildTimeConfig.defaultLocale.isPresent()) {
+ language = localesBuildTimeConfig.defaultLocale.get().getLanguage();
+ }
if (nativeConfig.userLanguage().isPresent()) {
log.warn(DEPRECATED_USER_LANGUAGE_WARNING);
// The deprecated option takes precedence for users who are already using it.
@@ -112,9 +136,14 @@ public static String nativeImageUserLanguage(NativeConfig nativeConfig, LocalesB
* @return User country set by 'quarkus.default-locale' or by deprecated 'quarkus.native.user-country' or
* effectively LocalesBuildTimeConfig.DEFAULT_COUNTRY (could be an empty string) if none of the aforementioned is
* set.
+ * @Deprecated
*/
+ @Deprecated
public static String nativeImageUserCountry(NativeConfig nativeConfig, LocalesBuildTimeConfig localesBuildTimeConfig) {
- String country = localesBuildTimeConfig.defaultLocale.getCountry();
+ String country = System.getProperty("user.country", "");
+ if (localesBuildTimeConfig.defaultLocale.isPresent()) {
+ country = localesBuildTimeConfig.defaultLocale.get().getCountry();
+ }
if (nativeConfig.userCountry().isPresent()) {
log.warn(DEPRECATED_USER_COUNTRY_WARNING);
// The deprecated option takes precedence for users who are already using it.
@@ -124,7 +153,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 +168,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 if set by the user.
+ // 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 if (localesBuildTimeConfig.defaultLocale.isPresent()) {
+ additionalLocales.add(localesBuildTimeConfig.defaultLocale.get());
}
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/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageFeatureStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageFeatureStep.java
index 97ade7fea8806..057d5d3ee9c02 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageFeatureStep.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageFeatureStep.java
@@ -9,6 +9,7 @@
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
+import org.graalvm.nativeimage.hosted.RuntimeSystemProperties;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
@@ -18,6 +19,9 @@
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedPackageBuildItem;
import io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.UnsafeAccessedFieldBuildItem;
+import io.quarkus.deployment.pkg.NativeConfig;
+import io.quarkus.gizmo.BranchResult;
+import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.CatchBlockCreator;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
@@ -25,6 +29,7 @@
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.gizmo.TryBlock;
+import io.quarkus.runtime.LocalesBuildTimeConfig;
import io.quarkus.runtime.graal.GraalVM;
public class NativeImageFeatureStep {
@@ -35,6 +40,12 @@ public class NativeImageFeatureStep {
Class.class);
private static final MethodDescriptor BUILD_TIME_INITIALIZATION = ofMethod(RuntimeClassInitialization.class,
"initializeAtBuildTime", void.class, String[].class);
+ private static final MethodDescriptor REGISTER_RUNTIME_SYSTEM_PROPERTIES = ofMethod(RuntimeSystemProperties.class,
+ "register", void.class, String.class, String.class);
+ private static final MethodDescriptor GRAALVM_VERSION_GET_CURRENT = ofMethod(GraalVM.Version.class, "getCurrent",
+ GraalVM.Version.class);
+ private static final MethodDescriptor GRAALVM_VERSION_COMPARE_TO = ofMethod(GraalVM.Version.class, "compareTo", int.class,
+ int[].class);
private static final MethodDescriptor INITIALIZE_CLASSES_AT_RUN_TIME = ofMethod(RuntimeClassInitialization.class,
"initializeAtRunTime", void.class, Class[].class);
private static final MethodDescriptor INITIALIZE_PACKAGES_AT_RUN_TIME = ofMethod(RuntimeClassInitialization.class,
@@ -58,11 +69,12 @@ void addExportsToNativeImage(BuildProducer features) {
@BuildStep
void generateFeature(BuildProducer nativeImageClass,
- BuildProducer exports,
List runtimeInitializedClassBuildItems,
List runtimeInitializedPackageBuildItems,
List runtimeReinitializedClassBuildItems,
- List unsafeAccessedFields) {
+ List unsafeAccessedFields,
+ NativeConfig nativeConfig,
+ LocalesBuildTimeConfig localesBuildTimeConfig) {
ClassCreator file = new ClassCreator(new ClassOutput() {
@Override
public void write(String s, byte[] bytes) {
@@ -81,6 +93,38 @@ public void write(String s, byte[] bytes) {
overallCatch.invokeStaticMethod(BUILD_TIME_INITIALIZATION,
overallCatch.marshalAsArray(String.class, overallCatch.load(""))); // empty string means initialize everything
+ // Set the user.language and user.country system properties to the default locale
+ // The deprecated option takes precedence for users who are already using it.
+ if (nativeConfig.userLanguage().isPresent()) {
+ overallCatch.invokeStaticMethod(REGISTER_RUNTIME_SYSTEM_PROPERTIES,
+ overallCatch.load("user.language"), overallCatch.load(nativeConfig.userLanguage().get()));
+ if (nativeConfig.userCountry().isPresent()) {
+ overallCatch.invokeStaticMethod(REGISTER_RUNTIME_SYSTEM_PROPERTIES,
+ overallCatch.load("user.country"), overallCatch.load(nativeConfig.userCountry().get()));
+ }
+ } else if (localesBuildTimeConfig.defaultLocale.isPresent()) {
+ overallCatch.invokeStaticMethod(REGISTER_RUNTIME_SYSTEM_PROPERTIES,
+ overallCatch.load("user.language"),
+ overallCatch.load(localesBuildTimeConfig.defaultLocale.get().getLanguage()));
+ overallCatch.invokeStaticMethod(REGISTER_RUNTIME_SYSTEM_PROPERTIES,
+ overallCatch.load("user.country"),
+ overallCatch.load(localesBuildTimeConfig.defaultLocale.get().getCountry()));
+ } else {
+ ResultHandle graalVMVersion = overallCatch.invokeStaticMethod(GRAALVM_VERSION_GET_CURRENT);
+ BranchResult graalVm24_2Test = overallCatch
+ .ifGreaterEqualZero(overallCatch.invokeVirtualMethod(GRAALVM_VERSION_COMPARE_TO, graalVMVersion,
+ overallCatch.marshalAsArray(int.class, overallCatch.load(24), overallCatch.load(2))));
+ /* GraalVM >= 24.2 */
+ try (BytecodeCreator greaterEqual24_2 = graalVm24_2Test.trueBranch()) {
+ greaterEqual24_2.invokeStaticMethod(REGISTER_RUNTIME_SYSTEM_PROPERTIES,
+ greaterEqual24_2.load("user.language"),
+ greaterEqual24_2.load("en"));
+ greaterEqual24_2.invokeStaticMethod(REGISTER_RUNTIME_SYSTEM_PROPERTIES,
+ greaterEqual24_2.load("user.country"),
+ greaterEqual24_2.load("US"));
+ }
+ }
+
if (!runtimeInitializedClassBuildItems.isEmpty()) {
// Class[] runtimeInitializedClasses()
MethodCreator runtimeInitializedClasses = file
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..1afd1e0a80742 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/LocalesBuildTimeConfig.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/LocalesBuildTimeConfig.java
@@ -1,6 +1,7 @@
package io.quarkus.runtime;
import java.util.Locale;
+import java.util.Optional;
import java.util.Set;
import io.quarkus.runtime.annotations.ConfigDocPrefix;
@@ -44,8 +45,10 @@ 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 {@code user.language} and {@code user.country} can also be overridden at
+ * runtime, provided the selected locale was included at image build time.
*/
- @ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-" + DEFAULT_COUNTRY, defaultValueDocumentation = "Build system locale")
- public Locale defaultLocale;
+ @ConfigItem(defaultValueDocumentation = "Defaults to the JVM's default locale if not set. "
+ + "Starting with GraalVM for JDK 24, it defaults to en-US for native executables.")
+ public Optional defaultLocale;
}
diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java
index baaafa562bfec..90a06fc13544a 100644
--- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java
+++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java
@@ -86,7 +86,7 @@ public void created(BeanContainer container) {
// Locales, Locale ROOT means all locales in this setting.
.locales(localesBuildTimeConfig.locales.contains(Locale.ROOT) ? Set.of(Locale.getAvailableLocales())
: localesBuildTimeConfig.locales)
- .defaultLocale(localesBuildTimeConfig.defaultLocale)
+ .defaultLocale(localesBuildTimeConfig.defaultLocale.orElse(Locale.getDefault()))
.beanMetaDataClassNormalizer(new ArcProxyBeanMetaDataClassNormalizer());
if (hibernateValidatorBuildTimeConfig.expressionLanguage().constraintExpressionFeatureLevel().isPresent()) {
diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java
index 8b3af1267819e..f67dd11dbc181 100644
--- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java
+++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java
@@ -18,6 +18,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
+import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -1417,7 +1418,7 @@ private String getDefaultLocale(AnnotationInstance bundleAnnotation, LocalesBuil
AnnotationValue localeValue = bundleAnnotation.value(BUNDLE_LOCALE);
String defaultLocale;
if (localeValue == null || localeValue.asString().equals(MessageBundle.DEFAULT_LOCALE)) {
- defaultLocale = locales.defaultLocale.toLanguageTag();
+ defaultLocale = locales.defaultLocale.orElse(Locale.getDefault()).toLanguageTag();
} else {
defaultLocale = localeValue.asString();
}
diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java
index f6bbc3957396d..ccf810e0da184 100644
--- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java
+++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/EngineProducer.java
@@ -97,7 +97,7 @@ public EngineProducer(QuteContext context, QuteConfig config, QuteRuntimeConfig
this.templateContents = Map.copyOf(context.getTemplateContents());
this.tags = context.getTags();
this.templatePathExclude = config.templatePathExclude;
- this.defaultLocale = locales.defaultLocale;
+ this.defaultLocale = locales.defaultLocale.orElse(Locale.getDefault());
this.defaultCharset = config.defaultCharset;
this.container = Arc.container();
diff --git a/integration-tests/locales/all/src/test/java/io/quarkus/locales/it/LocalesIT.java b/integration-tests/locales/all/src/test/java/io/quarkus/locales/it/LocalesIT.java
index 2fba39329a6ea..b08dd85b66ef4 100644
--- a/integration-tests/locales/all/src/test/java/io/quarkus/locales/it/LocalesIT.java
+++ b/integration-tests/locales/all/src/test/java/io/quarkus/locales/it/LocalesIT.java
@@ -4,12 +4,17 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
+import java.util.Locale;
+
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;
@@ -125,4 +130,31 @@ public void testValidationMessageLocale(String acceptLanguage, String expectedMe
.then()
.body(containsString(expectedMessage));
}
+
+ // This test works best in a non-english locale.
+ @Test
+ @DisableIfBuiltWithGraalVMNewerThan(value = GraalVMVersion.GRAALVM_24_1_999)
+ public void testDefaultLocalePre24_2() {
+ RestAssured.given().when()
+ .get("/default/de-CH")
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ // With GraalVM < 24.2, the default locale is picked up by the build system when not set by the user.
+ .body(is(Locale.forLanguageTag("de-CH").getDisplayCountry()))
+ .log().all();
+ }
+
+ // This test works best in a non-english locale.
+ @Test
+ @DisableIfBuiltWithGraalVMOlderThan(value = GraalVMVersion.GRAALVM_24_2_0)
+ public void testDefaultLocalePost24_1() {
+ RestAssured.given().when()
+ .get("/default/de-CH")
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ // Starting with GraalVM 24.2, the default locale is en-US when not set by the user.
+ .body(is("Switzerland"))
+ .log().all();
+ }
+
}
diff --git a/integration-tests/locales/all/src/test/resources/application.properties b/integration-tests/locales/all/src/test/resources/application.properties
index acc6621c863f2..3344cc17ceb05 100644
--- a/integration-tests/locales/all/src/test/resources/application.properties
+++ b/integration-tests/locales/all/src/test/resources/application.properties
@@ -1,2 +1,3 @@
quarkus.locales=all
quarkus.native.resources.includes=AppMessages_*.properties
+quarkus.test.env.LC_ALL=mt_MT.UTF-8
\ No newline at end of file
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..84a16611fef28
--- /dev/null
+++ b/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesIT.java
@@ -0,0 +1,59 @@
+package io.quarkus.locales.it;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+
+import org.apache.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+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 {
+
+ @Test
+ public void testDefaultLocale() {
+ 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();
+ }
+
+ /**
+ * @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 4dad3f2d0a8fc..3f062fcd844ae 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;
@@ -43,9 +44,6 @@ public void testCorrectLocales(String country, String language, String translati
.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
- @DisableIfBuiltWithGraalVMNewerThan(value = GraalVMVersion.GRAALVM_24_1_0)
@ParameterizedTest
@CsvSource(value = {
"en-US|en|US Dollar",
@@ -65,9 +63,6 @@ public void testCurrencies(String country, String language, String currency) {
.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
- @DisableIfBuiltWithGraalVMNewerThan(value = GraalVMVersion.GRAALVM_24_1_0)
@ParameterizedTest
@CsvSource(value = {
"Asia/Tokyo|fr|heure normale du Japon",
@@ -88,16 +83,15 @@ 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() {
+ @DisableIfBuiltWithGraalVMNewerThan(value = GraalVMVersion.GRAALVM_24_1_999)
+ public void testDefaultLocalePre24_2() {
RestAssured.given().when()
.get("/default/de-CH")
.then()
.statusCode(HttpStatus.SC_OK)
/*
+ * Prior to GraalVM 24.2, the locale could not be changed at runtime.
* "Švýcarsko" is the correct name for Switzerland in Czech language.
* Czech is the default language as per quarkus.native.user-language=cs.
*/
@@ -105,6 +99,22 @@ public void testDefaultLocale() {
.log().all();
}
+ @Test
+ @DisableIfBuiltWithGraalVMOlderThan(value = GraalVMVersion.GRAALVM_24_2_0)
+ public void testDefaultLocalePost24_1() {
+ RestAssured.given().when()
+ .get("/default/de-CH")
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ /*
+ * Starting with GraalVM 24.2, the locale can be set at runtime.
+ * "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..d5f80d916383d 100644
--- a/integration-tests/locales/some/src/test/resources/application.properties
+++ b/integration-tests/locales/some/src/test/resources/application.properties
@@ -3,3 +3,5 @@ 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
+quarkus.test.env.LC_ALL=mt_MT.UTF-8
\ 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..7a647525390d7 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,9 @@ 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_1_999(GraalVM.Version.VERSION_24_1_999),
+ GRAALVM_24_2_0(GraalVM.Version.VERSION_24_2_0);
private final GraalVM.Version version;