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:

  • .properties and .yaml configuration 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.

text
my-project/
  src/
    main/
      java/              # Compiled to target/classes
      resources/         # Also copied to target/classes
        application.properties
        logback.xml

Reading Resources at Runtime

Because resources end up on the classpath, you load them with ClassLoader rather than file system paths:

java
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:

properties
# Application configuration
app.name=My Application
app.version=1.0.0
app.environment=development

Tip

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

xml
<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:

properties
# 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:

xml
<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.

yaml
# application.yml (in src/main/resources)
server:
  port: 8080
  host: localhost
 
database:
  url: jdbc:postgresql://localhost:5432/mydb
  username: admin
  password: secret

If you are not using a framework that parses YAML automatically, add a parser dependency such as SnakeYAML:

xml
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>2.2</version>
</dependency>

Then load the file:

java
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:

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:

bash
# Build with the production profile
mvn package -Pprod

When 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:

xml
<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:

xml
<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:

xml
<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.