Skip to content

NullnessMethodInvocationValidator should consider jakarta.annotation.Nullable annotations #3316

@fjakop

Description

@fjakop

We switched to spring-data 3.5.1 recently and encountered errors on formerly (spring-data 3.4.4) working code

java.lang.NullPointerException: Return value is null but must not be null
	at org.springframework.data.util.NullnessMethodInvocationValidator.returnValueIsNull(NullnessMethodInvocationValidator.java:124)
...

The method in question is annotated with jakarta.annotation.Nullable, so this should not happen imho.
It's easily reproducible with this test code

package org.springframework.data.util;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.core.ParameterNameDiscoverer;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class MethodNullnessTest {

    private final ParameterNameDiscoverer discoverer = new ParameterNameDiscoverer() {
        @Override
        public String[] getParameterNames(Method method) {
            return null;
        }

        @Override
        public String[] getParameterNames(Constructor<?> ctor) {
            return null;
        }
    };

    @Test
    void isNullableReturn() throws NoSuchMethodException {

        assertFalse(NullnessMethodInvocationValidator.MethodNullness
                .of(Dummy.class.getDeclaredMethod("getNonnullData"), discoverer)
                .isNullableReturn());

        assertTrue(NullnessMethodInvocationValidator.MethodNullness
                .of(Dummy.class.getDeclaredMethod("getNullableData"), discoverer)
                .isNullableReturn());

    }

    static class Dummy {

        @Nonnull
        String getNonnullData() {
            return "";
        }

        @Nullable
        String getNullableData() {
            return "";
        }
    }
}

where the assertion for nullable = true always fails.

Activity

self-assigned this
on Jun 23, 2025
mp911de

mp911de commented on Jun 27, 2025

@mp911de
Member

jakarta.annotation.Nonnull (Jakarta Annotations) never were in scope for Spring Data's Nullness validation and the example doesn't work for me across 3.3 to 3.5 versions. We only support JSR305 (javax.annotation.Nonnull, javax.annotation.Nullable) as legacy annotation variant. JSR305 was never migrated to Jakarta.

Care to provide a reproducer for Spring Data 3.4.x?

fjakop

fjakop commented on Jun 30, 2025

@fjakop
Author

Reproducing for 3.4.x is not possible, since the class in question NullnessMethodInvocationValidator exists only from 3.5 on, so this particular testing strategy does not work. However, the issue originated of a more complex application.

I will try to create a reproducer for the complete scenario but I would also suggest to support jakarta.annotation.Nullable and jakarta.annotation.Nonnull in Spring Data, since the transition of JSR305 to Jakarta namespace is commonly adopted by various frameworks and tools.

I would think the support of Jakarta annotations is not that hard and would just require adding these classes to the respective sets in org.springframework.data.util.NullableUtils.

mp911de

mp911de commented on Jun 30, 2025

@mp911de
Member

You can use either ProxyFactory (or org.springframework.data.util.NullableUtils#isExplicitNullable(…) in a more isolated setup) to create a reproducer.

import static org.junit.jupiter.api.Assertions.*;


import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.junit.jupiter.api.Test;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.data.repository.core.support.MethodInvocationValidator;

public class MethodInvocationValidatorReproducer {

	@Test
	void reproducer() {

		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.addInterface(Something.class);
		proxyFactory.setTarget(new Dummy());
		proxyFactory.addAdvice(new MethodInvocationValidator());
		Something s = (Something) proxyFactory.getProxy();

		// should not throw an exception
		assertNull(s.getNullableData());
	}

	public static class Dummy implements Something {

		@Nonnull
		public String getNonnullData() {
			return "";
		}

		@Nullable
		public String getNullableData() {
			return null;
		}
	}

	public interface Something {

		@Nullable
		String getNullableData();
	}

}

I would also suggest to support jakarta.annotation.Nullable and jakarta.annotation.Nonnull in Spring Data, since the transition of JSR305 to Jakarta namespace is commonly adopted by various frameworks and tools.

JSR305 was never migrated to the Jakarta namespace.
We don't want folks to use nullness indication that has an incomplete scopewithout considering the entire JVM space where nullness applies. Spring has deliberately deprecated JSR-305 support (and with that, we're broadly checking annotation names) in favor of JSpecify.

I would think the support of Jakarta annotations is not that hard and would just require adding these classes to the respective sets in org.springframework.data.util.NullableUtils.

It's not about difficulty but about doing the right thing.

fjakop

fjakop commented on Jun 30, 2025

@fjakop
Author

Thanks for the clarification and the code, replacing the annotation with jakarta.annotation.Nullable leads exactly to our issue.
So our mitigation strategy would be to

  • switch back to javax-scoped annotations
  • use org.springframework.lang.Nullable

which both re-establish the pre 3.5.x behaviour.

mp911de

mp911de commented on Jul 7, 2025

@mp911de
Member

Exactly. Closing this ticket then.

changed the title [-]NullnessMethodInvocationValidator.MethodNullness#isNullableReturn does not work for Java classes[/-] [+]NullnessMethodInvocationValidator should consider `jakarta.annotation.Nullable` annotations[/+] on Jul 7, 2025
added
status: declinedA suggestion or change that we don't feel we should currently apply
and removed
status: invalidAn issue that we don't feel is valid
on Jul 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

status: declinedA suggestion or change that we don't feel we should currently apply

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

    Development

    No branches or pull requests

      Participants

      @mp911de@fjakop@spring-projects-issues

      Issue actions

        NullnessMethodInvocationValidator should consider `jakarta.annotation.Nullable` annotations · Issue #3316 · spring-projects/spring-data-commons