Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring-Boot Resource Not Found when using executeable Jar

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.

like image 364
Michael Hegner Avatar asked Oct 03 '14 19:10

Michael Hegner


2 Answers

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.

like image 111
Michael Hegner Avatar answered Sep 21 '22 23:09

Michael Hegner


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.

like image 38
Ulises Avatar answered Sep 22 '22 23:09

Ulises