How to Do

Where to Put SPRING_APPLICATION_JSON When Testing a Spring Boot Application?

By Chandana on

Introduction

You have a Spring Boot application whose configuration is supposed to arrive through an environment variable called SPRING_APPLICATION_JSON. In production, your platform team sets it. Now you need to run the app, or its tests, on your own machine, and the question is:

Where do I put SPRING_APPLICATION_JSON?

The short answer: it goes in the environment of the JVM process that runs your application or your tests. It does not go in application.properties and it does not go in your Java code. Which place that is depends on how you start the process:

How you run itWhere the variable goes
TerminalPrefix the command, or export it
IntelliJ IDEARun/Debug configuration → Environment variables
EclipseRun Configurations → Environment tab
VS Codeenv in launch.json, or java.test.config for tests
Maven testsSurefire <environmentVariables>, or the shell
Maven spring-boot:runSpring Boot plugin <environmentVariables>, or the shell
Gradleenvironment(...) on the test / bootRun task
Docker-e SPRING_APPLICATION_JSON=...
Docker Composeenvironment: block
Kubernetesenv: entry in the container spec
JUnit test codeUsually not the env var. Use @SpringBootTest(properties = ...) or @TestPropertySource

The rest of this article explains why, how Spring Boot handles the variable, and how to prove it was loaded. I checked the Spring Boot behavior below against the current reference documentation (Spring Boot 4.1.x) and Javadoc. IDE menus and plugin settings change between versions, so treat those sections as guidance and confirm against your IDE’s docs.

SPRING_APPLICATION_JSON

What Is SPRING_APPLICATION_JSON?

SPRING_APPLICATION_JSON is an operating-system environment variable whose value is a JSON document. When a Spring Boot application starts, it parses that JSON, flattens it into ordinary Spring properties, and adds them to the Spring Environment.

The reference documentation shows this example: SPRING_APPLICATION_JSON='{"my":{"name":"test"}}' java -jar myapp.jar, which results in my.name=test in the Spring Environment. spring

Spring Boot supports two equivalent names for the same feature:

  • SPRING_APPLICATION_JSON, an environment variable
  • spring.application.json, a JVM system property, a command-line argument, or (in application servers) a JNDI value

The same JSON can be supplied through any of them:

java -Dspring.application.json='{"my":{"name":"test"}}' -jar myapp.jar
java -jar myapp.jar --spring.application.json='{"my":{"name":"test"}}'

Why Does Spring Boot Support JSON in an Environment Variable?

The official docs give the reason: environment variables and system properties often have restrictions that mean some property names cannot be used, so Spring Boot lets you encode a block of properties into a single JSON structure. spring

The restriction is real. Most shells do not allow dots or dashes in environment variable names, so a property like app.mail.smtp.host cannot be an environment variable name directly. Spring Boot’s relaxed binding works around this with names like APP_MAIL_SMTP_HOST, but that has drawbacks:

  • Names become ambiguous (APP_MAIL_SMTP_HOST could map to several property shapes).
  • Lists and maps get awkward (APP_HOSTS_0_NAME).
  • You end up with many variables for one logical config block.

JSON avoids this. One variable carries a whole structure, with the original property names intact. It is especially handy on platforms where you can only set a few environment variables per service.

Environment Variables vs. System Properties vs. Files vs. Test Config

Many mix-ups come from treating these as interchangeable. They are separate mechanisms:

MechanismWhat it isExampleWho reads it
Environment variableSet in the OS process environment before the JVM startsSPRING_APPLICATION_JSON='{...}'System.getenv(); Spring Boot
JVM system propertyPassed to the JVM with -D (or set with System.setProperty)-Dspring.application.json='{...}'System.getProperty(); Spring Boot
application.propertiesA file on the classpath or disk, loaded by Spring Boot’s config data loaderapp.mail.sender=a@b.cSpring Boot
application.yml / .yamlSame as above, in YAMLapp:\n mail:\n sender: a@b.cSpring Boot
Test propertiesProperties declared on a test: @SpringBootTest(properties=...), @TestPropertySource, @DynamicPropertySource@TestPropertySource(properties = "app.x=1")Spring’s test framework
Spring test configurationBeans and profiles that alter the test context: @TestConfiguration, @ActiveProfiles@ActiveProfiles("test")Spring’s test framework

Two points to remember:

  1. SPRING_APPLICATION_JSON is an environment variable, so it must exist before the JVM starts. A Java test cannot reliably create it for itself. The JVM’s environment is effectively read-only from Java code.
  2. -DSPRING_APPLICATION_JSON=... is not the documented form. The documented system property is the dotted, lowercase spring.application.json.

How Spring Boot Reads and Processes It

At startup, SpringApplication prepares the Environment and runs EnvironmentPostProcessor implementations. One of them, SpringApplicationJsonEnvironmentPostProcessor, does this work. Its Javadoc says it parses JSON from spring.application.json or equivalently SPRING_APPLICATION_JSON and adds it as a map property source to the Environment, with higher priority than the system properties. (In Spring Boot 4 the class lives in the org.springframework.boot.support package, whereas older versions used org.springframework.boot.env.) spring

The steps are:

  1. Boot finds the value of spring.application.json (the env var SPRING_APPLICATION_JSON resolves to it).
  2. It parses the JSON into a map.
  3. It flattens nested objects into dotted keys.
  4. It registers the result as a property source (named spring.application.json).

How JSON keys become Spring properties

Nested objects become dotted names. Arrays become indexed names:

json

{
  "app": {
    "mail": {
      "sender": "noreply@example.com",
      "retries": 3,
      "smtp": { "host": "smtp.test.local", "port": 2525 },
      "allowed-domains": ["example.com", "example.org"]
    }
  }
}

That produces these properties:

properties

app.mail.sender=noreply@example.com
app.mail.retries=3
app.mail.smtp.host=smtp.test.local
app.mail.smtp.port=2525
app.mail.allowed-domains[0]=example.com
app.mail.allowed-domains[1]=example.org

The [index] form is the same one used when YAML lists are flattened, and Spring’s Binder can bind it to a List or Set.

Two consequences:

  • JSON key names must match property names. The JSON layer does not translate senderAddress to sender-address. Once the properties are in the Environment, @ConfigurationProperties relaxed binding applies as usual, but a plain @Value("${...}") needs the exact name. Prefer lowercase kebab-case (sender-address).
  • A typo does not raise an error. A wrongly spelled but valid key is just an extra property nobody reads.

A note on null

The docs warn that null values from the JSON are added to the property source, but Spring’s PropertySourcesPropertyResolver treats null properties as missing, so JSON cannot override lower-order sources with null. You cannot use {"app":{"x":null}} to “unset” a value from application.properties. spring

What happens with invalid JSON?

A GitHub change titled “Throw an exception on invalid syntax in SPRING_APPLICATION_JSON” removed the code that used to catch the parse failure and only log a warning. So in modern versions, malformed JSON should make startup fail with an exception, and you should look at the top of the stack trace. I could not confirm the exact release notes wording, so check your Boot version if you depend on this.

The Example Application

The examples below use one small app so every section is comparable.

java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
@EnableConfigurationProperties(MailProperties.class)
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

java

package com.example.demo;

import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(String sender, int retries, Smtp smtp, List<String> allowedDomains) {

    public record Smtp(String host, int port) {
    }
}

And a default in src/main/resources/application.properties:

properties

app.mail.sender=default@example.com
app.mail.retries=1

The JSON we want to inject:

json

{"app":{"mail":{"sender":"json@example.com","smtp":{"host":"smtp.test.local","port":2525}}}}

I have not compiled or executed these examples, so run them once in your own project.

Where to Put It, Tool by Tool

Command line

Linux / macOS (bash, zsh): single quotes stop the shell from touching the double quotes inside the JSON.

shell

SPRING_APPLICATION_JSON='{"app":{"mail":{"sender":"json@example.com"}}}' java -jar target/demo-0.0.1-SNAPSHOT.jar

Or export it for the whole session:

shell

export SPRING_APPLICATION_JSON='{"app":{"mail":{"sender":"json@example.com"}}}'
java -jar target/demo-0.0.1-SNAPSHOT.jar

Windows PowerShell:

powershell

$env:SPRING_APPLICATION_JSON = '{"app":{"mail":{"sender":"json@example.com"}}}'
java -jar target\demo-0.0.1-SNAPSHOT.jar

Windows cmd.exe: no surrounding quotes.

bat

set SPRING_APPLICATION_JSON={"app":{"mail":{"sender":"json@example.com"}}}
java -jar target\demo-0.0.1-SNAPSHOT.jar

Maven

A Maven build involves two separate JVM processes you might care about: the one running your app (spring-boot:run) and the forked one running your tests (Surefire).

Quickest option, from the shell. Forked processes inherit the parent environment:

shell

SPRING_APPLICATION_JSON='{"app":{"mail":{"sender":"ci@example.com"}}}' ./mvnw test
SPRING_APPLICATION_JSON='{"app":{"mail":{"sender":"dev@example.com"}}}' ./mvnw spring-boot:run

Permanent option for tests, in pom.xml (Surefire’s environmentVariables):

xml

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <configuration>
        <environmentVariables>
          <SPRING_APPLICATION_JSON>{"app":{"mail":{"sender":"ci@example.com"}}}</SPRING_APPLICATION_JSON>
        </environmentVariables>
      </configuration>
    </plugin>
  </plugins>
</build>

If you also run integration tests with Failsafe, configure that plugin the same way. Surefire and Failsafe are configured separately.

For spring-boot:run, the Spring Boot Maven plugin has its own environmentVariables setting:

xml

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    <environmentVariables>
      <SPRING_APPLICATION_JSON>{"app":{"mail":{"sender":"dev@example.com"}}}</SPRING_APPLICATION_JSON>
    </environmentVariables>
  </configuration>
</plugin>

Gradle

Gradle Kotlin DSL (build.gradle.kts):

kotlin

tasks.withType<Test> {
    environment(
        "SPRING_APPLICATION_JSON",
        """{"app":{"mail":{"sender":"ci@example.com"}}}"""
    )
}

tasks.named<org.springframework.boot.gradle.tasks.run.BootRun>("bootRun") {
    environment(
        "SPRING_APPLICATION_JSON",
        """{"app":{"mail":{"sender":"dev@example.com"}}}"""
    )
}

Gradle Groovy DSL (build.gradle):

groovy

tasks.named('test') {
    environment 'SPRING_APPLICATION_JSON', '{"app":{"mail":{"sender":"ci@example.com"}}}'
}

tasks.named('bootRun') {
    environment 'SPRING_APPLICATION_JSON', '{"app":{"mail":{"sender":"dev@example.com"}}}'
}

From the shell, SPRING_APPLICATION_JSON='...' ./gradlew test also works. But Gradle may decide the test task is up to date and skip it, because a shell variable is not a declared task input. If nothing seems to run, use ./gradlew cleanTest test.

IntelliJ IDEA

  1. Open Run | Edit Configurations…
  2. Select your JUnit or Spring Boot configuration (or, to apply to all future JUnit runs, Edit configuration templates… → JUnit).
  3. Open Modify options and enable Environment variables.
  4. Enter the variable as NAME=value, with the raw JSON and no surrounding quotes:

text

SPRING_APPLICATION_JSON={"app":{"mail":{"sender":"ide@example.com"}}}

IDE-specific caveats:

  • IntelliJ’s field uses ; to separate multiple variables, so a JSON value containing a semicolon needs care.
  • If IntelliJ is set to delegate test runs to Gradle or Maven, the environment that matters is the one in your build script, not the run configuration. This is an IDE behavior, not a Spring Boot one.

Eclipse / Spring Tools

  1. Run → Run Configurations… (or Debug Configurations…).
  2. Choose your JUnit or Java Application configuration.
  3. Open the Environment tab and click New…
  4. Name: SPRING_APPLICATION_JSON
    Value: {"app":{"mail":{"sender":"eclipse@example.com"}}} (raw JSON, no quotes)

Each JUnit run gets its own generated launch configuration, so an environment variable you add to one class’s configuration will not automatically apply to another class.

VS Code

For running the application with the Java debugger, add an env block to .vscode/launch.json. JSON inside a JSON string needs its quotes escaped:

json

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "java",
      "name": "Demo Application",
      "request": "launch",
      "mainClass": "com.example.demo.DemoApplication",
      "env": {
        "SPRING_APPLICATION_JSON": "{\"app\":{\"mail\":{\"sender\":\"vscode@example.com\"}}}"
      }
    }
  ]
}

For tests run from the Testing view, the Java test runner reads the java.test.config setting in .vscode/settings.json:

json

{
  "java.test.config": {
    "env": {
      "SPRING_APPLICATION_JSON": "{\"app\":{\"mail\":{\"sender\":\"vscode-test@example.com\"}}}"
    }
  }
}

I did not re-verify this setting against the extension’s current documentation, so confirm it in your version. If VS Code runs tests through Maven or Gradle, the build-tool configuration above applies instead.

Docker

shell

docker run --rm \
  -e SPRING_APPLICATION_JSON='{"app":{"mail":{"sender":"docker@example.com"}}}' \
  my-spring-app:latest

Confirm the container actually received it:

shell

docker exec <container-id> printenv SPRING_APPLICATION_JSON

Docker Compose

yaml

services:
  api:
    image: my-spring-app:latest
    ports:
      - "8080:8080"
    environment:
      SPRING_APPLICATION_JSON: '{"app":{"mail":{"sender":"compose@example.com","retries":5}}}'

Compose interpolates $ characters. If your JSON contains a literal $, write $$.

Kubernetes

Inline in a Deployment:

yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo-api
  template:
    metadata:
      labels:
        app: demo-api
    spec:
      containers:
        - name: api
          image: my-spring-app:latest
          env:
            - name: SPRING_APPLICATION_JSON
              value: '{"app":{"mail":{"sender":"k8s@example.com"}}}'

Or from a ConfigMap, which keeps the JSON out of the Deployment:

yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: demo-api-config
data:
  SPRING_APPLICATION_JSON: |-
    {"app":{"mail":{"sender":"cm@example.com"}}}

yaml

          env:
            - name: SPRING_APPLICATION_JSON
              valueFrom:
                configMapKeyRef:
                  name: demo-api-config
                  key: SPRING_APPLICATION_JSON

Avoid putting secrets in a plain ConfigMap or inline value. Use a Kubernetes Secret (via secretKeyRef), or mount secrets as files. Spring Boot supports importing config from mounted directories with spring.config.import=configtree:.

JUnit and Spring Boot Tests

This is where most confusion starts, so here is the key idea:

A @SpringBootTest starts a real SpringApplication, so it honors SPRING_APPLICATION_JSON if the variable exists in the test JVM’s environment. But you should not try to create the variable from inside the test.

You have four options.

Option 1: Provide the variable to the test JVM, then verify it

Use the Maven, Gradle, or IDE setup above, then add a test that proves it arrived:

java

package com.example.demo;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;

@SpringBootTest
class SpringApplicationJsonLoadedTest {

    @Autowired
    private Environment environment;

    @Test
    void propertiesFromSpringApplicationJsonAreVisible() {
        assumeTrue(System.getenv("SPRING_APPLICATION_JSON") != null,
                "SPRING_APPLICATION_JSON is not set for this JVM");

        assertThat(environment.getProperty("app.mail.sender")).isEqualTo("ci@example.com");
    }
}

The assumeTrue line makes the test skip (rather than fail) on machines where the variable is absent. Remove it in CI if the variable is mandatory.

A plain JUnit test without a Spring context never triggers this mechanism, because it is SpringApplication that parses the variable.

Option 2: Use test-native properties (usually the best choice)

If the goal is simply to set property values in a test, skip JSON:

java

@SpringBootTest(properties = {
        "app.mail.sender=test@example.com",
        "app.mail.smtp.host=localhost",
        "app.mail.smtp.port=2525"
})
class MailServiceTest {
    // ...
}

Or:

java

import org.springframework.test.context.TestPropertySource;

@SpringBootTest
@TestPropertySource(properties = "app.mail.sender=test@example.com")
class MailServiceTest2 {
    // ...
}

Or, for values computed at runtime (such as a Testcontainers port), use @DynamicPropertySource:

java

import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;

@SpringBootTest
class MailServiceTest3 {

    @DynamicPropertySource
    static void registerProperties(DynamicPropertyRegistry registry) {
        registry.add("app.mail.smtp.port", () -> 2525);
    }
}

These are also the highest-precedence property sources in Boot’s order, as you will see next.

Option 3: Pass JSON through the spring.application.json property

If you must exercise the JSON path itself, you can pass the dotted property form as a test property:

java

@SpringBootTest(properties =
        "spring.application.json={\"app\":{\"mail\":{\"sender\":\"inline@example.com\"}}}")
class InlineJsonTest {
    // ...
}

The post-processor reads spring.application.json from the Environment, which is why this should work, but I have not executed this exact form. Confirm it with an Environment assertion like the one in Option 1. Note that this tests the JSON parsing, not the environment variable name.

Option 4: Profile-based test files

For a fixed, reviewable set of test values, the boring approach is often best. Create src/test/resources/application-test.yml:

yaml

app:
  mail:
    sender: "test@example.com"
    smtp:
      host: "localhost"
      port: 2525

and activate it:

java

import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@ActiveProfiles("test")
class MailServiceProfileTest {
    // ...
}

What not to do

Calling System.setProperty("spring.application.json", ...) in a static block can work, but it leaks into every later test in the same JVM. Libraries that mutate environment variables at runtime rely on reflection hacks that can break on newer Java versions. Neither is recommended when the built-in test mechanisms above exist.

Property Precedence

Spring Boot merges many property sources. The current reference documentation lists them from lowest to highest priority, and later sources override earlier ones. Here is the part relevant to this topic:

  1. Default properties
  2. @PropertySource on @Configuration classes
  3. Config data (application.properties / .yml)
  4. random.* values
  5. OS environment variables
  6. Java system properties
  7. JNDI attributes
  8. Servlet context/config init parameters
  9. SPRING_APPLICATION_JSON
  10. Command-line arguments
  11. properties attribute on @SpringBootTest and test slices
  12. @DynamicPropertySource
  13. @TestPropertySource
  14. Devtools global settings, if devtools is active

(Source: the Externalized Configuration page of the Spring Boot reference, version 4.1.x.)

What this means in practice:

ScenarioWinner
application.properties sets app.mail.sender, JSON also sets itJSON
A -Dapp.mail.sender=x system property and the JSON both set itJSON
An APP_MAIL_SENDER env var and the JSON both set itJSON
The JSON and --app.mail.sender=x on the command lineCommand line
The JSON and @SpringBootTest(properties = "app.mail.sender=x")Test properties
The JSON and @TestPropertySource@TestPropertySource

Also, precedence works per property, not per source. If the JSON only sets app.mail.sender, then app.mail.retries still comes from application.properties. That is why partial JSON overrides are convenient: you only ship what differs.

Common Mistakes

1. Putting it in application.properties. That file is for the properties themselves. Write app.mail.sender=... there directly. Wrapping JSON in a properties file is not the documented use.

2. Setting it in the wrong process. A variable exported in one terminal does not affect an IDE launched from the desktop, and a variable in an IDE run configuration does not affect ./mvnw test in a terminal. Always ask: which process actually launches the JVM?

3. Quoting mistakes.

  • Bash needs single quotes around the JSON.
  • Windows cmd needs no quotes.
  • IDE environment fields need no quotes.
  • JSON embedded in another JSON file (launch.json) needs escaped \".

4. Invalid JSON. JSON allows double quotes only, with no single-quoted strings, no trailing commas, and no comments. {'app': {...}} is not valid JSON.

5. Confusing the env var with a system property. SPRING_APPLICATION_JSON goes in the environment. -Dspring.application.json goes on the JVM arguments. They are two entry points for one feature, and they are set in different places.

6. Wrong key names. Valid JSON with a misspelled key loads silently and does nothing.

7. Expecting JSON to beat command-line or test properties. It doesn’t, as shown above.

8. Expecting null to remove a value. It cannot override a lower-priority value.

9. Compose and Kubernetes YAML quoting. Unquoted JSON in YAML can be parsed as a YAML flow mapping instead of a string. Quote it with single quotes, as shown above. In Compose, escape a literal $ as $$.

10. Putting secrets in it. Environment variables appear in docker inspect, process inspection tools, and CI logs. Use secret stores for credentials.

11. Stale Gradle results. Tests skipped as up to date because the environment changed outside Gradle’s knowledge (see above).

How to Verify the Configuration Was Loaded

Work from the outside in.

Step 1: Is the variable in the process at all?

shell

printenv SPRING_APPLICATION_JSON                                 # local shell
docker exec <container> printenv SPRING_APPLICATION_JSON        # Docker
kubectl exec <pod> -- printenv SPRING_APPLICATION_JSON          # Kubernetes

If this prints nothing, Spring Boot is not the problem.

Step 2: Is it valid JSON? Paste it into a validator, or run:

shell

printenv SPRING_APPLICATION_JSON | python3 -m json.tool

Step 3: Does the Spring Environment see the property? Add a temporary check:

java

package com.example.demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@Configuration(proxyBeanMethods = false)
class StartupCheck {

    @Bean
    CommandLineRunner printMailSender(Environment env) {
        return args -> System.out.println("app.mail.sender = " + env.getProperty("app.mail.sender"));
    }
}

Delete it once you are done.

Step 4: Which source won? Spring Boot Actuator’s env endpoint shows every property source and the winning value for a key. Add spring-boot-starter-actuator, expose the endpoint, and, for local debugging only, show values (they are hidden by default):

properties

management.endpoints.web.exposure.include=env
management.endpoint.env.show-values=ALWAYS

Then request /actuator/env/app.mail.sender. The response lists the property sources that define the property, so you can see whether the JSON source or something with higher priority supplied the final value. The reference docs note that the env and configprops endpoints are useful for diagnosing why a property has a particular value. Do not enable show-values=ALWAYS in production.

Step 5: Is your binding correct? Inject your @ConfigurationProperties bean in a test and assert on its fields, not just on the raw Environment.

Troubleshooting Checklist

SymptomLikely causeWhat to do
App ignores the JSON completelyVariable set in a different processprintenv inside the actual process; check the IDE/Maven/Gradle configuration
Startup fails with a parse errorInvalid JSON, or quotes mangled by the shellValidate the JSON; check quoting rules for your shell
Value is still the application.properties defaultWrong key name, or a higher-precedence source is winningCheck the key spelling; inspect /actuator/env/<key>
Value differs in tests only@TestPropertySource, @DynamicPropertySource, or @SpringBootTest(properties=...) overrides itSearch the test class hierarchy for those annotations
Works in IntelliJ, fails in CIIDE config not mirrored in the buildPut the variable in Surefire or Gradle config
Gradle test doesn’t rerunEnvironment changed outside declared inputs./gradlew cleanTest test
Compose value is mangled$ interpolation or YAML quotingUse $$; single-quote the JSON
List binding failsWrong JSON structureUse a JSON array (["a","b"]), which flattens to name[0], name[1]
A null in JSON has no effectnull is treated as missingSet an explicit value or an empty string instead

When Is SPRING_APPLICATION_JSON the Right Tool?

It is a good fit when:

  • You deploy to a platform where environment variables are the main configuration channel.
  • You need to override a few nested properties without shipping a file.
  • Property names contain characters that make env var names awkward.
  • You want a one-off override for a manual test, a CI job, or a container.
  • You need to override a deployed default without rebuilding the artifact.

A normal application.properties / application.yml is better when:

  • The values are the application’s defaults and belong in version control with the code.
  • The configuration is large or has comments worth keeping. JSON has no comments.
  • You use profiles (application-test.yml, application-prod.yml).
  • Many developers need to read and review the configuration.
  • The values are static test fixtures. Prefer @SpringBootTest(properties = ...) or @TestPropertySource.

Rule of thumb: put defaults in files, environment-specific overrides in the environment, and test-specific values in test annotations.

Quick Interview Questions

Is SPRING_APPLICATION_JSON a system property?
No. It is an environment variable. The system-property equivalent is spring.application.json.

Does it override application.properties?
Yes. Config data files are near the bottom of the precedence list, and the JSON sits well above them.

Does it override command-line arguments?
No. Command-line arguments have higher precedence.

Can @TestPropertySource override it?
Yes. Test property sources sit above it.

How do nested JSON objects map to Spring properties?
They are flattened with dots. {"a":{"b":"c"}} becomes a.b=c, and arrays become name[0], name[1].