Classpath resource not found when running as jar
Asked Answered
L

17

208

Having this problem both in Spring Boot 1.1.5 and 1.1.6 - I'm loading a classpath resource using an @Value annotation, which works just fine when I run the application from within STS (3.6.0, Windows). However, when I run a mvn package and then try to run the jar, I get FileNotFound exceptions.

The resource, message.txt, is in src/main/resources. I've inspected the jar and verified that it contains the file "message.txt" at the top level (same level as application.properties).

Here's the application:

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application implements CommandLineRunner {

    private static final Logger logger = Logger.getLogger(Application.class);

    @Value("${message.file}")
    private Resource messageResource;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... arg0) throws Exception {
        // both of these work when running as Spring boot app from STS, but
        // fail after mvn package, and then running as java -jar
        testResource(new ClassPathResource("message.txt"));
        testResource(this.messageResource);
    }

    private void testResource(Resource resource) {
        try {
            resource.getFile();
            logger.debug("Found the resource " + resource.getFilename());
        } catch (IOException ex) {
            logger.error(ex.toString());
        }
    }
}

The exception:

c:\Users\glyoder\Documents\workspace-sts-3.5.1.RELEASE\classpath-resource-proble
m\target>java -jar demo-0.0.1-SNAPSHOT.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.1.5.RELEASE)

2014-09-16 08:46:34.635  INFO 5976 --- [           main] demo.Application
                  : Starting Application on 8W59XV1 with PID 5976 (C:\Users\glyo
der\Documents\workspace-sts-3.5.1.RELEASE\classpath-resource-problem\target\demo
-0.0.1-SNAPSHOT.jar started by glyoder in c:\Users\glyoder\Documents\workspace-s
ts-3.5.1.RELEASE\classpath-resource-problem\target)
2014-09-16 08:46:34.640 DEBUG 5976 --- [           main] demo.Application
                  : Running with Spring Boot v1.1.5.RELEASE, Spring v4.0.6.RELEA
SE
2014-09-16 08:46:34.681  INFO 5976 --- [           main] s.c.a.AnnotationConfigA
pplicationContext : Refreshing org.springframework.context.annotation.Annotation
ConfigApplicationContext@1c77b086: startup date [Tue Sep 16 08:46:34 EDT 2014];
root of context hierarchy
2014-09-16 08:46:35.196  INFO 5976 --- [           main] o.s.j.e.a.AnnotationMBe
anExporter        : Registering beans for JMX exposure on startup
2014-09-16 08:46:35.210 ERROR 5976 --- [           main] demo.Application
                  : java.io.FileNotFoundException: class path resource [message.
txt] cannot be resolved to absolute file path because it does not reside in the
file system: jar:file:/C:/Users/glyoder/Documents/workspace-sts-3.5.1.RELEASE/cl
asspath-resource-problem/target/demo-0.0.1-SNAPSHOT.jar!/message.txt
2014-09-16 08:46:35.211 ERROR 5976 --- [           main] demo.Application
                  : java.io.FileNotFoundException: class path resource [message.
txt] cannot be resolved to absolute file path because it does not reside in the
file system: jar:file:/C:/Users/glyoder/Documents/workspace-sts-3.5.1.RELEASE/cl
asspath-resource-problem/target/demo-0.0.1-SNAPSHOT.jar!/message.txt
2014-09-16 08:46:35.215  INFO 5976 --- [           main] demo.Application
                  : Started Application in 0.965 seconds (JVM running for 1.435)

2014-09-16 08:46:35.217  INFO 5976 --- [       Thread-2] s.c.a.AnnotationConfigA
pplicationContext : Closing org.springframework.context.annotation.AnnotationCon
figApplicationContext@1c77b086: startup date [Tue Sep 16 08:46:34 EDT 2014]; roo
t of context hierarchy
2014-09-16 08:46:35.218  INFO 5976 --- [       Thread-2] o.s.j.e.a.AnnotationMBe
anExporter        : Unregistering JMX-exposed beans on shutdown
Landsturm answered 16/9, 2014 at 12:57 Comment(0)
O
311

resource.getFile() expects the resource itself to be available on the file system, i.e. it can't be nested inside a jar file. This is why it works when you run your application in STS (Spring Tool Suite) but doesn't work once you've built your application and run it from the executable jar. Rather than using getFile() to access the resource's contents, I'd recommend using getInputStream() instead. That'll allow you to read the resource's content regardless of where it's located.

Onofredo answered 16/9, 2014 at 16:14 Comment(8)
In my case, I need to provide a path to the keystore file (for the sake of a tomcat https connector).I want to wrap the jks file inside my jar. According to the above solution it is not possible. Are you familiar with additional way to do it?Scholem
I have a very similar requirement and there are no apis that let you set the keystore as part of the jar. @Modi, Were you able to find any solution?Calceolaria
Do you mean for the key store use case? Are you using Spring Boot with Tomcat?Scholem
@RobinVarghese did you find any solution to your problem. I have a similar use case to be solved. Appreciate if you have some suggestion.Girand
In case if tomcat needs keystore location to enable https one can use classpath:filename so that keystore file can be read from within the jar.Girand
Works this way try (InputStream inputStream = new ClassPathResource(pathToFile).getInputStream();) { List<String> lines = IOUtils.readLines(inputStream, StandardCharsets.UTF_8); for (String line : lines) { // PRINT LINE HERE } }Astigmatic
Does this work if I need to read a folder instead?Dianetics
@Scholem Are you able to give keystore location where jks is in jar?Paratroops
S
83

If you're using Spring framework then reading ClassPathResource into a String is pretty simple using Spring framework's FileCopyUtils:

String data = "";
ClassPathResource cpr = new ClassPathResource("static/file.txt");
try {
    byte[] bdata = FileCopyUtils.copyToByteArray(cpr.getInputStream());
    data = new String(bdata, StandardCharsets.UTF_8);
} catch (IOException e) {
    LOG.warn("IOException", e);
}
Salver answered 11/4, 2016 at 12:35 Comment(4)
Is there a constraint as to where the file must sit ? Can it be anywhere in the classpath ? Can it be at the root of the project ?Knecht
It can be anywhere in classpath.Salver
Doesn't work with jar, from IDE able to get dataUlda
@Ulda how to did you solve this problem?Wehrmacht
S
63

If you want to use a file:

ClassPathResource classPathResource = new ClassPathResource("static/something.txt");

InputStream inputStream = classPathResource.getInputStream();
File somethingFile = File.createTempFile("test", ".txt");
try {
    FileUtils.copyInputStreamToFile(inputStream, somethingFile);
} finally {
    IOUtils.closeQuietly(inputStream);
}
Sarthe answered 23/7, 2015 at 5:52 Comment(1)
for anyone needing this. If FileUtils.copyInputStreamToFile is not resolvable. You might need to add 'commons-io' as you dependency.Strephonn
G
15

When spring boot project running as a jar and need read some file in classpath, I implement it by below code

Resource resource = new ClassPathResource("data.sql");
BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
reader.lines().forEach(System.out::println);
Grassofparnassus answered 24/7, 2016 at 3:50 Comment(2)
How to read the key/value pairs, for ex in: sensitive. propertiesUlda
This is the way, working on docker jar. I use Scanner to read inputStream.Geibel
S
6

To get list of data from a src/main/resources/data folder: first of all mention your folder location in properties file as resourceLoader.file.location=data.

Then inside class declare your location:

@Value("${resourceLoader.file.location}")
@Setter
private String location;

private final ResourceLoader resourceLoader;

public void readallfilesfromresources() {
       Resource[] resources;

        try {
            resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader).getResources("classpath:" + location + "/*.json");
            for (int i = 0; i < resources.length; i++) {
                try {
                InputStream is = resources[i].getInputStream();
                byte[] encoded = IOUtils.toByteArray(is);
                String content = new String(encoded, Charset.forName("UTF-8"));
                }
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
}
Sheliasheline answered 13/2, 2018 at 10:34 Comment(0)
B
5

I've created a ClassPathResourceReader class in a Java 8 way to make easy read files from classpath

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;

import org.springframework.core.io.ClassPathResource;

public final class ClassPathResourceReader {

    private final String path;

    private String content;

    public ClassPathResourceReader(String path) {
        this.path = path;
    }

    public String getContent() {
        if (content == null) {
            try {
                ClassPathResource resource = new ClassPathResource(path);
                BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
                content = reader.lines().collect(Collectors.joining("\n"));
                reader.close();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
        return content;
    }
}

Utilization:

String content = new ClassPathResourceReader("data.sql").getContent();
Bloodhound answered 5/11, 2017 at 22:8 Comment(0)
D
4

My solution to read the Stream to String more efficiently (using StreamUtils) instead of trying to create a temp file and read text.

  @Bean
  public String readFromResource(final @Value("classpath:data/message.txt") Resource messageFile)
      throws IOException {
    return StreamUtils.copyToString(messageFile.getInputStream(), StandardCharsets.UTF_8);
  }
Dihydrostreptomycin answered 7/2, 2022 at 12:54 Comment(0)
A
3

Another important thing I noticed is that when running the application it ignores capitals in file/folders in the resources folder where it doesn't ignore it while running as a jar. Therefore, in case your file is in the resources folder under Testfolder/messages.txt

@Autowired
ApplicationContext appContext;

// this will work when running the application, but will fail when running as jar
appContext.getResource("classpath:testfolder/message.txt");

Therefore, don't use capitals in your resources or also add those capitals in your constructor of ClassPathResource:

appContext.getResource("classpath:Testfolder/message.txt");
Adin answered 26/8, 2020 at 7:24 Comment(3)
The ignore/not ignore capitals must be due to the operating system: when running the application, your Windows will ignore capitals, and when running the jar, Java is resolving the names and properly respecting capitals.Ania
This was it for me thank you!! Not the first time I've been bit by macOS's file system being case insensitive while Linux's isn'tManny
This was the best approach for me as the file could have "classpath:" in the name and ClassPathResource does not trim it.Jangro
K
1

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.

Kellie answered 13/5, 2016 at 6:35 Comment(0)
T
1

Jersey needs to be unpacked jars.

<build>  
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <requiresUnpack>
                    <dependency>
                        <groupId>com.myapp</groupId>
                        <artifactId>rest-api</artifactId>
                    </dependency>
                </requiresUnpack>
            </configuration>
        </plugin>
    </plugins>
</build>  
Tildatilde answered 21/2, 2017 at 14:37 Comment(0)
G
0

This works on a dockerized springboot JAR.

InputStream is = new ClassPathResource("randomlists/names.txt").getInputStream();
Scanner s = new Scanner(is);
ArrayList<String> names = new ArrayList<>();
while (s.hasNext()){ 
    names.add(s.next());
}
s.close();

Scanner have constructor with inputStream.

This is my way to read statics text resource files whit Springboot.

Geibel answered 28/10, 2022 at 12:17 Comment(0)
C
0

In the most voted answer, mentioned Interface Resource which extends Interface InputStreamSource, so I can replace getFile() with getInputStream() for Resource object.

example(using getFile()):

    @GetMapping(value = "/excel-download")
    public void downloadExcel(HttpServletResponse httpServletResponse) throws IOException {
        ServletOutputStream servletOutputStream = httpServletResponse.getOutputStream();

//resourceLoader is Dependency injection from org.springframework.core.io.ResourceLoader, can be injected from @Autowired
        Resource resource = resourceLoader.getResource("classpath:test.xlsx" );
        File file = resource.getFile();//throws FileNotFoundException in jar execution
        ByteArrayInputStream byteArrayInputStream =
                new ByteArrayInputStream(Files.readAllBytes(Paths.get(file.getAbsolutePath())));
        httpServletResponse.setContentType("application/octet-stream");

        String attachment = MessageFormat.format(
            "attachment; filename=\"{0}\"",
            URLEncoder.encode("test.xlsx", StandardCharsets.UTF_8.toString())
        );
        httpServletResponse.addHeader("Content-Disposition", attachment);

        IOUtils.copy(byteArrayInputStream, servletOutputStream);

        servletOutputStream.flush();
        servletOutputStream.close();
    }

example(using getInputStream()):

    @GetMapping(value = "/excel-download")
    public void downloadExcel(HttpServletResponse httpServletResponse) throws IOException {
        ServletOutputStream servletOutputStream = httpServletResponse.getOutputStream();

//resourceLoader is Dependency injection from org.springframework.core.io.ResourceLoader, can be injected from @Autowired
        Resource resource = resourceLoader.getResource("classpath:test.xlsx");
        InputStream inputStream = resource.getInputStream();//works in jar execution!
        httpServletResponse.setContentType("application/octet-stream");

        String attachment = MessageFormat.format(
            "attachment; filename=\"{0}\"",
            URLEncoder.encode("test.xlsx", StandardCharsets.UTF_8.toString())
        );
        httpServletResponse.addHeader("Content-Disposition", attachment);

        IOUtils.copy(inputStream, servletOutputStream);

        servletOutputStream.flush();
        servletOutputStream.close();
    }

The situation is, if running the project directly in any IDE or editor like IntelliJ, my both examples work but when using package manager command like mvn package, which gets a jar file containing all the dependencies we need, and we execute it with the command:

java -jar <yourJarFileName>.jar

You will find that only the 2nd example works.

This works in Spring Boot 2.7.17.

Caught answered 26/10, 2023 at 17:4 Comment(0)
T
-1
in spring boot :

1) if your file is ouside jar you can use :        

@Autowired
private ResourceLoader resourceLoader;

**.resource(resourceLoader.getResource("file:/path_to_your_file"))**

2) if your file is inside resources of jar you can `enter code here`use :

**.resource(new ClassPathResource("file_name"))**
Treasury answered 25/2, 2020 at 13:24 Comment(0)
C
-1

Based on Andy's answer I used the following to get an input streams of all YAMLs under a directory and sub-directories in resources (Note that the path passed doesn't begin with /):

private static Stream<InputStream> getInputStreamsFromClasspath(
        String path,
        PathMatchingResourcePatternResolver resolver
) {
    try {
        return Arrays.stream(resolver.getResources("/" + path + "/**/*.yaml"))
                .filter(Resource::exists)
                .map(resource -> {
                    try {
                        return resource.getInputStream();
                    } catch (IOException e) {
                        return null;
                    }
                })
                .filter(Objects::nonNull);
    } catch (IOException e) {
        logger.error("Failed to get definitions from directory {}", path, e);
        return Stream.of();
    }
}
Cartie answered 7/4, 2020 at 12:12 Comment(0)
S
-1

I was facing same error

InputStream inputStream = new ClassPathResource("filename.ext").inputStream();

this should solve FileNotFoundException while running

Schreibe answered 29/9, 2020 at 5:24 Comment(2)
Hello, this reply is correct but it is basically duplicating previous answers: 6-year old, 5-year-old etc.Sowell
Might be but, i tried exactly same and error got resolved.Schreibe
H
-1

this worked for me in a static context:

    InputStream inputStream = ClassName.class.getClassLoader().getResourceAsStream("folderName/fileName.xml");
    Reader reader = new InputStreamReader(inputStream);
    String xml = CharStreams.toString(reader);
Howdah answered 1/12, 2021 at 19:51 Comment(1)
for some reason this fails for me where ClassPathResource worksRanita
H
-1

I had faced same issue while running with java -jar <file_name.jar. When I run it in STS it was working fine. The below line helped me to resolve the issue

String templateFilePath = "/templates/Itemized_Report_2021_V1.xlsx";
InputStream resourceFile = getClass().getResourceAsStream(templateFilePath);

excel report file template path ./templates/Itemized_Report_2021_V1.xlsx java.io.FileNotFoundException: D:\STS%20WORKSPACE\app\target\classes\templates\Itemized_Report_2021_V1.xlsx (The system cannot find the path specified)

Solution:

@Override
public void exportExcelReport(HttpServletResponse response, List<Context> contexts)
        throws IOException, URISyntaxException {
    String templateFilePath = "/templates/Itemized_Report_2021_V1.xlsx";
    InputStream resourceFile = **getClass().getResourceAsStream(templateFilePath)**;
    if (resourceFile == null) {
        throw new IllegalArgumentException("Template file not found!. " + templateFilePath);
    } else {
        ServletOutputStream outputStream = null;
        try {
            log.info("Combined report with Competency excel report file template path {}", templateFilePath);
            this.workbook = new XSSFWorkbook(resourceFile);
            prepareCompetencyReport(contexts);
            prepareItemizedReport(contexts);

            // Return as octet-stream format in the rest response
            outputStream = response.getOutputStream();
            workbook.write(outputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            workbook.close();
            if (outputStream != null)
                outputStream.close();
        }
    }
}
Hydrotropism answered 9/2, 2022 at 14:29 Comment(1)
Can you edit this Answer and make sure it is formatted so we can tell the code from the explanation? Thanks.Narrows

© 2022 - 2024 — McMap. All rights reserved.