Fundamentals 21 min read

Understanding Compile-Time and Run-Time Constants in Java

This article explains the fundamental differences between compile-time and run-time constants in Java, covering their definitions, required modifiers, allowed data types, initialization rules, JVM storage behavior, class‑initialization effects, constant‑folding optimizations, practical code examples, common pitfalls, and guidelines for choosing the appropriate constant type in production code.

Java Tech Workshop
Java Tech Workshop
Java Tech Workshop
Understanding Compile-Time and Run-Time Constants in Java

1. What Is a Java Constant?

In Java, a constant is a variable whose value cannot be changed after initialization. The final keyword enforces this immutability: once a final variable is assigned, its value cannot be reassigned (primitive values cannot change, and reference variables cannot point to a different object).

2. Compile-Time Constants (Compile‑time Constant)

2.1 Definition and Core Features

A compile‑time constant is a variable whose final value can be determined during the compilation phase. The compiler can embed the value directly into the bytecode of any class that references it, enabling optimizations such as constant folding.

Modifier: must be declared static final (fields in interfaces are implicitly public static final).

Data type: only primitive types (byte, short, int, long, float, double, boolean, char) or String. Reference types are not allowed.

Initialization value: must be a compile‑time constant expression (literal, arithmetic on other compile‑time constants, string concatenation, etc.). No method calls, new objects, random values, or other runtime calculations.

Underlying storage: after compilation the constant value is inlined into the bytecode of the using class and also stored in the runtime constant pool of the defining class.

Class initialization: accessing a compile‑time constant does **not** trigger initialization of its declaring class because the value is already known.

2.2 Valid and Invalid Examples

public static final int MAX_AGE = 100;
public static final boolean FLAG = true;
public static final char CH = 'A';
public static final double PI = 3.1415926;
public static final String NAME = "Java Constant";
public static final int SUM = 10 + 20; // compile‑time computed as 30
public static final String COMBINE = "Hello" + "World"; // compile‑time computed as "HelloWorld"
interface Constant {
    String URL = "https://xxx.com"; // compile‑time constant
    int TIMEOUT = 3000;
}

Invalid examples (do not satisfy all conditions):

// Reference type – not a compile‑time constant
public static final Integer NUM = 100; // Integer is a reference type
public static final List<String> LIST = new ArrayList<>(); // reference type
public static final EnumType TYPE = EnumType.A; // enum is a reference type
// Runtime initialization – not compile‑time constant
public static final int RANDOM = new Random().nextInt(); // method call
public static final String UUID = UUID.randomUUID().toString(); // method call
public static final int CURRENT_TIME = (int) System.currentTimeMillis(); // runtime value
// Missing static – becomes a run‑time constant
public final int AGE = 20; // only final, no static
// Illegal operator – ++/-- cannot be used in compile‑time expressions
public static final int COUNT = 10++; // compilation error

2.3 Underlying Mechanism: Constant Folding

The compiler performs constant folding by evaluating compile‑time constant expressions and replacing them with the computed result, eliminating the expression from the generated bytecode and reducing runtime overhead.

public class CompileConstant {
    public static final int A = 5;
    public static final int B = 10;
    public static final int C = A * B + 1; // expression 5*10+1
}

After compilation, the bytecode shows that C is replaced by the literal 51. The original expression A * B + 1 no longer exists, demonstrating constant folding.

String concatenation is optimized similarly: "Hello" + "World" becomes "HelloWorld" at compile time.

2.4 Key Feature: Access Does Not Trigger Class Initialization

Because the value is already embedded, accessing a compile‑time constant does not cause the JVM to load or initialize the declaring class.

// Constant class
public class ConstantClass {
    public static final String COMPILE_CONST = "Compile‑time Constant";
    static {
        System.out.println("ConstantClass initialized");
    }
}
// Test class
public class Test {
    public static void main(String[] args) {
        System.out.println(ConstantClass.COMPILE_CONST);
    }
}

Running the test prints only Compile‑time Constant; the static block message is never printed because the class is not initialized.

3. Run-Time Constants (Run‑time Constant)

3.1 Definition and Core Features

A run‑time constant is a final variable whose final value can only be determined during program execution (class loading or object instantiation). The compiler cannot perform constant folding for these values.

Modifier: can be just final (instance constant) or static final (static run‑time constant) when the initializer depends on runtime computation.

Data type: primitive, String, or any reference type (e.g., Integer, arrays, enums, custom objects).

Initialization value: may involve method calls, new objects, configuration file reads, random numbers, etc.

Underlying storage: instance constants reside in the heap as part of the object; static run‑time constants are stored in the runtime constant pool but their values are resolved at runtime.

Class initialization: accessing a static run‑time constant triggers initialization of its declaring class; accessing an instance constant requires creating an object first, which triggers object initialization.

3.2 Common Examples

// Instance constant – value set in constructor (run‑time)
public class RuntimeConstant {
    public final int INSTANCE_CONST;
    public RuntimeConstant(int value) {
        this.INSTANCE_CONST = value;
    }
}
// Static final but runtime‑initialized
public class RuntimeConstant2 {
    public static final int RANDOM_NUM = new Random().nextInt(100);
    public static final String CONFIG_VALUE = readConfig("config.key");
    public static final EnumType TYPE = EnumType.B;
    public static final Integer WRAP_NUM = 100;
    static {
        System.out.println("RuntimeConstant2 initialized");
    }
    private static String readConfig(String key) {
        // simulate config read
        return "config_value";
    }
}
// Local final variable inside a method (run‑time)
public class RuntimeConstant3 {
    public void test() {
        final int LOCAL_CONST = 100; // literal – still a run‑time constant
        final String LOCAL_STR = new String("Local Constant"); // object creation
    }
}

3.3 Key Feature: Access Triggers Initialization

Static run‑time constants cause class initialization when accessed; instance run‑time constants require object creation first.

public class Test {
    public static void main(String[] args) {
        System.out.println(RuntimeConstant2.RANDOM_NUM);
    }
}

Running the program prints a message from the static block (class initialization) followed by a random number, confirming that the class was loaded.

4. Core Differences Between Compile‑Time and Run‑Time Constants

Value determination : compile‑time constants are resolved during compilation; run‑time constants are resolved during execution.

Modifier requirement : compile‑time constants require static final; run‑time constants may be just final or static final with runtime initialization.

Allowed data types : compile‑time constants are limited to primitives and String; run‑time constants accept any type.

Initialization expression : compile‑time constants need a compile‑time constant expression; run‑time constants can use method calls, new, configuration reads, etc.

Storage : compile‑time values are inlined into referencing class bytecode and stored in the constant pool; run‑time static constants reside in the constant pool, while instance constants live on the heap.

Class initialization : accessing a compile‑time constant does not trigger class initialization; accessing a static run‑time constant does, and instance constants require object creation.

Optimization : compile‑time constants benefit from constant folding; run‑time constants incur runtime computation.

Effect of transient : ineffective for compile‑time constants (they are already inlined); effective for run‑time reference constants to exclude them from serialization.

Impact of modification : changing a compile‑time constant requires recompiling all classes that reference it; changing a run‑time constant only requires recompiling the class that defines it.

Typical use cases : compile‑time – fixed values like PI, API URLs, enum literals; run‑time – dynamic configuration, random values, per‑instance identifiers.

5. How to Choose Between the Two

5.1 Prefer Compile‑Time Constants When

The value is immutable and can be known at compile time (e.g., mathematical constants, fixed business thresholds, static API endpoints).

The constant is referenced by many classes and you want to avoid runtime overhead.

You want to prevent class initialization side effects (e.g., utility classes).

5.2 Prefer Run‑Time Constants When

The value must be determined dynamically (configuration files, database queries, random numbers, method results).

The constant is a reference type (enums, wrapper classes, arrays, custom objects).

Each object needs its own constant value (e.g., per‑instance IDs or configuration).

You anticipate frequent changes and want to avoid recompiling all dependent classes.

6. Common Pitfalls

Assuming all static final fields are compile‑time constants. Only those whose type is primitive or String and whose initializer is a compile‑time expression qualify. Reference types or runtime initializers make them run‑time constants.

Modifying a compile‑time constant without recompiling referencing classes. The old value remains embedded in the bytecode of the callers, leading to stale data.

Using transient on a compile‑time constant. The constant is already inlined, so transient has no effect on serialization.

Treating local final variables as compile‑time constants. Only literals or compile‑time expressions are treated as such; values produced by runtime calls are run‑time constants.

Assuming interface fields are compile‑time constants. If the initializer involves a method call, the field is a run‑time constant and will trigger interface initialization.

7. Full Summary

The core distinction between the two constant types is **when the value is determined** – compile‑time vs run‑time.

Compile‑time constants: static final + primitive/ String + compile‑time expression; no class initialization; support constant folding.

Run‑time constants: may be just final or static final with runtime initializer; support reference types; trigger class or object initialization.

Common interview focus: class‑initialization side effects, constant folding, transient behavior, and the recompilation impact of changing a compile‑time constant.

Selection guideline: use compile‑time constants for immutable, globally shared values; use run‑time constants for dynamic or per‑instance data.

Understanding these nuances helps avoid bugs such as “constant modification not taking effect”, unexpected class‑initialization errors, and serialization pitfalls, and it equips developers to answer high‑frequency interview questions on Java constant handling.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavajvmperformanceBest Practicescompile-timeconstantsrun-time
Java Tech Workshop
Written by

Java Tech Workshop

Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.