Spring Boot: Is it possible to use external application.properties files in arbitrary directories with a fat jar?

SpringConfigurationSpring Boot

Spring Problem Overview


Is it possible to have multiple application.properties file? (EDIT: note that this question evolved to the one on the title.)

I tried to have 2 files.

  • The first one is on the root folder in the application Jar.
  • The second one is on the directory which is specified in classpath.

2 files are both named 'application.properties'.

Is it possible to 'merge' the contents of both files? (and the second one's property values override the first one's) Or, if I have one file then the other file is ignored?

UPDATE 1: it is possible to 'merge' the contents. Yesterday it seemed that the first one was ignored, but it seems that it's because that something was broken then. Now it works well.

UPDATE 2: It's back again! Again only one of two files is being applied. It's strange... It started after I built the app jar file using Spring Tool Suite. And it seems that the Jar version always ignores the second one (in classpath), while the behavior of the expanded version which runs on STS varies. From where can I start to investigate?

UPDATE 3:

The behavior of the Jar version was in fact correct. It's the specification of java.exe. When -jar option is specified, java.exe ignores both -classpath option and CLASSPATH environment variable, and the classpath will contain only the jar file. So, the second application.properties file on the classpath is ignored.

Now, how can I let the second application.properties on the classpath be loaded?

UPDATE 4:

I managed to load an application.properties file in external path while using -jar option.

The key was PropertiesLauncher.

To use PropertiesLauncher, pom.xml file must be changed like this:

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
			<configuration>  <!-- added -->
				<layout>ZIP</layout> <!-- to use PropertiesLaunchar -->
			</configuration>
		</plugin>
	</plugins>
</build>

For this, I referenced the following StackOverflow question: https://stackoverflow.com/questions/21322673/spring-boot-properties-launcher-unable-to-use . BTW, In Spring Boot Maven Plugin document(http://docs.spring.io/spring-boot/docs/1.1.7.RELEASE/maven-plugin/repackage-mojo.html), there is no mention that specifying ZIP triggers that PropertiesLauncher is used. (Perhaps in another document?)

After the jar file had been built, I could see that the PropertiesLauncher is used by inspecting Main-Class property in META-INF/MENIFEST.MF in the jar.

Now, I can run the jar as follows(in Windows):

java -Dloader.path=file:///C:/My/External/Dir,MyApp-0.0.1-SNAPSHOT.jar -jar MyApp-0.0.1-SNAPSHOT.jar

Note that the application jar file is included in loader.path.

Now an application.properties file in C:\My\External\Dir\config is loaded.

As a bonus, any file (for example, static html file) in that directory can also be accessed by the jar since it's in the loader path.

As for the non-jar (expanded) version mentioned in UPDATE 2, maybe there was a classpath order problem.

(BTW, I changed the question's title to be more specific to this issue.)

Spring Solutions


Solution 1 - Spring

If you have not changed the defaults of Spring Boot (meaning you are using @EnableAutoConfiguration or @SpringBootApplication and have not changed any Property Source handling), then it will look for properties with the following order (highest overrides lowest):

  1. A /config subdir of the current directory
  2. The current directory
  3. A classpath /config package
  4. The classpath root

The list above is mentioned in this part of the documentation

What that means is that if a property is found for example application.properties under src/resources is will be overridden by a property with the same name found in application.properties in the /config directory that is "next" to the packaged jar.

This default order used by Spring Boot allows for very easy configuration externalization which in turn makes applications easy to configure in multiple environments (dev, staging, production, cloud etc)

To see the whole set of features provided by Spring Boot for property reading (hint: there is a lot more available than reading from application.properties) check out this part of the documentation.

As one can see from my short description above or from the full documentation, Spring Boot apps are very DevOps friendly!

Solution 2 - Spring

It's all explained here in the docs:

http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

Which explains that this is the order of precedence:

  • A /config subdir of the current directory.
  • The current directory
  • A classpath /config package
  • The classpath root

It also points out that you can define additional properties files for overrides like so:

java -jar myproject.jar 
    --spring.config.location=classpath:/overrides.properties

If you use spring.config.location, then all the default locations for application.properties are also included. This means that you can set up default values in application.properties and override as required for a particular environment.

Solution 3 - Spring

I managed to load an application.properties file in external path while using -jar option.

The key was PropertiesLauncher.

To use PropertiesLauncher, pom.xml file must be changed like this:

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
			<configuration>  <!-- added -->
				<layout>ZIP</layout> <!-- to use PropertiesLaunchar -->
			</configuration>
		</plugin>
	</plugins>
</build>

For this, I referenced the following StackOverflow question: https://stackoverflow.com/questions/21322673/spring-boot-properties-launcher-unable-to-use . BTW, In Spring Boot Maven Plugin document(http://docs.spring.io/spring-boot/docs/1.1.7.RELEASE/maven-plugin/repackage-mojo.html), there is no mention that specifying ZIP triggers that PropertiesLauncher is used. (Perhaps in another document?)

After the jar file had been built, I could see that the PropertiesLauncher is used by inspecting Main-Class property in META-INF/MENIFEST.MF in the jar.

Now, I can run the jar as follows(in Windows):

java -Dloader.path=file:///C:/My/External/Dir,MyApp-0.0.1-SNAPSHOT.jar -jar MyApp-0.0.1-SNAPSHOT.jar

Note that the application jar file is included in loader.path.

Now an application.properties file in C:\My\External\Dir\config is loaded.

As a bonus, any file (for example, static html file) in that directory can also be accessed by the jar since it's in the loader path.

As for the non-jar (expanded) version mentioned in UPDATE 2, maybe there was a classpath order problem.

Solution 4 - Spring

You would be able to launch your spring boot appication with the external properties file path as follows:

java -jar {jar-file-name}.jar 
--spring.config.location=file:///C:/{file-path}/{file-name}.properties

Solution 5 - Spring

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <layout>ZIP</layout> 
            </configuration>
        </plugin>
    </plugins>
</build>

java -Dloader.path=file:///absolute_path/external.jar -jar example.jar

Solution 6 - Spring

java -jar server-0.0.1-SNAPSHOT.jar --spring.config.location=application-prod.properties

Solution 7 - Spring

I know it is a pointed question, and the op wanted to load different properties file.

My answer is, doing custom hacks like this is a terrible idea.

If you are using spring-boot with a cloud provider such as cloud foundry, please do yourself a favor and use cloud config services

https://spring.io/projects/spring-cloud-config

It loads and merges default/dev/project-default/project-dev specific properties like magic

Again, Spring boot already gives you enough ways to do this right https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

Please do not re-invent the wheel.

Solution 8 - Spring

This may be coming in Late but I think I figured out a better way to load external configurations especially when you run your spring-boot app using java jar myapp.war instead of @PropertySource("classpath:some.properties")

The configuration would be loaded form the root of the project or from the location the war/jar file is being run from

public class Application implements EnvironmentAware {
	
	public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }

	@Override
	public void setEnvironment(Environment environment) {
	    //Set up Relative path of Configuration directory/folder, should be at the root of the project or the same folder where the jar/war is placed or being run from
	    String configFolder = "config";
	    //All static property file names here
	    List<String> propertyFiles = Arrays.asList("application.properties","server.properties");
	    //This is also useful for appending the profile names
	    Arrays.asList(environment.getActiveProfiles()).stream().forEach(environmentName -> propertyFiles.add(String.format("application-%s.properties", environmentName))); 
	    for (String configFileName : propertyFiles) {
	        File configFile = new File(configFolder, configFileName);
	        LOGGER.info("\n\n\n\n");
	        LOGGER.info(String.format("looking for configuration %s from %s", configFileName, configFolder));
	        FileSystemResource springResource = new FileSystemResource(configFile);
	        LOGGER.log(Level.INFO, "Config file : {0}", (configFile.exists() ? "FOund" : "Not Found"));
	        if (configFile.exists()) {
	            try {
	                LOGGER.info(String.format("Loading configuration file %s", configFileName));
	                PropertiesFactoryBean pfb = new PropertiesFactoryBean();
	                pfb.setFileEncoding("UTF-8");
	                pfb.setLocation(springResource);
	                pfb.afterPropertiesSet();
	                Properties properties = pfb.getObject();
	                PropertiesPropertySource externalConfig = new PropertiesPropertySource("externalConfig", properties);
	                ((ConfigurableEnvironment) environment).getPropertySources().addFirst(externalConfig);
	            } catch (IOException ex) {
	                LOGGER.log(Level.SEVERE, null, ex);
	            }
	        } else {
	            LOGGER.info(String.format("Cannot find Configuration file %s... \n\n\n\n", configFileName));

	        }

	    }
	}

}

Hope it helps.

Solution 9 - Spring

Another flexible way using classpath containing fat jar (-cp fat.jar) or all jars (-cp "$JARS_DIR/*") and another custom config classpath or folder containing configuration files usually elsewhere and outside jar. So instead of the limited java -jar, use the more flexible classpath way as follows:

java \
   -cp fat_app.jar \ 
   -Dloader.path=<path_to_your_additional_jars or config folder> \
   org.springframework.boot.loader.PropertiesLauncher
  

See Spring-boot executable jar doc and this link

If you do have multiple MainApps which is common, you can use https://stackoverflow.com/questions/23217002/how-do-i-tell-spring-boot-which-main-class-to-use-for-the-executable-jar

You can add additional locations by setting an environment variable LOADER_PATH or loader.path in loader.properties (comma-separated list of directories, archives, or directories within archives). Basically loader.path works for both java -jar or java -cp way.

And as always you can override and exactly specify the application.yml it should pickup for debugging purpose

--spring.config.location=/some-location/application.yml --debug

Solution 10 - Spring

Solution for yml file:

1.Copy yml to in same directory that jar application

2.Run command, example for xxx.yml:

java -jar app.jar --spring.config.location=xxx.yml

It's works fine, but in startup logger is INFO:

No active profile set .........

Solution 11 - Spring

When you are creating spring boot jar using maven install and you want all the resources like properties file and lib folder to be created outside of jar ...then add the following code inside pom.xml where i am defining the output folder where i want to extract and store the desired resources of jar.

<build>
<finalName>project_name_Build/project_name</finalName>
   <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-dependencies</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>${project.build.directory}/project_name_Build/lib</outputDirectory>
                        <overWriteReleases>false</overWriteReleases>
                        <overWriteSnapshots>false</overWriteSnapshots>
                        <overWriteIfNewer>true</overWriteIfNewer>
                    </configuration>
                </execution>
            </executions>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                        <classpathPrefix>lib/</classpathPrefix>
                        <mainClass>write here the qualified or complete path of main class of application</mainClass>
                    </manifest>
                    <manifestEntries>
                        <Class-Path>. resources/</Class-Path>
                    </manifestEntries>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
        </plugin>
    </plugins>

    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>application.properties</include>
                <include>log4j.properties</include>
            </includes>
            <targetPath>${project.build.directory}/ConsentGatewayOfflineBuild/resources</targetPath>
        </resource>

        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>application.properties</include>
                <include>log4j.properties</include>
            </includes>
        </resource>

    </resources>

    <pluginManagement>
        <plugins>
            <!-- Ignore/Execute plugin execution -->
            <plugin>
                <groupId>org.eclipse.m2e</groupId>
                <artifactId>lifecycle-mapping</artifactId>
                <version>1.0.0</version>
                <configuration>
                    <lifecycleMappingMetadata>
                        <pluginExecutions>
                            <!-- copy-dependency plugin -->
                            <pluginExecution>
                                <pluginExecutionFilter>
                                    <groupId>org.apache.maven.plugins</groupId>
                                    <artifactId>maven-dependency-plugin</artifactId>
                                    <versionRange>[1.0.0,)</versionRange>
                                    <goals>
                                        <goal>copy-dependencies</goal>
                                    </goals>
                                </pluginExecutionFilter>
                                <action>
                                    <ignore />
                                </action>
                            </pluginExecution>
                        </pluginExecutions>
                    </lifecycleMappingMetadata>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

Solution 12 - Spring

Place the properties which you want to externalize in a file.properties or yml outside your jar.

@Bean
    //Reads database properties from the config.yml
    public static PropertySourcesPlaceholderConfigurer properties() {
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
        yaml.setResources(new FileSystemResource(filePath + File.separator +"config.yml"));
        propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
        propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
        return propertySourcesPlaceholderConfigurer;
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionzeodtrView Question on Stackoverflow
Solution 1 - SpringgeoandView Answer on Stackoverflow
Solution 2 - SpringSteveView Answer on Stackoverflow
Solution 3 - SpringzeodtrView Answer on Stackoverflow
Solution 4 - Springkk.View Answer on Stackoverflow
Solution 5 - SpringRavindranath AkilaView Answer on Stackoverflow
Solution 6 - SpringHimalayanCoderView Answer on Stackoverflow
Solution 7 - SpringKalpesh SoniView Answer on Stackoverflow
Solution 8 - SpringJohnTheBelovedView Answer on Stackoverflow
Solution 9 - SpringkisnaView Answer on Stackoverflow
Solution 10 - SpringPiotr RogowskiView Answer on Stackoverflow
Solution 11 - SpringPulkitRajputView Answer on Stackoverflow
Solution 12 - SpringEmekaView Answer on Stackoverflow