Affects: spring-core 5.2.1

When updating from 5.1.10 to 5.2.0 / 5.2.1, then AnnotationUtils.findAnnotation(Class<?> clazz, @Nullable Class<A> annotationType) no longer finds annotations that are created on the parent.

Example project https://github.com/sellersj/spring-annotationutils-issue

I thought that https://github.com/spring-projects/spring-framework/issues/23856 was the same issue, but updating to 5.2.1 didn't fix the issue.

Comment From: sbrannen

Thanks for raising the issue.

I have confirmed that this is a regression; however, the scope is slightly more limited than your initial claim.

Specifically, the regression only applies to annotations that are not declared as @Inherited and whose package name starts with java. -- for example @java.lang.Deprecated, as in your demo project.

To illustrate this, the following modified version of your test class passes against master (i.e., 5.2.x).

package com.example;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationUtils;
import static org.junit.Assert.assertTrue;

public class AnnotationTest {

    @Test
    public void isClassDeprecated_DirectlyOnclass() {
        assertTrue("should mark as deprecated", isClassDeprecated(DeprecatedClass.class));
    }

    @Test
    public void isClassDeprecated_OnAbstractclass() {
        assertTrue("should mark as deprecated", isClassDeprecated(DeprecatedAbstractClass.class));
    }

    @Test
    public void isClassDeprecated_OnSuperclass() {
        assertTrue("should mark as deprecated", isClassDeprecated(SubClass.class));
    }

    private static boolean isClassDeprecated(Class<?> clazz) {
        return null != AnnotationUtils.findAnnotation(clazz, MyDeprecated.class);
    }

    @MyDeprecated
    static class DeprecatedClass {
    }

    @MyDeprecated
    abstract static class DeprecatedAbstractClass {
    }

    static class SubClass extends SuperClass {
    }

    @MyDeprecated
    static class SuperClass {
    }

    @Retention(RetentionPolicy.RUNTIME)
    // @Inherited
    @interface MyDeprecated {
    }
}

I have changed the title of this issue accordingly.

Comment From: jhoeller

Generally speaking, those AnnotationUtils operations are not meant to bend the standard Java annotation semantics, they are rather meant to operate on Spring-style annotations. Also, it constitutes an important performance optimization in 5.2 to bypass MergedAnnotations lookups for plain java.lang annotations completely. Our new internal metamodel there doesn't even store java.lang annotations so also saves some memory for cached class hierarchy representations.

That said, point taken that this worked before, in particular for the @Deprecated use case. I've restored basic superclass traversal for findAnnotation(Class, ...) even for the plain annotation case.