Maven resources and filtering
last modified November 13, 2026
In this article we work with resources in Maven. We see how resource files
get into the build output, substitute Maven properties in them with
filtering, read values from filter files, switch to @...@
delimiters, protect binary files, and copy resources to other directories.
The examples use Maven 3.10, Resources plugin 3.5.0, and Java 25.
Resources are the files of a project that are not compiled but
are needed at runtime: configuration files, templates, messages, images,
or keystores. Maven keeps them in src/main/resources, and test
resources in src/test/resources. The Maven Resources plugin
copies them to target/classes and
target/test-classes in the process-resources and
process-test-resources phases. From there they get on the
classpath and into the JAR.
The project
Our application reads a properties file from the classpath and prints its content.
app.name=${project.name}
app.version=${project.version}
app.greeting=${greeting}
build.java=${java.version}
log.dir=${user.home}/logs
db.url=${db.url}
The values contain placeholders in the ${...} syntax of
Maven properties. The first four refer to the project model, a property
defined in the POM, and a Java system property. The log.dir
value is meant to be expanded by the application at runtime, and
db.url refers to a property that does not exist yet.
package com.example.appinfo;
import java.io.IOException;
import java.util.Properties;
public class Main {
public static void main(String[] args) throws IOException {
var props = new Properties();
try (var in = Main.class.getResourceAsStream("/app.properties")) {
props.load(in);
}
props.stringPropertyNames().stream().sorted()
.forEach(key -> System.out.printf("%-12s %s%n", key, props.getProperty(key)));
}
}
The resource is read with getResourceAsStream; the leading
slash makes the path absolute, relative to the root of the classpath. The
properties are printed in sorted order.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>appinfo</artifactId>
<version>1.2.0</version>
<name>App info</name>
<properties>
<maven.compiler.release>25</maven.compiler.release>
<greeting>Hello from the POM</greeting>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.5.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.4</version>
<configuration>
<mainClass>com.example.appinfo.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
The greeting property is defined in the POM. The
Exec plugin runs the main class with
mvn exec:java.
Without any configuration, the plugin copies the resources as they are.
$ mvn compile
--- resources:3.5.0:resources (default-resources) @ appinfo ---
Copying 1 resource from src/main/resources to target/classes
BUILD SUCCESS
$ cat target/classes/app.properties
app.name=${project.name}
app.version=${project.version}
app.greeting=${greeting}
build.java=${java.version}
log.dir=${user.home}/logs
db.url=${db.url}
Filtering
Filtering is enabled per resource directory. To configure it, we declare
the resource directory in the build section.
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
Declaring a resources element replaces the default, so the
directory must be listed even though it is the default one. With
filtering set to true, the plugin replaces
placeholders with property values while copying.
The project properties, the POM property, and the Java version are
substituted. A placeholder with no matching property is left as it is.
Properties given with -D on the command line take part in
filtering and beat the POM properties. The log.dir value shows
the danger of filtering: user.home is a Java system property,
so the home directory of the build machine was baked into the file
instead of being resolved on the machine that runs the application.
$ mvn -q clean compile exec:java
app.greeting Hello from the POM
app.name App info
app.version 1.2.0
build.java 25.0.3
db.url ${db.url}
log.dir /home/user/logs
$ mvn -q clean compile exec:java -Dgreeting=Hi -Ddb.url=jdbc:h2:mem:test
app.greeting Hi
app.name App info
app.version 1.2.0
build.java 25.0.3
db.url jdbc:h2:mem:test
log.dir /home/user/logs
Filter files
Values can also come from separate properties files called filters. We create one file per environment.
db.url=jdbc:h2:mem:devdb greeting=Hello from dev.properties
The development file defines the database URL and a greeting.
db.url=jdbc:postgresql://db.example.com/app
The production file defines only the database URL.
<filters>
<filter>src/main/filters/${env}.properties</filter>
</filters>
The filters element lists filter files. The file name uses the
env property, which the POM sets to dev, so the
environment can be switched with -Denv=prod.
Maven profiles are another way to select
the file.
The database URL comes from the selected file. The greeting from
dev.properties is ignored: properties from the POM and the
command line take precedence over filter files, which only fill in values
that are not defined elsewhere. A missing filter file fails the build.
$ mvn -q clean compile exec:java app.greeting Hello from the POM app.name App info app.version 1.2.0 build.java 25.0.3 db.url jdbc:h2:mem:devdb log.dir /home/user/logs $ mvn -q clean compile exec:java -Denv=prod db.url jdbc:postgresql://db.example.com/app $ mvn -q clean compile -Denv=test Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.5.0:resources (default-resources) on project appinfo: Error loading property file '/home/user/project/appinfo/src/main/filters/test.properties' -> [Help 1]
Binary files
We add a PKCS12 keystore, server.p12, to the resources and
build with filtering enabled for the whole directory.
Filtering reads files as text in the project encoding, UTF-8. The
keystore is not valid UTF-8, so the build fails. The plugin skips common
image formats such as png, jpeg,
gif, bmp, and ico, but not
keystores, fonts, PDF files, or archives. More extensions can be added
with the nonFilteredFileExtensions parameter. A
cleaner solution is to filter only the files that need it.
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/*.properties</exclude>
</excludes>
</resource>
</resources>
The same directory is declared twice. The first declaration filters the properties files only, the second copies all other files unchanged.
Each declaration copies one file, and the keystore in
target/classes is identical to the original.
$ mvn clean compile Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.5.0:resources (default-resources) on project appinfo: filtering /home/user/project/appinfo/src/main/resources/server.p12 to /home/user/project/appinfo/target/classes/server.p12 failed with MalformedInputException: Input length = 1 -> [Help 1] $ mvn clean compile Copying 1 resource from src/main/resources to target/classes Copying 1 resource from src/main/resources to target/classes BUILD SUCCESS $ cmp src/main/resources/server.p12 target/classes/server.p12 && echo identical identical
Custom delimiters
The ${...} syntax is also used by Spring, Logback, shell
scripts, and many other tools, whose placeholders should survive the
build. We switch the plugin to @...@ delimiters, which are
used, for example, by Spring Boot.
<configuration>
<useDefaultDelimiters>false</useDefaultDelimiters>
<delimiters>
<delimiter>@</delimiter>
</delimiters>
</configuration>
With useDefaultDelimiters set to false, the
${...} and @...@ defaults are turned off, and
only the listed delimiters are used. A delimiter without a *
is used both at the start and the end of a placeholder.
app.name=@project.name@
app.version=@project.version@
app.greeting=@greeting@
build.java=@java.version@
log.dir=${user.home}/logs
db.url=${db.url}
The build values use the @...@ syntax, the runtime values
keep the ${...} syntax.
Now log.dir keeps its placeholder for the application to
resolve. An alternative is to keep the default delimiters and escape single
placeholders, for example \${user.home}, after setting the
escapeString parameter to a backslash.
$ mvn -q clean compile exec:java
app.greeting Hello from the POM
app.name App info
app.version 1.2.0
build.java 25.0.3
db.url ${db.url}
log.dir ${user.home}/logs
Copying resources elsewhere
The copy-resources goal copies any directory to any location.
We use it to prepare a configuration file for a distribution, outside of
the JAR.
# configuration of ${project.artifactId} ${project.version}
port = 8080
db.url = ${db.url}
The configuration file contains placeholders for project properties and for the database URL.
<executions>
<execution>
<id>copy-config</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/dist/config</outputDirectory>
<resources>
<resource>
<directory>src/main/config</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
The goal is not bound to any phase by default, so the execution names
one. Its resources element has the same format as the one in
the build section, including filtering.
The goal runs in the package phase, after the
resources and testResources goals, which run
for every build. The file in target/dist/config is filtered.
$ mvn clean package -Ddb.url=jdbc:h2:mem:test --- resources:3.5.0:resources (default-resources) @ appinfo --- Copying 1 resource from src/main/resources to target/classes --- resources:3.5.0:testResources (default-testResources) @ appinfo --- --- resources:3.5.0:copy-resources (copy-config) @ appinfo --- Copying 1 resource from src/main/config to target/dist/config BUILD SUCCESS $ cat target/dist/config/app.conf # configuration of appinfo 1.2.0 port = 8080 db.url = jdbc:h2:mem:test
Encoding of properties files
Filtered files are written in the project encoding, which Maven 3.10 sets to UTF-8. We change the greeting to a Slovak one.
<properties>
<maven.compiler.release>25</maven.compiler.release>
<greeting>Dobré ráno</greeting>
</properties>
The value of the greeting property contains non-ASCII
characters.
The output is garbled, although the file contains correct UTF-8 bytes:
Properties.load(InputStream) always reads ISO-8859-1. This is
what the information message about the encoding of filtered properties
files in the build output warns about. The plugin can write properties
files in a different encoding with the propertiesEncoding
parameter, but it is simpler to read them as UTF-8.
package com.example.appinfo;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
public class Main {
public static void main(String[] args) throws IOException {
var props = new Properties();
try (var in = Main.class.getResourceAsStream("/app.properties");
var reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
props.load(reader);
}
props.stringPropertyNames().stream().sorted()
.forEach(key -> System.out.printf("%-12s %s%n", key, props.getProperty(key)));
}
}
The stream is wrapped in an InputStreamReader with the UTF-8
charset, and load reads from the reader. Resource bundles
loaded with ResourceBundle are read as UTF-8 by default
since Java 9.
$ mvn -q clean compile exec:java app.greeting Dobré ráno $ grep greeting target/classes/app.properties | od -c | head -2 0000000 a p p . g r e e t i n g = D o b 0000020 r 303 251 r 303 241 n o \n $ mvn -q clean compile exec:java app.greeting Dobré ráno
Useful options
| Element or parameter | Description |
|---|---|
resource/directory | source directory |
resource/filtering | substitute properties |
resource/includes, excludes | files to copy |
resource/targetPath | subdirectory of the output directory |
filters | properties files with filter values |
delimiters, useDefaultDelimiters | placeholder syntax |
escapeString | string that disables a placeholder |
nonFilteredFileExtensions | extensions never filtered |
propertiesEncoding | encoding of filtered properties files |
Test resources are configured the same way in a
testResources element.
Source
In this article we have worked with Maven resources. We have filtered them with properties and filter files, used custom delimiters, protected binary files, and fixed the encoding of filtered properties files.
Author
List all Java tutorials.