Skip to content

Inconsistent behavior injecting null @Bean factory parameter #34929

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
mikewatt opened this issue May 21, 2025 · 2 comments
Closed

Inconsistent behavior injecting null @Bean factory parameter #34929

mikewatt opened this issue May 21, 2025 · 2 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: regression A bug that is also a regression
Milestone

Comments

@mikewatt
Copy link

mikewatt commented May 21, 2025

I am working on upgrading some Spring 5 code to Spring 6. This code does have some @Bean factory methods that can return null, and I've encountered some autowiring failures. While I understand this should be avoided, the following behavior seemed a bit unintuitive to me.

Given the following minimal example (Spring 6.2.6):

package example.example;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;

public class ExampleApplication {

    public static class Dependency { }

    @Configuration
    public static class TestConfiguration {
        @Nullable
        @Bean
        Dependency d() {
            return null;
        }

        // org.springframework.beans.factory.NoSuchBeanDefinitionException
        @Bean
        public Object test1(Dependency d) {
            return new Object();
        }

        // Injects null
        @Bean
        public Object test2(@Nullable Dependency d) {
            return new Object();
        }

        // Injects null
        @Bean
        public Object test3(Dependency x) {
            return new Object();
        }
    }

    public static void main(String[] args) {
        try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
            context.register(TestConfiguration.class);
            context.refresh();
        }
    }
}

test1(), in which the injected parameter is retrieved by name, throws NoSuchBeanDefinitionException (note: previously in Spring 5.x this would inject the parameter as null).

test2() is simply to demonstrate that annotating the parameter as @Nullable is a possible solution.

test3() in which the injected parameter is retrieved by type, continues to inject null without issue.

Therefore, I ran into wiring failures at injection points where parameter names happened to match the null bean name, but not where the parameter name differed and there was still apparently a null candidate bean retrieved by type.

My expectation would be that test1() and test3() would either both succeed or both fail in the same way.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label May 21, 2025
@sbrannen sbrannen added the in: core Issues in core modules (aop, beans, core, context, expression) label May 21, 2025
@jhoeller jhoeller self-assigned this May 21, 2025
@jhoeller jhoeller added type: regression A bug that is also a regression and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels May 21, 2025
@jhoeller jhoeller added this to the 6.2.8 milestone May 21, 2025
@jhoeller
Copy link
Contributor

Well spotted! This is very likely due to our new shortcut resolution for a bean name match (bean name "d" matching dependency name "d" in the injection point) which uses a faster but different code path as of 6.2 (#28122). I'll align this for 6.2.8, leniently injectinging a null value there as well.

Our general recommendation is to declare null acceptance at an injection point through @Nullable or Optional indeed. However, the different resolution code paths nevertheless need to agree on success/failure in a consistent and ideally backwards-compatible way.

@jhoeller
Copy link
Contributor

It turns out that the common resolution code path does not perform a proper null bean check in case of a pre-existing singleton, due to a mismatch between the singleton retrieval optimization (which immediately turns a NullBean into null) and the instance resolution step (which still checks for NullBean).

For 6.2.8, we'll restore backwards compatibility for the shortcut code path - leniently accepting null to be injected even if not declared as @Nullable - even if this actually revealed a bug. For 7.0, we'll fix the actual bug: then always rejecting null unless the injection point has been marked as @Nullable.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

4 participants