Spring-Boot Resource Not Found when using executeable Jar

31,244

Solution 1

Okay, already I found the real problem and the solution.

First, the application use the correct path to the csv files, but there is another issue when using an executable jar what I found under following link. Stackoverflow-Link

Before I come to issue with executable jar I used following solution for getting CSV-File (Issue is getFile()):

final List<String> resourceLines = FileReadUtils.readLines(specialisationResource.getFile());
for (final String line : resourceLines) {
  data.add(getNewTransientSpecialisation(line));
}

But in executeable jar I cant use my resource as file, I need to use it as stream, see provided link above. So I needed to change my code. If you prefer using native java, you can do follows:

final InputStream inputStream = specialisationResource.getInputStream();
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

String line;
while ((line = bufferedReader.readLine()) != null) {
    data.add(getNewTransientSpecialisation(line));
}

I prefer using frameworks and use apache commons like follows:

final List<String> resourceLines = IOUtils.readLines(specialisationResource.getInputStream());
for (final String line : resourceLines) {
    data.add(getNewTransientSpecialisation(line));
}

So just remember, don't use File() for getting resource, always use stream do avoid that issue from beginning :-)

Hope that helps someone.

Solution 2

I encountered this limitation too and created this library to overcome the issue: spring-boot-jar-resources

It basically allows you to register a custom ResourceLoader with Spring Boot that extracts the classpath resources from the JAR as needed, transparently:

new SpringApplicationBuilder()
        .sources(Application.class)
        .resourceLoader(new JarResourceLoader())
        .run(args);

With that ResourceLoader you can do resource.getFile() on any classpath resource.

Solution 3

Now I needed to find xmlFiles into a resource folder from a JAR and facing similar problems described here already. I would like to share my findings and how I got it work, maybe it is helpful.

In a jar I have "db-input" folder under src/main/resources with any number for xml-Files for DB-Input. My application is Spring-Based:

@Component
public class DatabaseInitializer implements InitializingBean {
    @Autowired DomainConfigurationRepository repository;
    @Autowired MarshallerService marshallerService;
    @Autowired ApplicationContext context;

    @Override
    public void afterPropertiesSet() throws Exception {
        final Resource[] resources = context.getResources("classpath*:db-input/*");
        final Set<String> filePaths = findInputFileNames(resources);
        final Set<DomainConfiguration> configurations = createConfigurations(filePaths);
        repository.save(configurations);
    }

    private  Set<DomainConfiguration> createConfigurations(final Set<String> filePaths) throws Exception {
        final Set<DomainConfiguration> configurations = new HashSet<>();
        for(final String path : filePaths){
            final Resource resource = context.getResource(path);
            final DomainConfigurationXO xoConfiguration = marshallerService.unmarshal(resource.getInputStream());
            final DomainConfiguration configuration = PopulationUtils.getPopulatedConfiguration(xoConfiguration);
            configurations.add(configuration);
        }
        return configurations;
    }

    public Set<String> findInputFileNames(final Resource[] inputDirectoryResources) throws IOException {
        return Arrays.stream(inputDirectoryResources)
                .map(resource -> extractURI(resource))
                .collect(Collectors.toSet());
    }

    private String extractURI(Resource resource){
        try {
            return resource.getURI().toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
Share:
31,244
Michael Hegner
Author by

Michael Hegner

Updated on October 03, 2020

Comments

  • Michael Hegner
    Michael Hegner over 3 years

    again I face a strange issue and hope someone here can help.

    I have a spring boot backend module, what works in eclipse well and application is executeable when starting main in application.java. Everything fine.

    My application makes import of example data to database using csv-files what is included in src/main/resources folder. As mentioned, when starting in eclipse everything works.

    Now I would like to execute it as executable jar, the application begins to start and then it failed to start, because it cannot find the csv files. The path what it prints out, where it looked for the files, is correct and the csv files are in the jar included.

    The Pom of the module looks like follows:

    <project>
        <modelVersion>4.0.0</modelVersion>
    
        <parent>
            <groupId>at.company.bbsng</groupId>
            <artifactId>bbsng-import</artifactId>
            <version>0.1.0-SNAPSHOT</version>
        </parent>
    
        <artifactId>bbsng-import-backend</artifactId>
        <name>bbsng-import-backend</name>
    
        <properties>
            <start-class>at.company.bbsng.dataimport.Application</start-class>
        </properties>
    
    
        <dependencies>
    
            <!-- SPRING ... -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-batch</artifactId>
                <!-- EXCLUDE LOGBACK AND USE LOG4J -->
                <exclusions>
                    <exclusion>
                        <artifactId>spring-boot-starter-logging</artifactId>
                        <groupId>org.springframework.boot</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-log4j</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <!-- COMMONS ... -->
    
            ...
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <goals>
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    Path to csv-files are configured in propery files as follows:

    # EXAMPLE PATH
    csv.path=config/csv/
    

    The part of java config file is as follows:

      ...
    
      @Value("${csv.path}")
      private String csvExamplePath;
    
      @Bean
      public Resource addressResource() {
        return new ClassPathResource(csvExamplePath + CSV_ADDRESS);
      }
    
        ...
    

    In the jar the files are located at path

    \config\csv\
    

    Stacktrace:

    Caused by: java.io.FileNotFoundException: class path resource [config/csv/Company.csv] cannot be resolved to absolute file path because it does not reside in th
    e file system: jar:file:/C:/Development/Projekte/bbsng/trunk/import/backend/target/bbsng-import-backend-0.1.0-SNAPSHOT.jar!/config/csv/Company.csv
            at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:207)
            at org.springframework.core.io.AbstractFileResolvingResource.getFile(AbstractFileResolvingResource.java:52)
            at at.compax.bbsng.dataimport.app.source.company.CompanyGenerator.init(CompanyGenerator.java:28)
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
            at java.lang.reflect.Method.invoke(Unknown Source)
            at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java
    

    Again, the application works as expected when starting it from eclipse, only executable jar complains about missing csv-files, what are in jar already.

    Any clue would be great.