Skip to Content

Hi, I'm Riccardo

Migrating to Spring Boot 6.1 | javac -parameters

screenshot of java installation process saying 3 billion devices run java

I'll start with a very loud sorry... Would you have lost the chance to post this meme while talking of Java? Me neither.

Now, back to the serious stuff.

The problem

Although I have a love-hate relationship with Java, it is what pays my bills every single day. Yes, I am a Java dev.

I was porting a project from Java 17 to Java 20 (or Java 21) and consequently from Spring Boot 3.0.x to Spring Boot 3.2.0 (which at the time of writing is on Milestone stage). Boot 3.2.0 used Spring 6.1 under the hood, whereas 3.0.x used 6.0.

The update worked like a breeze, but when I ran the test suite, there it was an error. Something along the lines of:

org.springframework.data.mapping.MappingException: Parameter org.springframework.data.mapping.PreferredConstructor$Parameter@c3f657893 does not have a name!

Something inside Spring Data was not working correctly. Obviously I searched every corner of the web for a solution, but to no avail. I don't know if it was a real undocumented issue or something out of my ignorance, but either way the only way left was the debugger.

After some fiddling, I stumbled upon StandardReflectionParameterNameDiscoverer.java:

@Nullable
private String[] getParameterNames(Parameter[] parameters) {
String[] parameterNames = new String[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter param = parameters[i];
if (!param.isNamePresent()) {
return null; // the debugger takes this route
}
parameterNames[i] = param.getName();
}
return parameterNames;
}

I learned isNamePresent() goes to some native code to understand if the parameter name is available in the compiled bytecode. Spoiler: it wasn't. Now it was clear it was a reflection issue.

The solution

Again, back to the search. What I found was that to have reflection metadata bundled inside the .class files, the code has to be compiled with java -parameters.

I was not using Gradle to run the tests, but IntelliJ. And guess what? Gradle does this automatically granted you have set your sourceCompatibility above 11, while IntelliJ does not. Once I configured the IDE to use javac -parameters everything was back to working order.

The documentation

Could it be that such a big change went unannounced? Or again was it due to my ignorance?

The answer can be found promptly on the wiki of the Spring Framework project at this page:

LocalVariableTableParameterNameDiscoverer has been removed in 6.1. Compile your Java sources with the common Java 8+ -parameters flag for parameter name retention (instead of relying on the -debug compiler flag) in order to be compatible with StandardReflectionParameterNameDiscoverer. With the Kotlin compiler, we recommend the -java-parameters flag.

It was indeed communicated in some way, although I would have preferred a somewhat louder announcement.

Final considerations

Why was this change done only on this minor version update? And why javac does not put parameter names into the bytecode by default?

Well, as this very informative Stack Overflow answer states, nothing comes for free.

Summarizing the answer, there are three major concerns:

  • File size
  • Compatibility
  • Exposure of sensible information

File size

As imaginable, filling the .class files with reflection metadata even if not needed increases the size of the class file itself. I don't consider this a real-world problem: Java application tend to be chunky anyway, a couple of megs more are not the issue here.

Compatibility

Although changing the parameter names doesn't hurt the binary compatibility of bytecode in the JVM, Java embraces very strictly the segregation pattern and treats them as local variables. You shouldn't really depend on them.

But, sometimes reflection is convenient, think JSON to class mapping or ORMs. I take convenience over dependence every day, especially because Java is already too verbose. Adding other boilerplate to manually map Jackson POJOs is one step more in the direction of "too much".

Exposure of sensible information

This can be true. If an attacker gains knowledge of the .class file source (looking at you Log4j), exposing the internal parameter names inherently broadens the attack surface area.

Hopefully my code artifacts will stay well hidden and protected behind some server and not reach any unwanted end user.

Useful links