Native Image build time initialization is a crucial feature of GraalVM that allows code to be executed during the native image generation process rather than at runtime. This significantly improves startup performance and reduces memory footprint.
What is Build Time Initialization?
Build Time Initialization (BTI) enables:
- Code execution during native image compilation
- Pre-computation of constant values
- Initialization of classes at build time
- Reduction of runtime initialization overhead
How Build Time Initialization Works
Source Code → GraalVM Native Image → Executable ↑ Build Time Execution (Class loading, static initializers, etc.)
Basic Build Time Initialization
Example 1: Simple Static Initialization
public class BuildTimeInitDemo {
// This static field will be initialized at build time
private static final String BUILD_INFO = initializeBuildInfo();
private static final long COMPLEX_CALCULATION = performComplexCalculation();
private static String initializeBuildInfo() {
String timestamp = java.time.Instant.now().toString();
String hostname = getHostname();
return "Built at: " + timestamp + " on: " + hostname;
}
private static String getHostname() {
try {
return java.net.InetAddress.getLocalHost().getHostName();
} catch (Exception e) {
return "unknown";
}
}
private static long performComplexCalculation() {
System.out.println("Performing complex calculation at build time...");
// Simulate expensive computation
long result = 0;
for (int i = 0; i < 1000000; i++) {
result += i * i;
}
return result;
}
// This method will be called at runtime
public static void printBuildInfo() {
System.out.println("Build Information: " + BUILD_INFO);
System.out.println("Precomputed value: " + COMPLEX_CALCULATION);
}
public static void main(String[] args) {
System.out.println("=== Application Started ===");
printBuildInfo();
System.out.println("Build time initialization completed successfully!");
}
}
Native Image Configuration
// META-INF/native-image/com.example/reflect-config.json
[
{
"name": "com.example.BuildTimeInitDemo",
"methods": [
{"name": "printBuildInfo", "parameterTypes": [] }
]
}
]
Advanced Build Time Initialization Patterns
Example 2: Configuration Preprocessing
import java.util.*;
import java.io.*;
public class ConfigurationInitializer {
// Configuration loaded at build time
private static final Properties APP_CONFIG = loadConfiguration();
private static final Map<String, String> CONFIG_MAP = processConfiguration();
private static Properties loadConfiguration() {
Properties props = new Properties();
try (InputStream input = ConfigurationInitializer.class
.getResourceAsStream("/application.properties")) {
if (input != null) {
props.load(input);
System.out.println("Configuration loaded at build time");
}
} catch (IOException e) {
System.err.println("Failed to load configuration: " + e.getMessage());
}
return props;
}
private static Map<String, String> processConfiguration() {
Map<String, String> configMap = new HashMap<>();
for (String key : APP_CONFIG.stringPropertyNames()) {
String value = APP_CONFIG.getProperty(key);
// Pre-process values (e.g., resolve placeholders)
String processedValue = processValue(value);
configMap.put(key, processedValue);
}
return Collections.unmodifiableMap(configMap);
}
private static String processValue(String value) {
// Example: Replace placeholders with build-time values
return value.replace("${build.timestamp}",
java.time.Instant.now().toString())
.replace("${build.java.version}",
System.getProperty("java.version"));
}
// Runtime access to precomputed configuration
public static String getConfig(String key) {
return CONFIG_MAP.get(key);
}
public static Set<String> getAllConfigKeys() {
return CONFIG_MAP.keySet();
}
public static void main(String[] args) {
System.out.println("=== Configuration Demo ===");
System.out.println("All configuration keys: " + getAllConfigKeys());
System.out.println("Database URL: " + getConfig("database.url"));
System.out.println("Feature flags: " + getConfig("feature.flags"));
}
}
Example 3: Database Schema Validation at Build Time
import java.sql.*;
import java.util.*;
public class DatabaseSchemaValidator {
// Schema information computed at build time
private static final SchemaInfo SCHEMA_INFO = validateSchema();
static class SchemaInfo {
final boolean isValid;
final List<String> tables;
final List<String> missingTables;
final Map<String, List<String>> tableColumns;
SchemaInfo(boolean isValid, List<String> tables,
List<String> missingTables, Map<String, List<String>> tableColumns) {
this.isValid = isValid;
this.tables = tables;
this.missingTables = missingTables;
this.tableColumns = tableColumns;
}
}
private static SchemaInfo validateSchema() {
System.out.println("Validating database schema at build time...");
// In real scenario, this would connect to actual database
// For demo, we'll simulate schema validation
List<String> expectedTables = Arrays.asList("users", "products", "orders");
List<String> actualTables = Arrays.asList("users", "products", "orders", "logs");
List<String> missingTables = new ArrayList<>();
Map<String, List<String>> tableColumns = new HashMap<>();
// Check for missing tables
for (String expected : expectedTables) {
if (!actualTables.contains(expected)) {
missingTables.add(expected);
}
}
// Simulate column information
tableColumns.put("users", Arrays.asList("id", "name", "email"));
tableColumns.put("products", Arrays.asList("id", "name", "price"));
tableColumns.put("orders", Arrays.asList("id", "user_id", "product_id"));
boolean isValid = missingTables.isEmpty();
if (!isValid) {
System.err.println("Missing tables: " + missingTables);
} else {
System.out.println("Schema validation passed!");
}
return new SchemaInfo(isValid, actualTables, missingTables, tableColumns);
}
public static boolean isSchemaValid() {
return SCHEMA_INFO.isValid;
}
public static List<String> getTableColumns(String tableName) {
return SCHEMA_INFO.tableColumns.getOrDefault(tableName,
Collections.emptyList());
}
public static void main(String[] args) {
if (!isSchemaValid()) {
System.err.println("Database schema is invalid!");
System.exit(1);
}
System.out.println("Database schema is valid");
System.out.println("Available tables: " + SCHEMA_INFO.tables);
System.out.println("Users table columns: " + getTableColumns("users"));
}
}
Conditional Build Time Initialization
Example 4: Feature Flag Preprocessing
import java.util.*;
public class FeatureFlagInitializer {
// Feature flags resolved at build time
private static final Map<String, Boolean> FEATURE_FLAGS = initializeFeatureFlags();
private static final Set<String> ENABLED_FEATURES = computeEnabledFeatures();
private static Map<String, Boolean> initializeFeatureFlags() {
Map<String, Boolean> flags = new HashMap<>();
// Read from configuration or environment
flags.put("new_ui", Boolean.parseBoolean(
System.getProperty("feature.new_ui", "true")));
flags.put("advanced_analytics", Boolean.parseBoolean(
System.getProperty("feature.advanced_analytics", "false")));
flags.put("experimental_api", Boolean.parseBoolean(
System.getProperty("feature.experimental_api", "false")));
flags.put("caching", Boolean.parseBoolean(
System.getProperty("feature.caching", "true")));
System.out.println("Feature flags initialized at build time:");
flags.forEach((k, v) -> System.out.println(" " + k + ": " + v));
return Collections.unmodifiableMap(flags);
}
private static Set<String> computeEnabledFeatures() {
Set<String> enabled = new HashSet<>();
FEATURE_FLAGS.forEach((feature, enabledFlag) -> {
if (enabledFlag) {
enabled.add(feature);
}
});
return Collections.unmodifiableSet(enabled);
}
// Runtime checks (no computation needed)
public static boolean isFeatureEnabled(String feature) {
return FEATURE_FLAGS.getOrDefault(feature, false);
}
public static Set<String> getEnabledFeatures() {
return ENABLED_FEATURES;
}
// Feature-specific code that can be optimized at build time
public static void initializeFeatures() {
if (isFeatureEnabled("new_ui")) {
initializeNewUI();
}
if (isFeatureEnabled("advanced_analytics")) {
initializeAnalytics();
}
if (isFeatureEnabled("caching")) {
initializeCache();
}
}
private static void initializeNewUI() {
System.out.println("Initializing new UI components...");
// UI initialization code
}
private static void initializeAnalytics() {
System.out.println("Initializing advanced analytics...");
// Analytics initialization code
}
private static void initializeCache() {
System.out.println("Initializing cache system...");
// Cache initialization code
}
public static void main(String[] args) {
System.out.println("=== Feature Flag Demo ===");
System.out.println("Enabled features: " + getEnabledFeatures());
initializeFeatures();
}
}
Build Time Resource Processing
Example 5: Resource Bundles and Internationalization
import java.util.*;
import java.io.*;
public class ResourceBundleInitializer {
// Resource bundles loaded and processed at build time
private static final Map<String, Properties> RESOURCE_BUNDLES =
loadResourceBundles();
private static final Set<Locale> SUPPORTED_LOCALES =
determineSupportedLocales();
private static Map<String, Properties> loadResourceBundles() {
Map<String, Properties> bundles = new HashMap<>();
String[] supportedLanguages = {"en", "es", "fr", "de"};
for (String lang : supportedLanguages) {
String resourceName = "/messages_" + lang + ".properties";
try (InputStream input = ResourceBundleInitializer.class
.getResourceAsStream(resourceName)) {
if (input != null) {
Properties props = new Properties();
props.load(input);
bundles.put(lang, props);
System.out.println("Loaded resource bundle: " + resourceName);
}
} catch (IOException e) {
System.err.println("Failed to load resource bundle: " + resourceName);
}
}
return Collections.unmodifiableMap(bundles);
}
private static Set<Locale> determineSupportedLocales() {
Set<Locale> locales = new HashSet<>();
for (String lang : RESOURCE_BUNDLES.keySet()) {
locales.add(new Locale(lang));
}
return Collections.unmodifiableSet(locales);
}
// Runtime access to preloaded resources
public static String getMessage(String language, String key) {
Properties bundle = RESOURCE_BUNDLES.get(language);
if (bundle != null) {
return bundle.getProperty(key, "[" + key + "]");
}
return "[" + key + "]";
}
public static Set<Locale> getSupportedLocales() {
return SUPPORTED_LOCALES;
}
public static boolean isLanguageSupported(String language) {
return RESOURCE_BUNDLES.containsKey(language);
}
public static void main(String[] args) {
System.out.println("=== Resource Bundle Demo ===");
System.out.println("Supported locales: " + getSupportedLocales());
// Demonstrate message retrieval
String[] testKeys = {"welcome", "goodbye", "error.not_found"};
for (String key : testKeys) {
System.out.printf("English '%s': %s%n",
key, getMessage("en", key));
System.out.printf("Spanish '%s': %s%n",
key, getMessage("es", key));
}
}
}
Build Time Code Generation
Example 6: Template Preprocessing
import java.util.*;
import java.util.regex.*;
public class TemplatePreprocessor {
// Templates preprocessed at build time
private static final Map<String, String> PROCESSED_TEMPLATES =
preprocessTemplates();
private static Map<String, String> preprocessTemplates() {
Map<String, String> templates = new HashMap<>();
// Load and preprocess templates
String[] templateNames = {"email_welcome", "email_reset", "report_daily"};
for (String name : templateNames) {
String template = loadTemplate(name);
if (template != null) {
String processed = processTemplate(template);
templates.put(name, processed);
System.out.println("Preprocessed template: " + name);
}
}
return Collections.unmodifiableMap(templates);
}
private static String loadTemplate(String name) {
// Simulate template loading
Map<String, String> rawTemplates = Map.of(
"email_welcome", """
Dear ${user.name},
Welcome to ${app.name}! Your account has been created successfully.
Account details:
- Username: ${user.username}
- Email: ${user.email}
- Created: ${timestamp}
Best regards,
The ${app.name} Team
""",
"email_reset", """
Hello ${user.name},
You requested a password reset for your ${app.name} account.
Reset link: ${reset.url}
This link will expire in ${reset.expiry_hours} hours.
If you didn't request this reset, please ignore this email.
""",
"report_daily", """
Daily Report - ${report.date}
=====================
Total Users: ${stats.total_users}
New Signups: ${stats.new_signups}
Active Today: ${stats.active_today}
Generated: ${timestamp}
"""
);
return rawTemplates.get(name);
}
private static String processTemplate(String template) {
// Pre-process static parts of the template
String processed = template
.replace("${app.name}", "MyAwesomeApp")
.replace("${reset.expiry_hours}", "24");
// Compile regex patterns for dynamic parts
// This reduces runtime overhead
return processed;
}
// Runtime template rendering
public static String renderTemplate(String templateName,
Map<String, String> variables) {
String template = PROCESSED_TEMPLATES.get(templateName);
if (template == null) {
throw new IllegalArgumentException("Unknown template: " + templateName);
}
String result = template;
for (Map.Entry<String, String> entry : variables.entrySet()) {
String placeholder = "${" + entry.getKey() + "}";
result = result.replace(placeholder, entry.getValue());
}
return result;
}
public static void main(String[] args) {
System.out.println("=== Template Rendering Demo ===");
// Render welcome email
Map<String, String> vars = new HashMap<>();
vars.put("user.name", "John Doe");
vars.put("user.username", "johndoe");
vars.put("user.email", "[email protected]");
vars.put("timestamp", java.time.Instant.now().toString());
String welcomeEmail = renderTemplate("email_welcome", vars);
System.out.println("Welcome Email:\n" + welcomeEmail);
}
}
Native Image Configuration for Build Time Initialization
Example 7: Custom Native Image Configuration
// META-INF/native-image/com.example/native-image.properties Args = --initialize-at-build-time=com.example.BuildTimeInitDemo,\ com.example.ConfigurationInitializer,\ com.example.DatabaseSchemaValidator,\ com.example.FeatureFlagInitializer,\ com.example.ResourceBundleInitializer,\ com.example.TemplatePreprocessor
Reflection Configuration
// META-INF/native-image/com.example/reflect-config.json
[
{
"name": "com.example.BuildTimeInitDemo",
"methods": [
{"name": "printBuildInfo", "parameterTypes": [] },
{"name": "main", "parameterTypes": ["[Ljava.lang.String;"] }
]
},
{
"name": "com.example.ConfigurationInitializer",
"methods": [
{"name": "getConfig", "parameterTypes": ["java.lang.String"] },
{"name": "getAllConfigKeys", "parameterTypes": [] },
{"name": "main", "parameterTypes": ["[Ljava.lang.String;"] }
]
}
]
Resource Configuration
// META-INF/native-image/com.example/resource-config.json
{
"resources": {
"includes": [
{
"pattern": ".*\\.properties$"
},
{
"pattern": ".*\\.json$"
}
]
},
"bundles": [
{
"name": "messages"
}
]
}
Build Script Example
Example 8: Maven Configuration for Native Image
<!-- pom.xml --> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <version>0.9.28</version> <executions> <execution> <id>build-native</id> <goals> <goal>build</goal> </goals> <phase>package</phase> </execution> </executions> <configuration> <imageName>my-application</imageName> <mainClass>com.example.BuildTimeInitDemo</mainClass> <buildArgs> <buildArg>--initialize-at-build-time=com.example</buildArg> <buildArg>--no-fallback</buildArg> <buildArg>--enable-https</buildArg> <buildArg>--enable-http</buildArg> </buildArgs> </configuration> </plugin>
Best Practices for Build Time Initialization
- Identify Stable Data: Only initialize data that doesn't change between builds
- Avoid I/O Operations: Be cautious with file system access during build
- Handle Errors Gracefully: Build time failures should be descriptive
- Test Thoroughly: Ensure build time behavior matches runtime expectations
- Use Conditional Logic: Leverage system properties for build-time decisions
- Monitor Performance: Track build time impact of initialization
Common Pitfalls and Solutions
public class BuildTimePitfalls {
// PITFALL: This will capture build time, not runtime
private static final String BUILD_TIME = java.time.Instant.now().toString();
// SOLUTION: Use lazy initialization for runtime values
private static class RuntimeValues {
static final String RUNTIME_TIME = java.time.Instant.now().toString();
}
// PITFALL: File paths that exist at build time but not runtime
private static final String BUILD_PATH = System.getProperty("user.dir");
// SOLUTION: Use relative paths or configuration
private static final String CONFIG_PATH =
System.getProperty("app.config.path", "/etc/myapp");
public static void main(String[] args) {
System.out.println("Build time: " + BUILD_TIME);
System.out.println("Runtime: " + RuntimeValues.RUNTIME_TIME);
System.out.println("Config path: " + CONFIG_PATH);
}
}
Performance Benefits
- Faster Startup: Reduced class loading and initialization overhead
- Smaller Memory Footprint: Pre-computed values don't require runtime computation
- Better Optimization: GraalVM can optimize based on build-time knowledge
- Predictable Behavior: Known state at application start
Conclusion
Build Time Initialization in Native Image provides significant benefits:
Key Advantages:
- Performance: Dramatically improved startup times
- Efficiency: Reduced memory footprint
- Predictability: Known application state at launch
- Optimization: Better ahead-of-time compilation
Use Cases:
- Configuration loading and validation
- Resource bundle processing
- Feature flag resolution
- Template preprocessing
- Schema validation
- Constant computation
Best Practices:
- Initialize only stable, build-time constant data
- Handle errors gracefully during build
- Use proper native image configuration
- Test both build-time and runtime behavior
By leveraging build time initialization effectively, you can create Native Image applications that start instantly and operate efficiently with minimal runtime overhead.
Next Steps: Experiment with your own build-time initialization scenarios, profile the performance benefits, and integrate build-time initialization into your continuous integration pipeline for optimal results.