Spring-Boot Resource Not Found when using executeable Jar
Asked Answered
M

3

6

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.

Magnetics answered 3/10, 2014 at 19:1 Comment(0)
M
20

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.

Magnetics answered 3/10, 2014 at 20:28 Comment(4)
Could you please look into this, I am having similar issue #37713835Aeroneurosis
What is specialisationResource?Netherlands
Specialisation is just an entity, SpecialisationResource just extends from Spring Resource.Magnetics
The point here is, do not use resource.getFile(), use resource.getInputStreamMagnetics
C
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.

Cartouche answered 13/5, 2016 at 6:38 Comment(1)
I wish there was a way to do this in Spring natively without relying on another outside library.Seasonal
M
1

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);
        }
    }
}
Magnetics answered 2/10, 2016 at 15:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.