Does the JLS require inlining of final String constants?

java, java-8, javac, jls, language-lawyer

Solution

Yes, the “inlining” behavior is mandated by the specification:

13.1. The Form of a Binary

A reference to a field that is a constant variable (§4.12.4) must be resolved at compile time to the value `V` denoted by the constant variable's initializer.

If such a field is `static`, then no reference to the field should be present in the code in a binary file, including the class or interface which declared the field. Such a field must always appear to have been initialized (§12.4.2); the default initial value for the field (if different than `V`) must never be observed.

If such a field is non-`static`, then no reference to the field should be present in the code in a binary file, except in the class containing the field. (It will be a class rather than an interface, since an interface has only `static` fields.) The class should have code to set the field's value to `V` during instance creation (§12.5).

Note, how this precisely addresses you scenario: “If such a field is `static`, then no reference to the field should be present in the code in a binary file, including the class or interface which declared the field”.

In other words, if you encounter a compiler not adhering to this, you found a compiler bug.

As an addendum, the starting point for finding this information was:

4.12.4. final Variables

A constant variable is a `final` variable of primitive type or type `String` that is initialized with a constant expression (§15.28). Whether a variable is a constant variable or not may have implications with respect to class initialization (§12.4.1), binary compatibility (§13.1, §13.4.9), and definite assignment (§16 (Definite Assignment)).

Problem

I ran into an issue while manipulating some bytecode, where a certain `final` `String` constant was not inlined by the java compiler (Java 8), see the example below: ``` public class MyTest { private static final String ENABLED = "Y"; private static final String DISABLED = "N"; private static boolean isEnabled(String key) { return key.equals("A"); } private static String getString(String key, String value) { return key + value; } public static void main(String[] args) throws Exception { String flag = getString("F", isEnabled("A") ? ENABLED : DISABLED); System.out.println(flag); String flag2 = getString("F", isEnabled("A") ? ENABLED : DISABLED); System.out.println(flag2); } } ``` Resulting bytecode with javac (1.8.0_101) ``` public static void main(java.lang.String[]) throws java.lang.Exception; descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: ldc #8 // String F 2: ldc #2 // String A 4: invokestatic #9 // Method isEnabled:(Ljava/lang/String;)Z 7: ifeq 16 10: getstatic #10 // Field ENABLED:Ljava/lang/String; 13: goto 19 16: getstatic #11 // Field DISABLED:Ljava/lang/String; 19: invokestatic #12 // Method getString:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; 22: astore_1 23: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream; 26: aload_1 27: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: ldc #8 // String F 32: ldc #2 // String A 34: invokestatic #9 // Method isEnabled:(Ljava/lang/String;)Z 37: ifeq 46 40: getstatic #10 // Field ENABLED:Ljava/lang/String; 43: goto 49 46: getstatic #11 // Field DISABLED:Ljava/lang/String; 49: invokestatic #12 // Method getString:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; 52: astore_2 53: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream; 56: aload_2 57: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 60: return ``` You can see that the second time the fields `ENABLED` and `DISABLED` are being accessed, the compiler did not inline their values (using `ldc`), but instead used `getstatic` to access the field directly. Testing it with other compilers (Java 7, Eclipse) did not trigger the same behavior and the constants were always inlined. Can this be considered a compiler bug, or is it allowed to not inline string constants all the time according to the JLS?

Original source