Spring PropertiesLoaderUtils loadAllProperties overrides properties in wrong order

The Spring PropertiesLoaderUtils.loadAllProperties method seems to have a bug where it returns entries from lower-priority classpath entries in preference to higher-priority classpath entries.

When running a Java program, you specify the classpath which is a list of places the JVM can find classes, resources (files) such as properties files containing key-value config, etc. This is deliberately an ordered list, and it is normal to e.g. take a library containing default config and add your config dir to the front of the list to override certain configs.

The JDK offers a facility to “find a file” which will respect this ordering, i.e. it will go through the classpath from first to last entry, and as soon as it finds a file it will return it. That means you can have a classpath like my-config:library and if the file is available in my-config it will return it, and if not it will fall back to the version in library.

java -cp my-config:library <my program>

Spring offers a seemingly-nicer way to do this, in that you can ask it to “get ‘all’ properties with a certain filename” and it will go through the classpath and not just return the first file found, but will look through all the classpath entries, find all files with that name, and merge the key-value properties file. This makes it possible for the library to offer a full properties file with config entries, and for the user to override just some properties within it (as opposed to having to copy the entire file just to change a few entries).

The problem is, this override works the wrong way. Properties in lower-priority classpath files override properties in higher-priority files, which is the wrong way around.

If you run the code at https://github.com/adrianmsmith/spring-properties-wrong-order, you can see:

Expecting the value 'higher-priority' to override 'lower-priority'
Value of property according to Java is: higher-priority
Value of property according to Spring is: lower-priority

You can definitely see that the default JDK methods and Spring disagree on the ordering of the items here.

In my case, this bug manifested itself as unit tests failing as the code was reading a config properties file from the src/main/resources directory as opposed to the correct overridden config properties file from the src/test/resources directory—but only when I moved the config file from a non-standard place (not in the classpath by default) to its standard place (in the classpath by default), which was quite mysterious.

(I think the bug is here. This code goes through the classpath (I assume the ordering is the same as in the classpath’s definition?) and finds all properties file with the requested name across all the classpath entries, and one-by-one overrides an initially empty result with the values from that file. That will result in properties from later files in the “for” loop (i.e. lower-priority classpath entries) overwriting the properties that have been already loaded from previous entries (i.e. higher-priority classpath entries).)

At least Spring 5.3.21 (where I originally discovered the problem) and Spring 6.0.3 (latest version at the time of writing), and presumably everything inbetween, are affected.

This article is © Adrian Smith.
It was originally published on 12 Jan 2023
More on: Coding | Java