Read a packaged classpath resource through its input stream, not as a filesystem path. A file under src/main/resources is copied to the application’s classpath during the build. It may be a normal file while running from an IDE, but it becomes an entry inside the executable JAR after packaging. The same stream-based code works in both places.
1. Add a File to the Classpath
An order application includes a fixed email template with its release. Save it at src/main/resources/mail/order-confirmation.txt:
Hello {name}, your order is confirmed.
The build copies this file to target/classes/mail/order-confirmation.txt. The runtime location is mail/order-confirmation.txt relative to the classpath root. The src/main/resources segment is a build-time directory, so it does not belong in the lookup string. A packaged template is suitable for text that changes with an application release. If operators must edit the file without rebuilding the application, store it outside the JAR and configure an external location instead.
The sample uses spring-boot-starter and the Spring Boot starter parent. It needs no web starter, Lombok, or file utility library to read this resource.
2. Inject the Resource and Open a Stream
Spring’s Resource interface represents a location without promising that it is a physical File. The template reader asks Spring to inject the classpath resource, then opens a stream and decodes its bytes as UTF-8:
package com.howtodoinjava.resources;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
@Component
public class TemplateReader {
private final Resource template;
public TemplateReader(@Value("classpath:mail/order-confirmation.txt") Resource template) {
this.template = template;
}
public String messageFor(String name) throws IOException {
try (var input = template.getInputStream()) {
String text = new String(input.readAllBytes(), StandardCharsets.UTF_8);
return text.replace("{name}", name).trim();
}
}
}
The classpath: prefix tells Spring where to look. getInputStream() opens the resource whether its bytes come from target/classes or a JAR entry. The try-with-resources block closes the stream after reading. This example reads a small text template in one operation; for a large resource, process the stream incrementally rather than loading all bytes into memory.
3. Call the Reader from the Application
The main class runs the reader once and prints the rendered message. CommandLineRunner runs after the Spring application context is ready, so Spring has already created and injected TemplateReader:
package com.howtodoinjava.resources;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ResourceApplication implements CommandLineRunner {
private final TemplateReader templates;
private final ResourceComparisons comparisons;
public ResourceApplication(TemplateReader templates, ResourceComparisons comparisons) {
this.templates = templates;
this.comparisons = comparisons;
}
public static void main(String[] args) {
SpringApplication.run(ResourceApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println(templates.messageFor("Lokesh"));
System.out.println("ClassPathResource: " + comparisons.withClassPathResource());
System.out.println("ResourceLoader: " + comparisons.withResourceLoader());
System.out.println("Physical file available: " + comparisons.hasPhysicalFile());
}
}
Run from the project directory:
mvn spring-boot:run
The relevant output is:
Hello Lokesh, your order is confirmed.
The source file contains {name}. The result confirms that the resource was found, decoded, and passed through messageFor() before printing.
4. Choose ClassPathResource or ResourceLoader When Appropriate
Constructor injection works when one component always needs the same template. A direct ClassPathResource lookup is useful when the class itself chooses the classpath path. ResourceLoader fits a Spring component that receives or selects a resource location at runtime. The sample puts the two forms side by side:
package com.howtodoinjava.resources;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
@Component
public class ResourceComparisons {
private final ResourceLoader loader;
public ResourceComparisons(ResourceLoader loader) {
this.loader = loader;
}
public String withClassPathResource() throws IOException {
return read(new ClassPathResource("mail/order-confirmation.txt"));
}
public String withResourceLoader() throws IOException {
return read(loader.getResource("classpath:mail/order-confirmation.txt"));
}
public boolean hasPhysicalFile() {
try {
new ClassPathResource("mail/order-confirmation.txt").getFile();
return true;
} catch (IOException ex) {
return false;
}
}
private String read(Resource resource) throws IOException {
try (var input = resource.getInputStream()) {
return new String(input.readAllBytes(), StandardCharsets.UTF_8).trim();
}
}
}
The ClassPathResource constructor takes a path relative to the classpath root, without classpath:. The ResourceLoader.getResource() method recognizes the classpath: prefix. Both return a Resource, so the same stream-reading method handles either one. The hasPhysicalFile() check exists only to expose the development-versus-JAR difference in Section 5; use the stream method for application reads.
| Need | Use | Location string |
|---|---|---|
| A fixed resource in a Spring component | Inject Resource with @Value | classpath:mail/order-confirmation.txt |
| A direct classpath lookup | ClassPathResource | mail/order-confirmation.txt |
| A location chosen at runtime | ResourceLoader | classpath:mail/order-confirmation.txt |
These approaches choose a resource differently but all use getInputStream() to read it. The Spring Framework resource reference documents the prefixes and resource implementations.
5. Verify the Packaged JAR Behavior
Package and run the same sample:
mvn package
java -jar target/spring-boot-resource-demo-1.0.0.jar
The sample reads the template through all three access paths and also checks whether getFile() can return a physical file. The relevant output from our Spring Boot 4.1.2 run was:
Hello Lokesh, your order is confirmed.
ClassPathResource: Hello {name}, your order is confirmed.
ResourceLoader: Hello {name}, your order is confirmed.
Physical file available: false
When we ran mvn spring-boot:run from the project directory, the last line was Physical file available: true. That difference is the trap: getFile() can appear correct during development and fail once the classpath entry is inside a JAR. The stream reads succeeded in both runs. Do not wrap a classpath URL in new File(…) or rely on Resource.getFile() for a packaged resource.
6. Handle Missing Resources and Encoding
If a template path is wrong, fail with a message that identifies the requested resource. For a dynamically selected path, a method in ResourceComparisons can check existence before opening it:
Resource template = resourceLoader.getResource("classpath:mail/missing.txt");
if (!template.exists()) {
throw new IllegalArgumentException("Missing classpath template: mail/missing.txt");
}
exists() is a useful early check, but the stream operation can still fail with an IOException. Keep the read in a try-with-resources block and either handle or propagate that exception. Decode text with a known charset such as StandardCharsets.UTF_8; the platform default can produce different characters on another machine. For binary files, keep the bytes rather than converting them to a String.
7. FAQs
7.1. Should the lookup include src/main/resources?
No. That is the source directory name. Once the build copies the file to the classpath, its resource path is mail/order-confirmation.txt. Add classpath: when using a location string that Spring resolves, such as the @Value or ResourceLoader examples.
7.2. Can an application modify a resource inside its JAR?
Treat a packaged resource as read-only application content. Put data that must change at runtime in an external file, database, or object store. Rebuild and redeploy the JAR when the bundled template itself changes.
8. Conclusion
Use a stream for a file bundled under src/main/resources. Inject a fixed Resource into a Spring component, or use ClassPathResource or ResourceLoader for other lookup needs. Verify the packaged JAR, because a filesystem-only implementation can pass during development and fail after deployment.
Happy Learning!
The complete runnable example on GitHub contains the template, all three lookup forms, and the development-versus-JAR check.
I have file present in src/main/webapp/WEB-INF/templates, but using below code, could not locate file and also couldn’t locate file generated in war file of the application.
InputStream inStream = servletContext.getResourceAsStream(/WEB-INF/templates/Sample.xlsx);
When I log inStream it is showing as null. Could you please help.
If the file is not present in the generated war then please check the build script (pom.xml if you are using Maven) to check why it is not copied to war.