GraalVM Native Image: How to Include Full Locale Data for Date/Time Formatting?
I’m experiencing an issue with GraalVM native-image where localized date/time formatting doesn’t work correctly for certain locales. Specifically, when formatting dates in Arabic, the output falls back to English abbreviations instead of showing the full Arabic text.
Problem Description
When running the same Java code in JVM mode, I get the expected output:
arabicGregorianDate = السبت 18\10\2025 م
englishGregorianDate = Saturday 18/10/2025 AD
However, when compiling to GraalVM native-image and running it, the output is:
arabicGregorianDate = Sat 18\10\2025 CE
englishGregorianDate = Saturday 18/10/2025 AD
Notice that:
- The Arabic word for Saturday (السبت) is fallbacked to “Sat”
- The Arabic era marker (م) is fallbacked to “CE”
Code Sample
final var ARABIC_GREGORIAN_DATE_FORMATTER = DateTimeFormatter.ofPattern("EEEE dd\\MM\\yyyy G").localizedBy(Locale.forLanguageTag("ar"));
final var ENGLISH_GREGORIAN_DATE_FORMATTER = DateTimeFormatter.ofPattern("EEEE dd/MM/yyyy G").localizedBy(Locale.ENGLISH);
var now = Instant.now();
String arabicGregorianDate = ARABIC_GREGORIAN_DATE_FORMATTER.format(now.atZone(ZoneId.systemDefault()));
String englishGregorianDate = ENGLISH_GREGORIAN_DATE_FORMATTER.format(now.atZone(ZoneId.systemDefault()));
Log.info("arabicGregorianDate = " + arabicGregorianDate);
Log.info("englishGregorianDate = " + englishGregorianDate);
Current Configuration
I’m already using the following configuration:
-H:IncludeLocales=ar,en
I’ve also tried using the GraalVM tracing agent, but it didn’t fix the issue.
Question
How can I configure GraalVM native-image to include the necessary locale data (possibly CLDR data) so that Arabic date formatting works correctly?
A reproducer is available at: https://github.com/Eng-Fouad/graal-native-date-locale
GraalVM Native Image has specific limitations regarding locale data inclusion that cause the Arabic date formatting to fall back to English. To resolve this, you need to explicitly include the required locale resources using GraalVM’s native image configuration options and ensure proper CLDR data is bundled with your application.
Contents
- Understanding GraalVM Locale Limitations
- Configuration Options for Locale Data
- Step-by-Step Solutions
- Alternative Approaches
- Best Practices and Considerations
- Debugging and Verification
Understanding GraalVM Locale Limitations
GraalVM Native Image operates with a different approach to resource handling compared to traditional JVM execution. By default, it includes only the minimal set of locale data required for the application to function, which often results in incomplete locale support.
According to the GitHub issue on Graal locale support, as of GraalVM 20.2.0, native executables contain only the single locale that has been returned by Locale.getDefault() in the building JVM. This limitation affects date/time formatting, number formatting, and other locale-sensitive operations.
The core of the issue lies in how GraalVM handles locale resources:
- Native Image performs static analysis during build time
- It only includes resources that are statically reachable
- Locale data is considered a resource that must be explicitly included
- The Common Locale Data Repository (CLDR) data is not automatically included
Key Insight: The CLDR data maintained by the Unicode Consortium provides locale data of higher quality than the legacy data in JDK 8, as mentioned in JEP 252: Use CLDR Locale Data by Default.
Configuration Options for Locale Data
GraalVM provides several configuration options to include locale data in native images:
1. Include Specific Locales
The -H:IncludeLocales option allows you to specify which locales to include. However, as you’ve discovered, this might not be sufficient for date formatting:
-H:IncludeLocales=ar,en
The format for locale tags follows the IETF BCP 47 standard. For Arabic, you can use:
ar- general Arabicar-SA- Saudi Arabia specific Arabicar-EG- Egypt specific Arabic
2. Include All Locales
For comprehensive locale support, you can use:
-H:+IncludeAllLocales
As noted in the GraalVM documentation on resources, this option includes all locales but significantly increases the size of the resulting executable.
3. Resource Configuration
You can also configure resources through a JSON configuration file:
{
"resources": {
"includes": [
"META-INF/**"
],
"bundles": [
"java.text.resources.*"
]
}
}
Step-by-Step Solutions
Solution 1: Enhanced Locale Configuration
Try a more comprehensive locale specification:
-H:IncludeLocales=ar,en,ar-SA,ar-EG
This ensures that various Arabic locale variants are included.
Solution 2: Using the Tracing Agent Properly
The tracing agent needs to be run with your actual application code that exercises the locale formatting. Here’s the proper approach:
- Run your application with the tracing agent:
java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image \
-jar your-application.jar
-
Ensure you actually trigger the Arabic date formatting during this run
-
Rebuild your native image with the generated configuration:
native-image --config-dir=src/main/resources/META-INF/native-image -H:IncludeLocales=ar,en \
-jar your-application.jar
Solution 3: Resource Bundle Configuration
Create a resource configuration file (src/main/resources/META-INF/native-image/resource-config.json):
{
"resources": {
"includes": [
"**/*.json",
"**/*.properties"
],
"bundles": [
"java.text.resources.DateFormatData",
"java.text.resources.DateFormatData_en",
"java.text.resources.DateFormatData_ar"
]
}
}
Then build with:
native-image -H:ConfigurationFileDirectories=src/main/resources/META-INF/native-image \
-H:IncludeLocales=ar,en -jar your-application.jar
Solution 4: Using Build Time Configuration
For Quarkus applications, you can use the @AutomaticFeature approach mentioned in the Stack Overflow discussion:
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void configureNativeImage(NativeImageBuildItem nativeImageBuildItem) {
nativeImageBuildItem.getBuildArgs()
.add("--enable-url-protocols=http,https")
.add("-H:IncludeLocales=ar,en");
}
Alternative Approaches
1. Custom Locale Data Implementation
If the built-in locale data doesn’t suffice, you can implement a custom solution:
public class ArabicDateFormatter {
private static final Map<String, String> ARABIC_DAY_NAMES = Map.of(
"Saturday", "السبت",
"Sunday", "الأحد",
// ... other days
);
private static final Map<String, String> ARABIC_ERA_NAMES = Map.of(
"AD", "م",
"CE", "م"
);
public static String formatArabicDate(Instant instant) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEEE dd\\MM\\yyyy G")
.withLocale(Locale.ENGLISH);
String formatted = formatter.format(instant.atZone(ZoneId.systemDefault()));
// Replace English names with Arabic
for (Map.Entry<String, String> entry : ARABIC_DAY_NAMES.entrySet()) {
formatted = formatted.replace(entry.getKey(), entry.getValue());
}
for (Map.Entry<String, String> entry : ARABIC_ERA_NAMES.entrySet()) {
formatted = formatted.replace(entry.getKey(), entry.getValue());
}
return formatted;
}
}
2. External Locale Resources
Bundle the required locale resources as external files and load them at runtime:
// Load locale resources from external file
private static void loadLocaleResources() {
try (InputStream is = ArabicLocaleSupport.class.getResourceAsStream("/locales/arabic-date-format.properties")) {
if (is != null) {
// Load and configure locale data
}
} catch (IOException e) {
throw new RuntimeException("Failed to load Arabic locale resources", e);
}
}
Best Practices and Considerations
1. Locale Testing Strategy
Always test your application with the target locales before deploying:
public class LocaleTest {
@Test
void testArabicDateFormatting() {
Locale arabicLocale = Locale.forLanguageTag("ar-SA");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEEE dd\\MM\\yyyy G")
.localizedBy(arabicLocale);
String result = formatter.format(Instant.now().atZone(ZoneId.systemDefault()));
assertTrue(result.contains("السبت"), "Should contain Arabic Saturday");
assertTrue(result.contains("م"), "Should contain Arabic era marker");
}
}
2. Build Configuration Optimization
Balance between locale support and executable size:
# For production builds with specific locales
native-image -H:IncludeLocales=ar,en,ar-SA,ar-EG,fr,de \
--no-fallback -jar your-application.jar
# For development with all locales
native-image -H:+IncludeAllLocales \
-jar your-application-dev.jar
3. Runtime Locale Detection
Implement runtime locale detection as a fallback:
public static DateTimeFormatter createLocalizedFormatter(String languageTag) {
try {
Locale locale = Locale.forLanguageTag(languageTag);
return DateTimeFormatter.ofPattern("EEEE dd\\MM\\yyyy G").localizedBy(locale);
} catch (Exception e) {
// Fallback to English
return DateTimeFormatter.ofPattern("EEEE dd\\MM\\yyyy G").localizedBy(Locale.ENGLISH);
}
}
Debugging and Verification
1. Verify Locale Inclusion
Check which locales are actually included in your native image:
import java.text.spi.DateFormatProvider;
import java.util.Locale;
public class LocaleDebugger {
public static void debugLocales() {
Locale[] availableLocales = Locale.getAvailableLocales();
System.out.println("Available locales: " + availableLocales.length);
for (Locale locale : availableLocales) {
if (locale.getLanguage().equals("ar")) {
System.out.println("Arabic locale found: " + locale);
}
}
// Test date formatting
testDateFormatting(Locale.forLanguageTag("ar"));
}
private static void testDateFormatting(Locale locale) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEEE dd\\MM\\yyyy G")
.localizedBy(locale);
System.out.println("Formatted date in " + locale + ": " +
formatter.format(Instant.now().atZone(ZoneId.systemDefault())));
}
}
2. Native Image Analysis
Use GraalVM’s tools to analyze your native image:
# Analyze native image resources
native-image -H:+PrintAnalysisResources -jar your-application.jar
# Check for missing resources
native-image -H:+ReportExceptionStackTraces -jar your-application.jar
3. Reproduce the Issue
Based on your GitHub repository, here’s a minimal reproduction setup:
public class ArabicDateReproducer {
public static void main(String[] args) {
final var ARABIC_GREGORIAN_DATE_FORMATTER = DateTimeFormatter.ofPattern("EEEE dd\\MM\\yyyy G")
.localizedBy(Locale.forLanguageTag("ar"));
final var ENGLISH_GREGORIAN_DATE_FORMATTER = DateTimeFormatter.ofPattern("EEEE dd/MM/yyyy G")
.localizedBy(Locale.ENGLISH);
var now = Instant.now();
String arabicGregorianDate = ARABIC_GREGORIAN_DATE_FORMATTER.format(now.atZone(ZoneId.systemDefault()));
String englishGregorianDate = ENGLISH_GREGORIAN_DATE_FORMATTER.format(now.atZone(ZoneId.systemDefault()));
System.out.println("arabicGregorianDate = " + arabicGregorianDate);
System.out.println("englishGregorianDate = " + englishGregorianDate);
}
}
Build and test with:
# JVM mode (should work correctly)
java ArabicDateReproducer.java
# Native mode (may have issues)
native-image ArabicDateReproducer
./ArabicDateReproducer
Conclusion
-
Core Issue: GraalVM Native Image doesn’t automatically include full locale data, causing Arabic date formatting to fall back to English abbreviations.
-
Primary Solutions: Use
-H:IncludeLocales=ar,enwith comprehensive locale tags, or-H:+IncludeAllLocalesfor complete locale support. -
Advanced Configuration: Implement resource configuration files and proper tracing agent usage to ensure all required locale resources are included.
-
Testing Strategy: Always test with target locales before deployment and implement runtime fallbacks for edge cases.
-
Performance Trade-off: Including all locales increases executable size, so balance between functionality and resource usage based on your application’s requirements.
For the most robust solution, combine multiple approaches: specify exact locale requirements, use the tracing agent to capture missing resources, and implement custom fallback mechanisms for production environments.
Sources
- GraalVM Native Image Resources Documentation
- GitHub Issue: Support for non-default locale
- JEP 252: Use CLDR Locale Data by Default
- Stack Overflow: Quarkus GraalVM native image DateTimeFormatter and Localization
- Stack Overflow: GraalVM native-image Java i18n Locale problem
- GraalVM Native Image Build Configuration