Skip to content

Breaking change when mapping empty maps from application.yml (when migrating from 3.3.x to >=3.4.0) #45707

Closed
@pgpego

Description

@pgpego

Hi, we found what appears to be a backwards incompatible change in behaviour that we couldn't find documented anywhere: Empty maps indicated in the application.yml file as {} are deserialized as an empty map in Sprint Boot 3.3.X (tested with 3.3.12), but as a null map in Spring Boot 3.4.0 (or later).

Sample code:

Main.java:

package com.mycompany.test;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.lang.NonNull;

import java.util.Map;

@Slf4j
@SpringBootApplication
@EnableConfigurationProperties(value = {Main.Settings.class, Main.Settings2.class})
public class Main implements ApplicationRunner {

    @ConfigurationProperties("app")
    public record Settings(
            @NonNull Map<String, String> data1,
            @NonNull Map<String, String> data2) {
    }

    @Getter
    @Setter
    @ToString
    @ConfigurationProperties("app")
    public static class Settings2 {
        private Map<String, String> data1;
        private Map<String, String> data2;
    }

    @Autowired
    Settings settings;

    @Autowired
    Settings2 settings2;

    @Override
    public void run(ApplicationArguments args) {
        log.info("Record: {}", settings);
        log.info("Regular class: {}", settings2);
    }

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

}

application.yml:

app:
  data1:
    a: "A"
    b: "B"

  data2: {}

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.0</version>
    </parent>

    <groupId>com.mycompany</groupId>
    <artifactId>test-spring</artifactId>
    <version>0.1-SNAPSHOT</version>

    <properties>
        <java.version>21</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Example output when running the app:

...
2025-05-28T11:08:28.678+02:00  INFO 90044 --- [           main] com.mycompany.test.Main                  : Started Main in 0.355 seconds (process running for 0.508)
2025-05-28T11:08:28.679+02:00  INFO 90044 --- [           main] com.mycompany.test.Main                  : Record: Settings[data1={a=A, b=B}, data2=null]
2025-05-28T11:08:28.680+02:00  INFO 90044 --- [           main] com.mycompany.test.Main                  : Regular class: Main.Settings2(data1={a=A, b=B}, data2=null)

When using Spring Boot 3.3.12 the output is:

...
2025-05-28T11:16:49.459+02:00  INFO 91012 --- [           main] com.mycompany.test.Main                  : Started Main in 0.36 seconds (process running for 0.51)
2025-05-28T11:16:49.461+02:00  INFO 91012 --- [           main] com.mycompany.test.Main                  : Record: Settings[data1={a=A, b=B}, data2={}]
2025-05-28T11:16:49.464+02:00  INFO 91012 --- [           main] com.mycompany.test.Main                  : Regular class: Main.Settings2(data1={a=A, b=B}, data2={})

Please, note the differences in the two last lines.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions