JDK-8303112 : For classes declared in instance methods, constructor.getParameterAnnotations does not include implicit enclosing param
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 17.0.2,17.0.6,20,21
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: linux
  • CPU: x86_64
  • Submitted: 2023-02-23
  • Updated: 2024-07-02
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Relates :  
Relates :  
Description
Affects OpenJDK 17.0.6, 20+36-2344 (GA), 21-ea+10-784.

With the following classes:

public class TopLevel {
    public Class<?> myInstanceMethod() {
        class InstanceMethodNamedClass {
            public InstanceMethodNamedClass(@MyParameterAnnotation String foo) {
            }
        }
        return InstanceMethodNamedClass.class;
    }
}

@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyParameterAnnotation {
}

... calling `getParameterAnnotations()`on the constructor of `InstanceMethodNamedClass` will return an array that excludes the implicit enclosing parameter representing the instance of `TopLevel`:

var clazz = new TopLevel().myInstanceMethod();
var constructor = clazz.getDeclaredConstructors()[0];
assert constructor.getParameters().length == 2; // succeeds
assert constructor.getParameterAnnotations().length == 2; // fails; length is actually 1

This behavior seems wrong since the javadoc of getParameterAnnotations() states:

> Synthetic and mandated parameters (see explanation below), such as the outer "this" parameter to an
> inner class constructor will be represented in the returned array


-----

Executable reproducer: see https://github.com/yrodiere/jdk-playground/tree/parameter-annotations, check out branch "parameter-annotations" and run `./mvnw clean install`

-----

Relates to JDK-8180892.

This bug is similar to JDK-8074977, but different since it is still present after JDK-8074977 was fixed.
Comments
This is a local class: https://github.com/openjdk/jdk/blob/38e17148faef7799515478bd834ed2fa1a5153de/src/java.base/share/classes/java/lang/reflect/Constructor.java#L629 explicitly rejects parameter remapping for local classes.
25-03-2023

To anyone needing a workaround, we implemented one here: https://github.com/hibernate/hibernate-validator/pull/1306/files Basically the workaround involves detecting that the number of parameters is inconsistent and re-creating the arrays of annotations, inserting empty arrays of annotations for implicit/synthetic parameters. There's probably a cost to that, so some level of caching might be necessary depending on how you use the metadata. Constructor<?> constructor = /* ... */; int parameterCount = constructor.getParameterCount(); Parameter[] parameterArray = constructor.getParameters(); Annotation[][] parameterAnnotationsArray = constructor.getParameterAnnotations(); Annotation[][] annotationsForJDK8303112 = null; if ( parameterAnnotationsArray.length != parameterCount ) { // This is a workaround for https://bugs.openjdk.org/browse/JDK-8303112 // We're in a situation where parameter.getAnnotation()/parameter.getAnnotations() // is buggy when there are implicit/synthetic parameters, // because constructor.getParameterAnnotations() (wrongly) ignores implicit/synthetic parameters // while parameter.getAnnotations() (rightly) assumes they are present in the array. annotationsForJDK8303112 = new Annotation[parameterCount][]; int nonImplicitNorSyntheticParamIndex = 0; for ( int i = 0; i < parameterCount; i++ ) { Parameter parameter = parameterArray[i]; if ( parameter.isImplicit() || parameter.isSynthetic() ) { annotationsForJDK8303112[i] = new Annotation[0]; } else { annotationsForJDK8303112[i] = parameterAnnotationsArray[nonImplicitNorSyntheticParamIndex]; ++nonImplicitNorSyntheticParamIndex; } } } else { annotationsForJDK8303112 = parameterAnnotationsArray }
27-02-2023