File inside jar is not visible for spring
Asked Answered
F

13

166

All

I created a jar file with the following MANIFEST.MF inside:

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.3
Created-By: 1.6.0_25-b06 (Sun Microsystems Inc.)
Main-Class: my.Main
Class-Path: . lib/spring-core-3.2.0.M2.jar lib/spring-beans-3.2.0.M2.jar

In its root there is a file called my.config which is referenced in my spring-context.xml like this:

<bean id="..." class="...">
    <property name="resource" value="classpath:my.config" />
</bean>

If I run the jar, everything looks fine escept the loading of that specific file:

Caused by: java.io.FileNotFoundException: class path resource [my.config] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/D:/work/my.jar!/my.config
        at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:205)
    at org.springframework.core.io.AbstractFileResolvingResource.getFile(AbstractFileResolvingResource.java:52)
    at eu.stepman.server.configuration.BeanConfigurationFactoryBean.getObject(BeanConfigurationFactoryBean.java:32)
    at eu.stepman.server.configuration.BeanConfigurationFactoryBean.getObject(BeanConfigurationFactoryBean.java:1)
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:142)
    ... 22 more
  • classes are loaded the from inside the jar
  • spring and other dependencies are loaded from separated jars
  • spring context is loaded (new ClassPathXmlApplicationContext("spring-context/applicationContext.xml"))
  • my.properties is loaded into PropertyPlaceholderConfigurer ("classpath:my.properties")
  • if I put my .config file outside the file system, and change the resource url to 'file:', everything seems to be fine...

Any tips?

Foregoing answered 14/2, 2013 at 14:17 Comment(0)
S
293

If your spring-context.xml and my.config files are in different jars then you will need to use classpath*:my.config?

More info here

Also, make sure you are using resource.getInputStream() not resource.getFile() when loading from inside a jar file.

Skindeep answered 14/2, 2013 at 14:24 Comment(19)
They are in the same jar, but I tried your solution with the same result: java.io.FileNotFoundException: class path resource [classpath*:my.config] cannot be resolved to URL because it does not existForegoing
I tried to move the file inside the 'conf' directory, but it did not help. (The documentation says: "This means that a pattern like "classpath*:*.xml" will not retrieve files from the root of jar files but rather only from the root of expanded directories. This originates from a limitation in the JDK's ClassLoader.getResources() method which only returns file system locations for a passed-in empty string (indicating potential roots to search).")Foregoing
Weird thing is: my.properties is loaded by PropertyPlaceholderConfigurer and log4j.properties has been found by sl4j... and they are in the same location... Just this file...Foregoing
Looking again, some of your calling code (possibly BeanConfigurationFactoryBean) is trying to load a java.io.File. File refers to files on the filesystem, which entries in a jar are not. The calling code should be using resource.getInputStream instead to load out of a jar.Skindeep
...and THIS is the answer... Thanks! Inside a jar do not use resource.getFile() :-)Foregoing
any chance there is a "why?" behind not using getFile() inside of a jar? Is it simply that the file is inside of the Jar and hence the "file" is the jar file??Stets
That's it. A java.io.File represents a file on the file system, in a directory structure. The Jar is a java.io.File. But anything within that file is beyond the reach of java.io.File. As far as java is concerned, until it is uncompressed, a class in jar file is no different than a word in a word document.Skindeep
When the file is into a jar, you must use getClass().getResourceAsStream()Unprofitable
In resource.getInputStream() , what is resource over here?Viv
Can anybody post complete code to read/load a file which is inside a jar?Viv
For anyone wondering, I used Apache commons IOUtils.toString(inputStream, "UTF-8") to retrieve the text from the InputStream - very cleanPoacher
@ShashiRanjan file contents for a file within a JAR must be read via java.io.InputStream of the file (via java.io.File is not possible when the file is within a JAR). Once you accept that, then it makes sense why this Answer on a similar question (using InputStream) has the code to do so.Musser
Instead of resource.getInpuStream(), you can also use resource.getURL()Globulin
@ValeriyK. hands down most underrated comment lol :D, this fixed my issue, instead of spring framework's org.springframework.util.ResourceUtils.getFile() i Used org.springframework.util.ResourceUtils.getUrl() and no more exception...Capita
Am I the only one scratching my head on why someone would store a file in jar to then painfully pull into your code as an InputStream? What is the ultimate objective? Would perhaps serialization be the better answer? As a framework, Spring should take the responsibility of either 1) supporting getFile() and 2) not allow any nonsensical workaround like getInputStrrem() . Personally, I would roll my own code for this as a service. That way I can provide for any number of options and prioritize.Bengali
This advice is invaluable. I read as a file and the JAR can't see the folder and files. This is solved with using the resource.getInputStream()Aftmost
Hi @Capita do you know how to create a file while my spring application is in the docker container? If you can answer my question, thanks!!Subcortex
@Subcortex huh, you can ask that question as its own question not via comments...Capita
In my case this was the solution: Resource resource = new ClassPathResource("path"); //In my case the file was in resource/reports. "path" = reports/image.png InputStream inputStream = resource.getInputStream();Harshman
I
84

In the spring jar package, I use new ClassPathResource(filename).getFile(), which throws the exception:

cannot be resolved to absolute file path because it does not reside in the file system: jar

But using new ClassPathResource(filename).getInputStream() will solve this problem. The reason is that the configuration file in the jar does not exist in the operating system's file tree,so must use getInputStream().

Italy answered 2/7, 2018 at 8:24 Comment(2)
this is the correct solution. ThanksKhamsin
Hi do you know how to create a file while my spring application is in the docker container? If you can answer my question, thanks!!Subcortex
T
70

I know this question has already been answered. However, for those using spring boot, this link helped me - https://smarterco.de/java-load-file-classpath-spring-boot/

However, the resourceLoader.getResource("classpath:file.txt").getFile(); was causing this problem and sbk's comment:

That's it. A java.io.File represents a file on the file system, in a directory structure. The Jar is a java.io.File. But anything within that file is beyond the reach of java.io.File. As far as java is concerned, until it is uncompressed, a class in jar file is no different than a word in a word document.

helped me understand why to use getInputStream() instead. It works for me now!

Thanks!

Thesis answered 26/1, 2017 at 16:5 Comment(1)
Hi do you know how to create a file while my spring application is in the docker container? If you can answer my question, thanks!!Subcortex
M
16

The error message is correct (if not very helpful): the file we're trying to load is not a file on the filesystem, but a chunk of bytes in a ZIP inside a ZIP.

Through experimentation (Java 11, Spring Boot 2.3.x), I found this to work without changing any config or even a wildcard:

var resource = ResourceUtils.getURL("classpath:some/resource/in/a/dependency");
new BufferedReader(
  new InputStreamReader(resource.openStream())
).lines().forEach(System.out::println);
Muco answered 6/11, 2020 at 16:4 Comment(2)
This was the exact problem! Thanks!Gorgeous
Thanks! it was so helpful! required when running on Liberty serverEra
G
3

I was having an issue recursively loading resources in my Spring app, and found that the issue was I should be using resource.getInputStream. Here's an example showing how to recursively read in all files in config/myfiles that are json files.

Example.java

private String myFilesResourceUrl = "config/myfiles/**/";
private String myFilesResourceExtension = "json";

ResourceLoader rl = new ResourceLoader();

// Recursively get resources that match. 
// Big note: If you decide to iterate over these, 
// use resource.GetResourceAsStream to load the contents
// or use the `readFileResource` of the ResourceLoader class.
Resource[] resources = rl.getResourcesInResourceFolder(myFilesResourceUrl, myFilesResourceExtension);

// Recursively get resource and their contents that match. 
// This loads all the files into memory, so maybe use the same approach 
// as this method, if need be.
Map<Resource,String> contents = rl.getResourceContentsInResourceFolder(myFilesResourceUrl, myFilesResourceExtension);

ResourceLoader.java

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.StreamUtils;

public class ResourceLoader {
  public Resource[] getResourcesInResourceFolder(String folder, String extension) {
    ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    try {
      String resourceUrl = folder + "/*." + extension;
      Resource[] resources = resolver.getResources(resourceUrl);
      return resources;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public String readResource(Resource resource) throws IOException {
    try (InputStream stream = resource.getInputStream()) {
      return StreamUtils.copyToString(stream, Charset.defaultCharset());
    }
  }

  public Map<Resource, String> getResourceContentsInResourceFolder(
      String folder, String extension) {
    Resource[] resources = getResourcesInResourceFolder(folder, extension);

    HashMap<Resource, String> result = new HashMap<>();
    for (var resource : resources) {
      try {
        String contents = readResource(resource);
        result.put(resource, contents);
      } catch (IOException e) {
        throw new RuntimeException("Could not load resource=" + resource + ", e=" + e);
      }
    }
    return result;
  }
}
Gnawing answered 18/11, 2019 at 16:18 Comment(1)
Hi do you know how to create a file while my spring application is in the docker container? If you can answer my question, thanks!!Subcortex
P
2

I had similar problem when using Tomcat6.x and none of the advices I found was helping. At the end I deleted work folder (of Tomcat) and the problem gone.

I know it is illogical but for documentation purpose...

Porfirioporgy answered 24/11, 2014 at 12:49 Comment(0)
U
2

I was having an issue more complex because I have more than one file with same name, one is in the main Spring Boot jar and others are in jars inside main fat jar. My solution was getting all the resources with same name and after that get the one I needed filtering by package name. To get all the files:

ResourceLoader resourceLoader = new FileSystemResourceLoader();
final Enumeration<URL> systemResources = resourceLoader.getClassLoader().getResources(fileNameWithoutExt + FILE_EXT);
Unaccustomed answered 29/4, 2020 at 8:50 Comment(0)
A
2

For kotlin users, I solved it like this:

val url = ResourceUtils.getURL("classpath:$fileName")
val response = url.openStream().bufferedReader().readText()
Auk answered 7/10, 2021 at 20:54 Comment(1)
This worked for me. If a file is inside a folder you have to reference the folder then the file name i.e /data/myfile.jsonIngeingeberg
B
1

The answer by @sbk is the way we should do it in spring-boot environment (apart from @Value("${classpath*:})), in my opinion. But in my scenario it was not working if the execute from standalone jar..may be I did something wrong.

But this can be another way of doing this,

InputStream is = this.getClass().getClassLoader().getResourceAsStream(<relative path of the resource from resource directory>);
Blew answered 5/11, 2019 at 8:56 Comment(1)
Hi do you know how to create a file while my spring application is in the docker container? If you can answer my question, thanks!!Subcortex
K
1

In Spring boot 1.5.22.RELEASE Jar packaging this worked for me:

InputStream resource = new ClassPathResource("example.pdf").getInputStream();
            

"example.pdf" is in src/main/resources.

And then to read it as byte[]

FileCopyUtils.copyToByteArray(resource);
Kandacekandahar answered 11/11, 2021 at 18:55 Comment(1)
Hi do you know how to create a file while my spring application is in the docker container? If you can answer my question, thanks!!Subcortex
H
0

I had the same issue, ended up using the much more convenient Guava Resources:

Resources.getResource("my.file")
Hatchment answered 3/6, 2020 at 10:40 Comment(0)
A
0

While this is a very old thread, but I also faced the same issue while adding FCM in a Spring Boot Application.

In development, the file was getting opened and no errors but when I deployed the application to AWS Elastic beanstalk , the error of FileNotFoundException was getting thrown and FCM was not working.

So here's my solution to get it working on both development env and jar deployment production.

I have a Component class FCMService which has a method as follows:

  @PostConstruct
  public void initialize() {

    log.info("Starting FCM Service");
    InputStream inputStream;

    try {

      ClassPathResource resource = new ClassPathResource("classpath:fcm/my_project_firebase_config.json");
      URL url = null;
      try {
        url = resource.getURL();
      } catch (IOException e) {

      }

      if (url != null) {
        inputStream = url.openStream();
      } else {
        File file = ResourceUtils.getFile("classpath:fcm/my_project_firebase_config.json");
        inputStream = new FileInputStream(file);
      }

      FirebaseOptions options = FirebaseOptions.builder().setCredentials(GoogleCredentials.fromStream(inputStream))
          .build();
      FirebaseApp.initializeApp(options);

      log.info("FCM Service started");

    } catch (IOException e) {
      log.error("Error starting FCM Service");
      e.printStackTrace();
    }

  }

Hope this helps someone looking for a quick fix with implementing FCM.

Agalloch answered 10/8, 2021 at 13:46 Comment(0)
K
0

Can be handled like:

var serviceAccount = ClassLoader.getSystemResourceAsStream(FB_CONFIG_FILE_NAME);

FirebaseOptions options = new FirebaseOptions.Builder()
  .setCredentials(GoogleCredentials.fromStream(serviceAccount))
  .build();

Where FB_CONFIG_FILE_NAME is name of file in your 'resources' folder.

Kelsey answered 20/4, 2022 at 18:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.