Resources and Configuration
Most real applications need configuration files — database URLs, API keys, logging settings, or feature flags. Maven has dedicated conventions for where these files live, how they get packaged, and how to swap them out for different environments. This chapter covers both resource handling and environment-specific builds using Maven profiles.
Prerequisites
- A working Maven project
- Basic understanding of Java I/O and class loading
The Resources Directory
Maven reserves src/main/resources for files that are not source code but still need to end up in the final artifact. Common examples include:
.propertiesand.yamlconfiguration files- SQL migration scripts
- Logback or Log4j configuration
- HTML templates
- Internationalization bundles
The src/test/resources directory serves the same purpose for test code. Files placed there are available during tests but are not included in the production artifact.
How Resources Are Packaged
During the process-resources phase, Maven copies everything from src/main/resources into target/classes. When the project is packaged into a JAR, those files sit at the root of the archive alongside compiled .class files.
my-project/
src/
main/
java/ # Compiled to target/classes
resources/ # Also copied to target/classes
application.properties
logback.xmlReading Resources at Runtime
Because resources end up on the classpath, you load them with ClassLoader rather than file system paths:
package com.example;
import java.io.InputStream;
import java.util.Properties;
public class ConfigLoader {
public Properties loadConfig() throws Exception {
// Obtain an input stream from the classpath
InputStream stream = getClass()
.getClassLoader()
.getResourceAsStream("application.properties");
Properties props = new Properties();
// Load key-value pairs from the stream
props.load(stream);
return props;
}
}Create src/main/resources/application.properties:
# Application configuration
app.name=My Application
app.version=1.0.0
app.environment=developmentTip
Leading Slashes
getResourceAsStream("application.properties") works because the file lands at the root of the classpath. Do not prefix with a slash when using the class loader directly. If you call getClass().getResourceAsStream(), a leading slash is required: "/application.properties".
Resource Filtering and Variable Substitution
Maven can replace placeholders in resource files with values from pom.xml properties during the build. This is useful for embedding the project version or build timestamp into configuration files.
Enabling Filtering
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<!-- Enable variable substitution in this directory -->
<filtering>true</filtering>
</resource>
</resources>
</build>Using Placeholders
With filtering enabled, you can reference Maven properties using ${...} syntax inside resource files:
# application.properties (in src/main/resources)
app.name=${project.name}
app.version=${project.version}
build.timestamp=${maven.build.timestamp}The maven.build.timestamp property uses the build start time. Its format defaults to yyyy-MM-dd'T'HH:mm:ss'Z'. You can customize it:
<properties>
<!-- Custom timestamp format -->
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
</properties>Warning
Filtering Binary Files
Do not enable filtering on directories that contain binary files such as images or certificates. Maven treats them as text and may corrupt them. Keep binaries in a separate resource directory with <filtering>false</filtering>.
Working with YAML
YAML has become the dominant configuration format for Spring Boot and many modern frameworks. Maven treats .yml and .yaml files the same way as .properties — simply place them in src/main/resources.
# application.yml (in src/main/resources)
server:
port: 8080
host: localhost
database:
url: jdbc:postgresql://localhost:5432/mydb
username: admin
password: secretIf you are not using a framework that parses YAML automatically, add a parser dependency such as SnakeYAML:
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
</dependency>Then load the file:
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.util.Map;
public class YamlLoader {
public Map<String, Object> loadYaml() {
// Load the YAML file from the classpath
InputStream stream = getClass()
.getClassLoader()
.getResourceAsStream("application.yml");
Yaml yaml = new Yaml();
// Parse into a nested map structure
return yaml.load(stream);
}
}Profiles for Multi-Environment Builds
Hardcoding environment-specific values in application.properties is a maintenance headache. Maven profiles let you define different configurations for development, testing, and production without duplicating your entire POM.
Defining Profiles
Add a <profiles> section at the bottom of your pom.xml:
<profiles>
<profile>
<id>dev</id>
<activation>
<!-- Active by default if no other profile is specified -->
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<db.url>jdbc:h2:mem:testdb</db.url>
<db.username>sa</db.username>
<db.password></db.password>
<log.level>DEBUG</log.level>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<db.url>jdbc:postgresql://prod-db:5432/mydb</db.url>
<db.username>${env.DB_USER}</db.username>
<db.password>${env.DB_PASS}</db.password>
<log.level>WARN</log.level>
</properties>
</profile>
</profiles>Activating a Profile
Profiles are activated with the -P flag:
# Build with the production profile
mvn package -PprodWhen a profile is active, its properties override any properties with the same name defined in the base POM. You can reference these properties in resource files (with filtering enabled) or directly in the POM.
Profile-Specific Resources
You can also keep entirely separate resource directories per profile:
<profile>
<id>prod</id>
<build>
<resources>
<resource>
<directory>src/main/resources-prod</directory>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
</profile>With this setup, files in src/main/resources-prod/ take precedence over files in src/main/resources/ when the prod profile is active. This lets you ship entirely different configuration files for each environment.
Profile-Specific Dependencies and Plugins
Profiles can also add dependencies or reconfigure plugins:
<profile>
<id>integration-tests</id>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.1.2</version>
</plugin>
</plugins>
</build>
</profile>Tip
Keep Profiles Lean
Profiles are powerful, but they can make builds hard to reason about if overused. Store environment differences in properties or resource files when possible. Reserve profile-specific dependencies and plugins for truly optional features like integration testing or code quality checks.
FAQ
Why does getResourceAsStream return null?
The most common cause is a path mismatch. Remember that the path is relative to the classpath root. If your file is at src/main/resources/config/app.properties, load it with "config/app.properties". Also verify that Maven has run process-resources so the file exists in target/classes.
Can I exclude certain resources from the JAR?
Yes. Use <excludes> inside the <resource> element:
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>**/*.local</exclude>
</excludes>
</resource>How do I read the current Maven version inside my Java code?
Enable filtering on a properties file and reference ${project.version}. At runtime, load that properties file and read the value. Hard-coding the version in Java source is brittle and easy to forget during releases.
What is the difference between src/main/resources and src/main/java?
src/main/java contains compilable source code that Maven feeds to the Java compiler. src/main/resources contains non-code files that Maven copies verbatim to the classpath. Both end up in target/classes and the final JAR, but only .java files are compiled.
Should I commit environment-specific profiles to version control?
Profiles themselves, yes — they are part of the build. But never commit secrets such as passwords or API keys. Use environment variables (referenced as ${env.VAR_NAME}) or external secret management tools, and keep the profile structure in pom.xml while the sensitive values live outside it.