I'm trying to upgrade from Spring Boot v3.5.4 to v4.0.0-M1, which also means upgrading to Spring Framework to 7.0.0-M7. After upgrading the dependencies, I get this error at startup

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: file [/Users/donal/workspace/myapp/build/classes/java/main/com/myapp/controller/request/BusinessUnitInvitationRequest.class]
    at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:476) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:318) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:276) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:128) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:347) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:283) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:206) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:174) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:442) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:306) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:349) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:118) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:782) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:600) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142) ~[spring-boot-web-server-4.0.0-M1.jar:4.0.0-M1]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737) ~[spring-boot-4.0.0-M1.jar:4.0.0-M1]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:435) ~[spring-boot-4.0.0-M1.jar:4.0.0-M1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-4.0.0-M1.jar:4.0.0-M1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1344) ~[spring-boot-4.0.0-M1.jar:4.0.0-M1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1333) ~[spring-boot-4.0.0-M1.jar:4.0.0-M1]
    at com.myapp.BidsEngineApplication.main(BidsEngineApplication.java:11) ~[main/:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[na:na]
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-4.0.0-M1.jar:4.0.0-M1]
Caused by: java.lang.ArrayStoreException: com.myapp.security.role.RoleType$5
    at java.base/java.util.stream.Nodes$FixedNodeBuilder.accept(Nodes.java:1232) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:215) ~[na:na]
    at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:722) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:636) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:291) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:656) ~[na:na]
    at org.springframework.core.type.classreading.ClassFileAnnotationMetadata.parseArrayValue(ClassFileAnnotationMetadata.java:125) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.core.type.classreading.ClassFileAnnotationMetadata.readAnnotationValue(ClassFileAnnotationMetadata.java:95) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.core.type.classreading.ClassFileAnnotationMetadata.createMergedAnnotation(ClassFileAnnotationMetadata.java:66) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.core.type.classreading.ClassFileAnnotationMetadata.lambda$createMergedAnnotations$0(ClassFileAnnotationMetadata.java:51) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:215) ~[na:na]
    at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:722) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560) ~[na:na]
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:727) ~[na:na]
    at org.springframework.core.type.classreading.ClassFileAnnotationMetadata.createMergedAnnotations(ClassFileAnnotationMetadata.java:53) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.core.type.classreading.ClassFileMethodMetadata.lambda$of$2(ClassFileMethodMetadata.java:144) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at java.base/java.util.Optional.map(Optional.java:260) ~[na:na]
    at org.springframework.core.type.classreading.ClassFileMethodMetadata.of(ClassFileMethodMetadata.java:144) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.core.type.classreading.ClassFileClassMetadata$Builder.method(ClassFileClassMetadata.java:289) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.core.type.classreading.ClassFileClassMetadata.lambda$of$0(ClassFileClassMetadata.java:208) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at java.base/java.util.ArrayList$Itr.forEachRemaining(ArrayList.java:1086) ~[na:na]
    at java.base/java.util.Collections$UnmodifiableCollection$1.forEachRemaining(Collections.java:1086) ~[na:na]
    at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:807) ~[na:na]
    at org.springframework.core.type.classreading.ClassFileClassMetadata.of(ClassFileClassMetadata.java:186) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.core.type.classreading.ClassFileMetadataReader.<init>(ClassFileMetadataReader.java:44) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.core.type.classreading.ClassFileMetadataReaderFactory.getMetadataReader(ClassFileMetadataReaderFactory.java:62) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:127) ~[spring-core-7.0.0-M7.jar:7.0.0-M7]
    at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:436) ~[spring-context-7.0.0-M7.jar:7.0.0-M7]
    ... 23 common frames omitted

The class in question is shown below

package com.myapp.controller.request;

import com.myapp.controller.validation.role.RoleNameSubset;
import com.myapp.security.role.RoleType;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;

public record BusinessUnitInvitationRequest(

    @NotNull(message = "Email can't be empty")
    @Email
    String email,

    @NotNull(message = "Role can't be empty")
    @RoleNameSubset(anyOf = {RoleType.BUSINESS_UNIT_ADMIN, RoleType.BUSINESS_UNIT_ADMIN_API})
    RoleType role
) {
    @Override
    public String email() {
        return email.toLowerCase();
    }
}

I'm using JDK v24

Comment From: bclozel

Hey @donalmurtagh

I have tried to reproduce this issue with an annotation attribute that points to an array of enums, on a record class. I also tried various things with Spring Boot Devtools, various scanning setups, etc.

Sorry but I cannot reproduce right now, I'm probably missing a critical information in @RoleNameSubset or some other configuration. Can you please share a minimal sample please?

Comment From: donalmurtagh

Hi @bclozel

I've created a minimal example that reproduces the issue (be sure to use the v4.0.0-M1 branch).

It uses JDK v24, but I've no reason to believe the JDK version is particularly relevant. You can reproduce the problem by checking out the repo and running

./gradlew bootRun

If you revert back to Spring Boot v3.5.5, the problem no longer occurs. The cause of the problem appears to be this code

package com.example.demo;

public enum RoleType {
    USER {
        @Override
        public String thisCausesTheError() {
            return "foo";
        }
    },
    ADMIN {
        @Override
        public String thisCausesTheError() {
            return "bar";
        }
    };

    public abstract String thisCausesTheError();
}

If you replace this with

package com.example.demo;

public enum RoleType {
    USER, ADMIN
}

the problem no longer occurs. In fact, the abstract method isn't necessary to reproduce the issue, the following is sufficient

package com.example.demo;

public enum RoleType {
    USER {
    },
    ADMIN {
    }
}

Comment From: bclozel

Thanks for the sample, this is indeed a bug with our new ClassFile API support introduced in #33616. Both Java 24 and expanding enum values as types were required to reproduce the problem.

The fix should be available soon in SNAPSHOTs.

Comment From: donalmurtagh

@bclozel thanks for the fix and the explanation of the problem. I'll try out the fix once v4.0.0-M2 of Spring Boot is available on Maven Central.

Comment From: bclozel

Reopened because this change caused another regression.

Comment From: donalmurtagh

@bclozel I've confirmed that this is resolved in Spring Boot v4.0.0-M2, thanks again

Comment From: bclozel

Thanks for trying the milestones @donalmurtagh - but this fix hasn't been released yet. This will be released with Spring Boot 4.0.0-M3 on September 18th. Did you try SNAPSHOTs maybe?

Comment From: donalmurtagh

@bclozel no, I upgraded to Spring Boot v4.0.0-M2 and am no longer getting the error described in this issue. However, I am now getting a different error that occurs during the compile phase, so it may be that this compile-phase error is "shadowing" the classfile error.