Adding a resource directory to the runtime class path and excluding it from the jar

Greetings. I have a Java/Spring Boot project where I need to decrypt some sops encrypted .env files when the app starts up. The encrypted files are checked into the BitBucket repo, and I need to 1) use the Java class path to find the appropriate file, 2) decrypt it when the app starts using a fixed directory structure/naming convention, and 3) make sure that neither the encrypted or decrypted files are part of the Jar file that gets built in the project. The sops .env files reside in a directory directly below the project root, and the name and location of the directory are fixed. So it looks like this:

project-root
    conf/
        dev.env
        int.env
      ...
    src/
        main/
            java/
            resources/

The conf name and location are not changeable, and for internal reasons, I need to be able to find the .env files using classpath:conf/dev.env. I have tried a million things in my build.gradle file like using processResources, sourceSets, and configurations to try to add the conf directory to the class path and retain the project structure. At one point, I had it working, but when I excluded the files from the jar, things broke again. I’ve tried so many different things, read the Gradle documentation, and changed the code to a point where I’m dizzy, and now I can’t for the life of me figure out what I did.

My hello world test case looks like this:

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.ResourceUtils;

import java.io.FileNotFoundException;

@SpringBootTest
class DemoApplicationTests {

	@Test
	void classPath() throws FileNotFoundException {
		var file = ResourceUtils.getFile("classpath:conf/dev.env");
	}
}

The current build.gradle file looks something like this:

sourceSets {
	sops {
		output.resourcesDir = file("$rootDir/conf/decrypted")
		runtimeClasspath = files("$rootDir/conf")
	}
}

configurations {
	sops
}

This is a runtime thing only. I’ll be using sops to decrypt the *.env files when the app starts up, and neither the encrypted or decrypted files should ever be included in the jar. I’m totally stuck on this, and much appreciate any help to get unblocked.

How do you intend to add the files to the runtime classpath when you want them not to be in the jar file?

I’m looking for guidance here. Maybe I should have been more thorough in explaining the context. The Spring Boot app’s configuration files (application.yml and profile specific ones) contain environment variables that need to be set before the Spring Boot Application starts. In the case of cloud deployment of the app inside a Docker container, the environment variables are set through a DevOps process. The other scenario is that I’m looking for help to solve. As I stated, I need to search for the .env files from the class path, but I need to make sure the files don’t end up in the jar file that we deploy to the cloud.

The other scenario is local development, where we need to start/restart the app frequently, either via ./gradlew bootRun, or by using a Run Configuration in the IDE (right click -> Run <app name>.main(). All environment variables listed in the .env file(s) need to be set before Spring Boot starts, so I’ve created an ApplicationContextInitializer implementation that searches for the sops .env files, decrypts them, and parses the decrypted file into key/value pairs that are fed to a PropertySource that is added to the application context. That way, Spring Boot starts successfully.

I wasn’t sure if bootRun or the Run Configuration simply use what’s underneath the build directory after compiling, or if it packages up the fat jar and uses it. I’m not exactly sure what happens when I run the Spring Boot application from within the IDE. I’m not sure it uses the bootRun task. There isn’t much info in the automatically created Run Configuration in IntelliJ. Maybe I can create a task that removes them from the jar file, and then use that jar file in the Docker container in the deployment? Any suggestions are welcome. I’ve stated the problem, and I’m looking for a solution. Thanks.

That are more questions to Spring Boot, than to Gradle.
It is their plugins that create and configure things like bootRun.
But if you look at their source for example at spring-boot/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java at main · spring-projects/spring-boot · GitHub you see, that the bootRun task is a BootRun task which is a JavaExec task and they use sourceSets.main.runtimeClasspath to execute the application, so it should be the dependency jars and the output directories of tasks like compileJava, processResources, and so on. You can also easily check what is configured there, or use --info or --debug to see the used Java commandline.

What IntelliJ does when you click “Run” is even less a Gradle question, but more a question to JetBrains.
And it heavily depends on your configuration, for example whether you configured to use the “IntelliJ” runner or the default “Gradle” runner in the Gradle settings of IntelliJ. But even in the latter case, it will usually not use bootRun or the boot jar, unless the Spring Boot plugin or your build scripts take some measures to do that or similar.

So as a conclusion, you might achieve what you want by simply doing sourceSets { main { resources { srcDir("conf") } } } or similar and excluding the files from the boot jar task. But it is hard to give concrete advice without an MCVE.

I appreciate the response. But since I was trying to solve the problem using Gradle, and wasn’t successful initially, I posted the question here. It turns out that neither running the application from a Run Configuration in IntelliJ, or starting it with ./gradlew bootRun creates the fat jar. They both seem to use classes and resources from the build directory. I was able to bypass the issue with sops insisting on the files residing under conf (which is what I had trouble accomplishing in Gradle), so now everything works. I find that even after many years of using Gradle in various Java projects, I struggle with understanding all the mechanisms for configuration.

1 Like