JDK-8320575 : generic type information lost on mandated parameters of record's compact constructors
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 21,22,23
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2023-11-20
  • Updated: 2024-11-08
  • Resolved: 2024-05-24
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.
JDK 21 JDK 23
21.0.6-oracleFixed 23 b25Fixed
Related Reports
CSR :  
Duplicate :  
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Tested on MacBook Pro M2, Ventura 13.4
Java 21.0.1

A DESCRIPTION OF THE PROBLEM :
When, via reflection, trying to determine the generic type of an argument of the default record constructor,
the generic type cannot be determined in Java 21.
This did work in pre 21 versions. Tested in 17.0.9.

REGRESSION : Last worked in version 17.0.9

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run provided test case.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Expect generic type to be available in the default record constructor.
ACTUAL -
No generic type information available.

---------- BEGIN SOURCE ----------
import java.util.Optional;

public class Reproducer {
    interface NoConstructorDeclarations {
        record Person(Optional<String> name, Optional<Integer> age) {}
    }

    interface AnnotatedCompactConstructor {
        record Person(Optional<String> name, Optional<Integer> age) {
            @Deprecated public Person {}
        }
    }

    interface AnotatedExplicitCanonicalConstructor  {
        record Person(Optional<String> name, Optional<Integer> age) {
            @Deprecated
            public Person(Optional<String> name, Optional<Integer> age) {
                this.name = name;
                this.age = age;
            }
        }
    }

    public static void main(String args[]) {
        for(var approach: Reproducer.class.getDeclaredClasses()) {
            Class<?> recordClass = approach.getClasses()[0];
            System.out.println(approach.getSimpleName());
  
            var constructor = recordClass.getConstructors()[0];
            System.out.println(constructor.isAnnotationPresent(Deprecated.class));
            for(var p: constructor.getParameters()) {
                System.out.println(p);
            }
            System.out.println();
        }
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Explicitly add a canonical constructor with all record properties.

FREQUENCY : always



Comments
Fix request [21u] I backport this for parity with 21.0.6-oracle. CSR available. Medium risk, it changes behaviour but also fixes an issue. Clean backport, but I removed parts according to CSR Test passes and fails without the fix. SAP nightly testing passed.
08-10-2024

A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk21u-dev/pull/1026 Date: 2024-10-07 07:56:28 +0000
07-10-2024

Changeset: 7bf1989f Author: Vicente Romero <vromero@openjdk.org> Date: 2024-05-24 20:43:23 +0000 URL: https://git.openjdk.org/jdk/commit/7bf1989f59695c3d08b4bd116fb4c022cf9661f4
24-05-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/17070 Date: 2023-12-11 23:33:16 +0000
11-12-2023

caused by: JDK-8292275, introduced in: jdk-21+21. But not that fix for JDK-8292275 introduced the error. I think that it just uncovered a (hidden | known) issue in reflection: this patch fixes the problem: ``` diff --git a/src/java.base/share/classes/java/lang/reflect/Executable.java b/src/java.base/share/classes/java/lang/reflect/Executable.java index 420be5029b0..092dde8177d 100644 --- a/src/java.base/share/classes/java/lang/reflect/Executable.java +++ b/src/java.base/share/classes/java/lang/reflect/Executable.java @@ -340,8 +340,8 @@ Type[] getAllGenericParameterTypes() { int fromidx = 0; for (int i = 0; i < out.length; i++) { final Parameter param = params[i]; - if (param.isSynthetic() || param.isImplicit()) { - // If we hit a synthetic or mandated parameter, + if (param.isSynthetic()) { + // If we hit a synthetic parameter, // use the non generic parameter info. out[i] = nonGenericParamTypes[i]; } else { ``` but the specification of java.lang.reflect.Executable::getGenericParameterTypes states: ``` * If * generic information is present, only parameters explicitly * present in the source will be returned; if generic information * is not present, implicit and synthetic parameters may be * returned as well. ``` so now that the mandated flag is added to formal parameters of a compact constructor of a record class, theirs generic information is just ignored. Although the reporter found the issue for records, this is a general issue with mandated parameters
11-12-2023

JDK 17: ======== PS C:\test> java Reproducer AnotatedExplicitCanonicalConstructor true java.util.Optional<java.lang.String> name java.util.Optional<java.lang.Integer> age AnnotatedCompactConstructor true java.util.Optional<java.lang.String> name java.util.Optional<java.lang.Integer> age NoConstructorDeclarations false java.util.Optional<java.lang.String> name java.util.Optional<java.lang.Integer> age JDK 21.0.1: ========== PS C:\test> C:\jdk\jdk-21.0.1\bin\java Reproducer AnotatedExplicitCanonicalConstructor true java.util.Optional<java.lang.String> name java.util.Optional<java.lang.Integer> age AnnotatedCompactConstructor true java.util.Optional name java.util.Optional age NoConstructorDeclarations false java.util.Optional<java.lang.String> name java.util.Optional<java.lang.Integer> age Open JDK 22: ============= PS C:\test> C:\jdk\openjdk-22-ea+16_windows-x64_bin\jdk-22\bin\java Reproducer AnotatedExplicitCanonicalConstructor true java.util.Optional<java.lang.String> name java.util.Optional<java.lang.Integer> age AnnotatedCompactConstructor true java.util.Optional<java.lang.String> name java.util.Optional<java.lang.Integer> age NoConstructorDeclarations false java.util.Optional<java.lang.String> name java.util.Optional<java.lang.Integer> The issue is thus reproducible in JDK 21
22-11-2023