Skip to content

Fix ClassFileMethodMetadata return type names for primitives and arrays on JDK 24+#36577

Open
bebeis wants to merge 1 commit intospring-projects:mainfrom
bebeis:fix/resovle-type-name
Open

Fix ClassFileMethodMetadata return type names for primitives and arrays on JDK 24+#36577
bebeis wants to merge 1 commit intospring-projects:mainfrom
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.

Return correct names for primitive and array types

Introduce resolveTypeName helper for ClassDesc handling

Add ClassFileMethodMetadataTests (java24Test)

Extend AbstractMethodMetadataTests with void return case

Signed-off-by: Junseo Bae <ferrater1013@gmail.com>
@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.

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