NeuroAgent

GraalVM Native Image: Include Full Locale Data for Dates

Learn how to configure GraalVM Native Image to include full locale data for proper Arabic date/time formatting. Fix fallback issues with CLDR data and comprehensive locale configuration.

Question

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

java
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

NeuroAgent

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

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:

bash
-H:IncludeLocales=ar,en

The format for locale tags follows the IETF BCP 47 standard. For Arabic, you can use:

  • ar - general Arabic
  • ar-SA - Saudi Arabia specific Arabic
  • ar-EG - Egypt specific Arabic

2. Include All Locales

For comprehensive locale support, you can use:

bash
-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:

json
{
  "resources": {
    "includes": [
      "META-INF/**"
    ],
    "bundles": [
      "java.text.resources.*"
    ]
  }
}

Step-by-Step Solutions

Solution 1: Enhanced Locale Configuration

Try a more comprehensive locale specification:

bash
-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:

  1. Run your application with the tracing agent:
bash
java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image \
     -jar your-application.jar
  1. Ensure you actually trigger the Arabic date formatting during this run

  2. Rebuild your native image with the generated configuration:

bash
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):

json
{
  "resources": {
    "includes": [
      "**/*.json",
      "**/*.properties"
    ],
    "bundles": [
      "java.text.resources.DateFormatData",
      "java.text.resources.DateFormatData_en",
      "java.text.resources.DateFormatData_ar"
    ]
  }
}

Then build with:

bash
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:

java
@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:

java
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:

java
// 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:

java
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:

bash
# 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:

java
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:

java
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:

bash
# 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:

java
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:

bash
# JVM mode (should work correctly)
java ArabicDateReproducer.java

# Native mode (may have issues)
native-image ArabicDateReproducer
./ArabicDateReproducer

Conclusion

  1. Core Issue: GraalVM Native Image doesn’t automatically include full locale data, causing Arabic date formatting to fall back to English abbreviations.

  2. Primary Solutions: Use -H:IncludeLocales=ar,en with comprehensive locale tags, or -H:+IncludeAllLocales for complete locale support.

  3. Advanced Configuration: Implement resource configuration files and proper tracing agent usage to ensure all required locale resources are included.

  4. Testing Strategy: Always test with target locales before deployment and implement runtime fallbacks for edge cases.

  5. 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

  1. GraalVM Native Image Resources Documentation
  2. GitHub Issue: Support for non-default locale
  3. JEP 252: Use CLDR Locale Data by Default
  4. Stack Overflow: Quarkus GraalVM native image DateTimeFormatter and Localization
  5. Stack Overflow: GraalVM native-image Java i18n Locale problem
  6. GraalVM Native Image Build Configuration