Java+Spring : @Value annotations on final variables

· May 17, 2016

So, using Java since, like, 24 hours.

Using Java is one of those things making you appreciate C#. Or well, just about any other language.

 

The cool part of using Java is that after the first waves of vomit have passed, you have to deal with using Spring, which causes a lot more waves – how can Java programmers feel fine in leaving so much control to such a monolith I don’t know.

 

Vomit aside, I found a little compiler+env issue – I’m probably about to state the obvious, but remember I’m using the language since just like 24hrs so…

 

Example 1

Let’s start with a standard class, using a variable initialized from configuration:

@Service
public class MyService {

 @Value("${foo.bar.url}")
 final String fooBarUrl = "";

 public void whatever() {
    URL url = new URL(fooBarUrl); // exception thrown here
 }

The above throws an exception, saying that the fooBarUrl doesn’t contain a valid protocol part (actually, it’s empty) just as if the variable wasn’t initialized. But – wow – peak it with the debugger.. and the value is there.  This is guaranteed to cause headaches.

 

Example 2

Let’s start with a standard class, using a variable initialized from configuration:

@Service
public class MyService {

 @Value("${foo.bar.url}")
 final String fooBarUrl;

 public MyService() {
    fooBarUrl = "";
 }
 
 public void whatever() {
   URL url = new URL(fooBarUrl); // it works!
 }

This works, but I wouldn’t trust it to run after JRE updates, read below to know why.

 

Example 3

Let’s start with a standard class, using a variable initialized from configuration:

@Service
public class MyService {

 @Value("${foo.bar.url}")
 String fooBarUrl;

  public void whatever() {
   URL url = new URL(fooBarUrl); // it works!
 }

This works, and is the good solution, even if code could overwrite fooBarUrl – something I would like to avoid.

 

Why

I can only guess why Example 1 doesn’t work and the reason why I don’t trust Example 2 even if it works.. Guess mode ON.

My guess is that the compiler sees the fooBarUrl initialized with a constant value outside of constructor code. The variable – in code – could not be overwritten in any way, so to optimize it replaces the variable accesses with a literal string – after all we declared it not to change. Because it has to support reflection in a meaningful way (i.e. not crashing) it still creates the backing store for the variable and that’s what gets accessed with reflection and.. by the debugger of course. So in essence the debugger correctly reports the variable value, but the code has been “changed” to use new URL(“”) instead of new URL(fooBarUrl);

Under this theory, Example2 works because the initialization is in the constructor and the compiler is not sophisticated enough to see that that is the only possible initialization path – thus it skips the “optimization” and goes on. Any “improvement” in the compiler could break this though.

Example3 is just clean.

While very debatable, I personally think this as a bug in the tool chain because, well, that optimization is quite hazardous. We live in a world where almost every program uses reflection heavily so compilers shouldn’t rely on finals/readonlys not to be overwritten, privates not to be changed from outside, etc. Either have the compiler/JIT be consistent, or disallow writing from reflection.

 

Filed under: Uncategorized