Skip to the content.
blog talks comics about

static final what?

I accidentally discovered something interesting about constant fields in Java, so I decided to share it here in a short article.

When we need to create a constant field in Java, we somehow automatically use this conventional construction:

static final <type> CAPS_NAME = <value>;

Why does it take so many keywords to create a constant field?

Let’s take a look!

static final

As we know, static makes the value belong to a class rather than a particular instance, and final means that we are no longer allowed to assign a different value to that field. It makes sense for a constant field to be final, so we can’t change its value. It also logically makes sense for it to be static, because any new instance would have the same value anyway, so why not have it belong to the class?

But technically, is it mandatory to add static?

ConstantValue attribute

Let’s consider the following example:

public static final String TEST = "TEST";
public final String FINAL_TEST = "TEST";
public static String STATIC_TEST = "TEST";

In the bytecode, we see the following:

public static final java.lang.String TEST;
    descriptor: Ljava/lang/String;
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: String TEST

public final java.lang.String FINAL_TEST;
    descriptor: Ljava/lang/String;
    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
    ConstantValue: String TEST

public static java.lang.String STATIC_TEST;
    descriptor: Ljava/lang/String;
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC

Here we see that the final fields have a ConstantValue attribute. According to the JVM Specification chapter about the class file format, this attribute is meant to store the value of a constant expression. When the ACC_STATIC flag is set, the field is assigned the value represented by the ConstantValue attribute. Otherwise, the JVM silently ignores the attribute.

The specifications do not explicitly mention anything about ACC_FINAL. We could, perhaps, derive its necessity from the mysterious “constant expression” formulation.

initialization

If we look further into the bytecode generated by the javac compiler, we will find a static block where STATIC_TEST is initialized. This block is invoked at runtime when our class is referenced for the first time:

static {};
    descriptor: ()V
    flags: (0x0008) ACC_STATIC
    Code:
        stack=1, locals=0, args_size=0
            0: ldc           #7                  // String TEST
            2: putstatic     #15                 // Field STATIC_TEST:Ljava/lang/String;
            5: return
        LineNumberTable:
            line 6: 0

We will also find that FINAL_TEST is initialized inside of the constructor which is invoked at runtime when we create a new instance of our class:

public smthelusive.ConstantStuff();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
        stack=2, locals=1, args_size=1
            0: aload_0
            1: invokespecial #1                  // Method java/lang/Object."<init>":()V
            4: aload_0
            5: ldc           #7                  // String TEST
            7: putfield      #9                  // Field FINAL_TEST:Ljava/lang/String;
            10: return

In the end, the only field that is not explicitly initialized in the bytecode is TEST, which has both ACC_FINAL and ACC_STATIC flags. So where is that value actually initialized?

There are several steps that JVM performs in order to load our class. One of these steps is initialization, and this is also the step when our static block is invoked. The TEST field is special for the reason that it has both ACC_STATIC and ACC_FINAL flags, which hints JVM that there must be some ConstantValue attribute and the field should be initialized with the value stored under that attribute. So JVM will do it internally without the need to explicitly coding it in the bytecode.

extra fun experiment

javac generates sane bytecode. The ConstantValue attribute will be present only if the ACC_FINAL flag is set.

However, what happens if another compiler or tool, following the specifications, generates bytecode with a static non-final field that has a ConstantValue attribute… And no initialization in the bytecode?

Will JVM initialize it in that case? I tested it with OpenJDK and OpenJ9, and strangely they did :) The Fernflower decompiler, on the other hand, doesn’t recognise that there is any initialization going on at all.

couple of conclusions

  • Is the static modifier mandatory for the constant values? Technically, not. Logically, yes. It makes the bytecode more compact by utilizing the ConstantValue attribute. It also takes less memory at runtime. It simply makes more sense.
  • Is the final modifier mandatory? Absolutely. It helps us control our code. Compared to it, ConstantValue attribute is not that important, and it’s here solely for initialization purposes. So if we miss a static keyword, we are somewhat fine. final, on the other hand, helps us enforce constant value in the Java code.
  • Are the JVM specs fully clear? Let me show you a quote from the section that describes the class initialization step:

… initialize each final static field of C with the constant value in its ConstantValue attribute (§4.7.2), in the order the fields appear in the ClassFile structure.

While this quote explicitly mentions final, the section related to class file format and ConstantValue attribute in particular doesn’t say a word about it. Following this specification, most of the JVM implementations don’t consider final as mandatory in order to use ConstantValue… So I’ll leave the last question for you to answer :)

back to blog
Hits