ZetCode

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.

appinfo/src/main/resources/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}

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.

appinfo/src/main/java/com/example/appinfo/Main.java
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.

appinfo/pom.xml
<?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.

pom.xml (resources)
<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.

appinfo/src/main/filters/dev.properties
db.url=jdbc:h2:mem:devdb
greeting=Hello from dev.properties

The development file defines the database URL and a greeting.

appinfo/src/main/filters/prod.properties
db.url=jdbc:postgresql://db.example.com/app

The production file defines only the database URL.

pom.xml (filters)
<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.

pom.xml (resources)
<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.

pom.xml (Resources plugin configuration)
<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.

src/main/resources/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}

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.

src/main/config/app.conf
# 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.

pom.xml (Resources plugin executions)
<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.

pom.xml (properties)
<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.

src/main/java/com/example/appinfo/Main.java
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 parameterDescription
resource/directorysource directory
resource/filteringsubstitute properties
resource/includes, excludesfiles to copy
resource/targetPathsubdirectory of the output directory
filtersproperties files with filter values
delimiters, useDefaultDelimitersplaceholder syntax
escapeStringstring that disables a placeholder
nonFilteredFileExtensionsextensions never filtered
propertiesEncodingencoding of filtered properties files

Test resources are configured the same way in a testResources element.

Source

Apache Maven Resources Plugin

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.