'Spring bean of type Map<String, Object> is special?

Platform: JDK 11, Spring Boot 2.6.6 / Spring 5.3.18

For reasons related to a dependency, I found myself wanting to create and then inject a bean of type Map<String, Object>. There seems to be something special about this type, though, as I will demonstrate.

Minimal illuminating code sample:

import java.util.Map;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ThingyTest {
  @Bean("someOtherBean")
  Object otherBean(@Qualifier("mapBean") Map<String, Object> map) {
    return map.keySet();
  }
  
  @Bean("mapBean")
  Map<String, Object> mapBean() {
    return Map.of(
        "key1", "value1",
        "key2", "value2");
  }
}

My desire is for the parameter named map to contain:

{
    "key1": "value1",
    "key2", "value2"
}

However, it actually contains:

{
    "mapBean": {
        "key1": "value1",
        "key2": "value2
    }
}

If I change the type of mapBean (in both the method return type and the method argument) to Map<String, String> then I get the desired behavior, but I have to do some nasty type wrangling to get back to the Map<String, Object> that I need.

I've spent many hours searching the Internet and Spring documentation for any discussion of this apparent "feature." I'm sure this behavior is intentional, but I just can't find any documentation or explanation of it. Ideally, someone could explain how to get my desired behavior, but I would be satisfied with references to official documentation describing the observed behavior.



Solution 1:[1]

A Map<String, ?> is a bit of a special case in the Spring Framework.

Even typed Map instances can be autowired as long as the expected key type is String. The map values contain all beans of the expected type, and the keys contain the corresponding bean names, as the following example shows:

As your Map has a type Object it will inject a Map with all the beans in the application context.

To workaround this you could try the following

@Configuration
public class ThingyTest {
  @Bean("someOtherBean")
  Object otherBean() {
    return mapBean().keySet();
  }
  
  @Bean("mapBean")
  Map<String, Object> mapBean() {
    return Map.of(
        "key1", "value1",
        "key2", "value2");
  }
}

Although this might be a workaround I would consider registering a bug with the Spring Framework. As you have added an @Qualifier it is to be expected that that specific bean would be taken and not the special case of Map<String, ?>.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 M. Deinum