Can not read spring boot packaging jar file

Viewed 1972

Environment: Ubuntu 18 + Spring boot 2.0 + JDK 11

It performs correctly when the application runs in IntelliJ, but it doesn't when reading the file after Spring boot maven plugin packaging file.

PS: The file indeed could be found in the packaged jar file!

java.io.FileNotFoundException: class path resource [jmxremote.password] 
cannot be resolved to absolute file path because it does not reside in the 
file system: jar:file:/home/XXX/YYY/target/YYY-1.0-Final.jar!/BOOT-
INF/classes!/jmxremote.password
2 Answers

You are most likely attempting to use java.io classes to load a class path resource. This won't work, the java.io classes are designed only for file system operations.

Either extract jmxremote.password outside of the JAR or use the regular InputStream to access it as a class path resource e.g. by using Class.getResourceAsStream():

try (InputStream in : getClass().getResourceAsStream("/jmxremote.password")) {
  // read input
}

TL;DR

In the Spring Framework environment focus on using Spring utility tooling for handling resources (like the ResourceUtils class) which nicely encapsulate lower-level, operating system dependent IO operations. The ResourceUtils already contains multiple traps to figure out if the project you are running is exploded (run in IDE) or packaged (inside a JAR file).


The answer provided by Karol seems the easiest and is relatively bullet-proof, until you need a certain level of flexibility to specify file location (inside a jar file but with the possibility to define it externally and provide somewhere in the file system). Then the approach with getResourceAsStream() method won't work.

Standard Java IO (java.nio) is using FileSystemProvider classes to delegate IO operations (like creating, reading and deleting files).

A provider is identified by a URI scheme. The default provider is identified by the URI scheme "file." It creates the FileSystem that provides access to the file systems accessible to the Java virtual machine. The FileSystems class defines how file system providers are located and loaded.

So, if your file is somewhere on the filesystem, there are no issues, and everything works fine. Technically, the URL returned by Application.class.getResource("").toURI() starts with file:// and contains a valid filesystem path.

Having said that, when your file "land" inside a jar file, the Application.class.getResource("").toURI() returns something more like file://{jar-location}!/ (mind the exclamation mark), which is not a valid file schema path and Java doesn't know how to handle it. An additional file system provider needs registering.

FileSystems.newFileSystem(uri, emptyMap());

Java figures out (based on URI) the scheme and registers a new file system. From now on, standard java.nio file operations can be used.

For example, if you have some files in a /webapp folder which can (but don't need to) be inside a jar file and you'd like to list them.

// Load zip specific filesystem provider when run from inside a fat-jar
URI uri = Application.class.getResource("").toURI();
if (uri.toString().contains("!")) {
    FileSystems.newFileSystem(uri, emptyMap());
}

URI rootFolder = Application.class.getResource("/webapp").toURI();
List<Path> banners = Files.list(Paths.get(rootFolder))
        .collect(Collectors.toList());

Random rand = new Random();
Path path = banners.get(rand.nextInt(banners.size()));

log.info("Random image: {}", path.getFileName());
byte[] bytes = Files.readAllBytes(path);

Installation of a new file system provider is global and should be done only once.

Related