I have found something that might be related to (27542) or it is morelikely a separate bug regarding DI of Maps. I would surely like it to be fixed.

Spring: 5.3.18 (Spring Boot: 2.6.6) TestNG: 7.5 Java: 11

Demo: Spring 5 + XML context + TestNG = wrong type injected: ServiceA3XmlTest.java

factoryGroupsA_fails test fails because wrong type is injected. This happens in combination of using: - XML context definition - TestNG (... extends AbstractTestNGSpringContextTests)

In our Application we use bare Spring 5. Only thing that is different in the example test demo is that it uses Spring Boot (we don't in this case).

Expected type: Map<String, Map<String, MyServiceFactory>> Injected type: Map<String, Map<String, Map<String, MyServiceFactory>>>

ServiceA3XmlTest.java:

package test.autowiremap.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.Test;
import test.autowiremap.service.MyServiceFactory;

import java.util.Map;

@Test
@ContextConfiguration(locations = {"classpath:context.xml"})
public class ServiceA3XmlTest extends AbstractTestNGSpringContextTests {
    @Autowired
    @Qualifier("xmlFactoryMap")
    private Map<String, Map<String, MyServiceFactory>> factoryGroupsXml;
    @Autowired
    @Qualifier("xmlFactoryMap")
    private Object factoryGroupsXmlObject;

    @Test
    public void factoryGroupsA_fails() {
        // Throws NullPointerException due to different data type
        MyServiceFactory myServiceFactory = factoryGroupsXml.get("xmlGroup").get("xmlFactory");
        System.out.println(myServiceFactory);
    }

    @Test
    public void factoryGroupsA_working() {
        var factoryGroupsXmlObject = (Map<String, Map<String, MyServiceFactory>>) this.factoryGroupsXmlObject;
        MyServiceFactory myServiceFactory = factoryGroupsXmlObject.get("xmlGroup").get("xmlFactory");
        System.out.println(myServiceFactory);
    }
}

factoryGroupsA_working test shows how to evade this issue.

Test Demo: Test results in CI

Error:  Failures: 
Error:    ServiceA3XmlTest>AbstractTestNGSpringContextTests.run:184->factoryGroupsA_fails:25 NullPointer

Comment From: snicoll

I am trying to get my head around that sample and the fact it injects an Object that you claim works and a precise generic type that doesn't confuses me.

factoryGroups has a precise generic type and the context is respecting the type you've specified. Can you clarify?

Comment From: SpiReCZ

I get this in runtime for the test class fields (for factoryGroupsXml the expected type gets wrapped in LinkedHashMap with a key responding to the XML definition).

Spring Wrong type injected when @Autowire Map in TestNG with XML context definition

And to answer your question: It would have seem as the Spring context to honor the specified type:

Spring Wrong type injected when @Autowire Map in TestNG with XML context definition

Comment From: snicoll

Sorry that doesn't answer my question. I have cloned your sample and I ran it.

factoryGroupsXml is defined as Map<String, Map<String, MyServiceFactory>> and that is exactly what you get. What is the problem?

Comment From: SpiReCZ

I get injected different type than I want/expect to factoryGroupsXml:

    @Autowired
    @Qualifier("xmlFactoryMap")
    private Map<String, Map<String, MyServiceFactory>> factoryGroupsXml;

Expected type: Map> that is 2 maps Injected type: Map>> that is 3 maps

obrazek

Comment From: SpiReCZ

@snicoll Were you able to replicate the issue by running the tests? My test fails on NullPointerException due to different injected type:

Spring Wrong type injected when @Autowire Map in TestNG with XML context definition

Comment From: snicoll

Alright I get it now, thanks.

I can reproduce with recent JDK and version so I am adding this to the pile of things to investigate.

Comment From: SpiReCZ

Thanks 👍 , appreciated.

Comment From: snicoll

The problem here is that you're injecting a generic type where the related map has been created through XML, which doesn't really provide the full generic type. For the framework, the type ends up being Map<String, Map<?,?>

There's a fallback in the framework when it can't match the map you're asking for to inject a map of bean names to beans. We'd recommend using a dedicated type rather than injecting maps the way you do.