Skip to content

Invalid method return type metadata for ClassFile variant on JDK 24+#36577

Merged
bclozel merged 2 commits intospring-projects:7.0.xfrom
bebeis:fix/resovle-type-name
Apr 2, 2026
Merged

Invalid method return type metadata for ClassFile variant on JDK 24+#36577
bclozel merged 2 commits intospring-projects:7.0.xfrom
bebeis:fix/resovle-type-name

Conversation

@bebeis
Copy link
Copy Markdown
Contributor

@bebeis bebeis commented Mar 31, 2026

Problem

On JDK 24+, @Bean methods declared with a void return type are not rejected during configuration parsing, and no BeanDefinitionParsingException is raised.

Reproduction

JDK 21 (expected behavior)

package com.java21.asm.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public void doSetUp() {
        // invalid @Bean method
    }
}
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Bean method 'doSetUp' must not be declared as void; change the method's return type or its annotation.
@Test
void should_throw_beanDefinitionParsingException() {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.java21.asm.config");

    assertThatThrownBy(ctx::refresh)
            .isInstanceOf(BeanDefinitionParsingException.class)
            .hasMessageContaining("must not be declared as void");
}

JDK 24+ (actual behavior)

package com.java25.classfile.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public void doSetUp() {
		// no exception is raised
    }
}

No exception is thrown and the context starts normally.

Test Failed

@Test
void should_throw_beanDefinitionParsingException() {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.java25.classfile.config");

    assertThatThrownBy(ctx::refresh)
            .isInstanceOf(BeanDefinitionParsingException.class)
            .hasMessageContaining("must not be declared as void");
}

Root cause

On JDK 24+, Spring uses ClassFileMethodMetadata (backed by the JDK 24 ClassFile API) to read method metadata. The getReturnTypeName() method was implemented as:

// Before fix
String returnTypeName = returnType.packageName() + "." + returnType.displayName();

ClassDesc.packageName() is only defined for class and interface types. Using it unconditionally when building return type names causes malformed results for primitive and array types.

Return type Before fix After fix
void ".void" "void"
int ".int" "int"
String "java.lang.String" "java.lang.String"
String[] ".String[]" "java.lang.String[]"

The most visible symptom is that BeanMethod.validate() in spring-context checks:

// spring-context: BeanMethod.java
if ("void".equals(getMetadata().getReturnTypeName())) {
    problemReporter.error(new VoidDeclaredMethodError());
}

Because getReturnTypeName() returns ".void" instead of "void" on JDK 24+, this check silently passes — allowing void @Bean methods without any error.

Fix

Replaced the inline string concatenation with a resolveTypeName() helper that handles each case correctly:

// After fix — ClassFileMethodMetadata.java (java24 source set)
private static String resolveTypeName(ClassDesc type) {
    if (type.isPrimitive()) {
        return type.displayName();
    }
    if (type.isArray()) {
        return resolveTypeName(type.componentType()) + "[]";
    }
    String packageName = type.packageName();
    return (packageName.isEmpty() ? type.displayName() : packageName + "." + type.displayName());
}

This ensures:

  • Correct handling of primitive types (void, int, etc.)
  • Proper resolution of array types based on their component type
  • Consistent behavior with ASM and reflection-based metadata
  • Safe handling of types in the default package, avoiding malformed names such as .MyClass

This change aligns ClassFile-based metadata with existing ASM and reflection-based implementations.

Tests

  • Added ClassFileMethodMetadataTests (java24Test)
    • covers primitive, array, reference, and void return types
  • Extended AbstractMethodMetadataTests
    • added explicit void return coverage

Notes

Similar issues exist in other ClassFile-based implementations (e.g. ClassFileAnnotationDelegate and Source#toString()), where primitive, array, and default package types may also be rendered incorrectly. These are not addressed in this PR.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Mar 31, 2026
@bclozel bclozel self-assigned this Mar 31, 2026
@bclozel bclozel added type: regression A bug that is also a regression in: core Issues in core modules (aop, beans, core, context, expression) and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Mar 31, 2026
@bclozel
Copy link
Copy Markdown
Member

bclozel commented Mar 31, 2026

Please hold off further changes here, I'll take care of fixing the failing build and additional cases.

@bclozel bclozel changed the base branch from main to 7.0.x April 1, 2026 09:09
@bclozel bclozel force-pushed the fix/resovle-type-name branch from f8325f8 to 6f4f0ed Compare April 1, 2026 09:12
@bebeis
Copy link
Copy Markdown
Contributor Author

bebeis commented Apr 1, 2026

I realized that I mistakenly used spaces instead of a tab on one line (ClassFileMethodMetadata.java:165 only). Sorry about that.
I’ll hold off on pushing any changes and leave it to you as suggested.

@bclozel bclozel added this to the 7.0.7 milestone Apr 1, 2026
Return correct names for primitive and array types
Introduce resolveTypeName helper for ClassDesc handling
Add ClassFileMethodMetadataTests (java24Test)
Extend AbstractMethodMetadataTests with void return case

See spring-projectsgh-36577

Signed-off-by: Junseo Bae <ferrater1013@gmail.com>
bclozel added a commit to bebeis/spring-framework that referenced this pull request Apr 2, 2026
Prior to this commit, the ClassFile variant for  annotation and method
metadata would report incorrect metadata for:
* the method return type names in case of primitives and array types
* `toString` values for methods
* `equals` and `hashcode` information for methods

This commit expands the test suite and ensures that the ASM and
ClassFile variants are aligned.

Fixes spring-projectsgh-36577
@bclozel bclozel force-pushed the fix/resovle-type-name branch from 6f4f0ed to 36c9f42 Compare April 2, 2026 08:17
Prior to this commit, the ClassFile variant for  annotation and method
metadata would report incorrect metadata for:
* the method return type names in case of primitives and array types
* `toString` values for methods
* `equals` and `hashcode` information for methods

This commit expands the test suite and ensures that the ASM and
ClassFile variants are aligned.

Fixes spring-projectsgh-36577

Signed-off-by: Brian Clozel <brian.clozel@broadcom.com>
@bclozel bclozel force-pushed the fix/resovle-type-name branch from 36c9f42 to 40ef6f6 Compare April 2, 2026 08:18
@bclozel bclozel changed the title Fix ClassFileMethodMetadata return type names for primitives and arrays on JDK 24+ Invalid method return type metadata for ClassFile variant on JDK 24+ Apr 2, 2026
@bclozel bclozel closed this in 40ef6f6 Apr 2, 2026
@bclozel bclozel merged commit 40ef6f6 into spring-projects:7.0.x Apr 2, 2026
1 of 2 checks passed
@bclozel
Copy link
Copy Markdown
Member

bclozel commented Apr 2, 2026

Thanks for your contribution @bebeis !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in 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

Successfully merging this pull request may close these issues.

3 participants