Hello Spring team,
First of all, I just wanted to thank you guys for the release of 4.0.0-M1. We are very excited for the final 4.0.0 version.
Just wanted to reach out with an issue for 4.0.0-M1 + Java 24 + GraalVM native image. I understand 4.0.0-M1 is just a milestone version. I also understand Spring Boot is being modularized in this version. But I managed to have a 100% reproducible result. I tried to make it as minimal as possible.
I have a very small 4 classes reproducible:
This is just the main class, nothing here.
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class QuestionApplication {
public static void main(final String[] args) {
SpringApplication.run(QuestionApplication.class);
}
}
This is a class that deals with open telemetry tracing. I suspect the issue might be from here? But I do not have the technical capabilities to prove this.
package org.example;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TracingConfiguration {
@Bean
public SpanExporter ptlpGrpcSpanExporter() {
return OtlpGrpcSpanExporter.builder().setEndpoint("https://otlp-gateway-prod-us-central-0.grafana.net/otlp/v1/traces").addHeader("Authorization", "Basic ODYDA9").build(); //fake
}
}
This is just a Reactor Kafka consumer configuration
package org.example;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.observation.ObservationRegistry;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.kafka.receiver.KafkaReceiver;
import reactor.kafka.receiver.MicrometerConsumerListener;
import reactor.kafka.receiver.ReceiverOptions;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Configuration
public class ConsumerConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(ConsumerConfiguration.class);
@Bean
public KafkaReceiver<String, String> kafkaReceiver(final MeterRegistry meterRegistry, final ObservationRegistry observationRegistry, final SslBundles sslBundles) {
final Map<String, Object> properties = new ConcurrentHashMap<>();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "bootstrapServer");
properties.put(ConsumerConfig.CLIENT_ID_CONFIG, "consumerGroup");
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "consumerGroup");
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
final ReceiverOptions<String, String> receiverOptions = ReceiverOptions.create(properties);
return KafkaReceiver.create(receiverOptions
.consumerListener(new MicrometerConsumerListener(meterRegistry))
.withObservation(observationRegistry)
.subscription(Collections.singleton("topicSource")));
}
}
This is just the main service class, which does nothing but consumes messages in a reactive way with observability.
package org.example;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;
import reactor.core.observability.micrometer.Micrometer;
import reactor.core.publisher.Mono;
import reactor.kafka.receiver.KafkaReceiver;
import reactor.kafka.receiver.observation.KafkaReceiverObservation;
import reactor.kafka.receiver.observation.KafkaRecordReceiverContext;
import java.util.Locale;
import java.util.function.Function;
@Service
public final class MyService implements CommandLineRunner {
private final KafkaReceiver<String, String> kafkaReceiver;
private final ObservationRegistry observationRegistry;
public MyService(final KafkaReceiver<String, String> receiver, final ObservationRegistry registry) {
this.kafkaReceiver = receiver;
this.observationRegistry = registry;
}
@Override
public void run(final String... args) {
kafkaReceiver.receiveAutoAck().concatMap(Function.identity()).flatMap(this::transform).subscribe();
}
private Mono<String> transform(final ConsumerRecord<String, String> consumerRecord) {
final var receiverObservation = KafkaReceiverObservation.RECEIVER_OBSERVATION.start(null, KafkaReceiverObservation.DefaultKafkaReceiverObservationConvention.INSTANCE, () -> new KafkaRecordReceiverContext(consumerRecord, "abc", "def"), observationRegistry);
return Mono.just(consumerRecord)
.map(one -> one.value().toUpperCase(Locale.ROOT))
.tap(Micrometer.observation(observationRegistry))
.doOnTerminate(receiverObservation::stop)
.doOnError(receiverObservation::error)
.contextWrite(context -> context.put(ObservationThreadLocalAccessor.KEY, receiverObservation));
}
}
The most important file is this 4.0.0-M1 pom file. As you can see in this pom file, I added the modularized Spring Boot.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.0-M1</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>aotissue</artifactId>
<version>1.1</version>
<properties>
<java.version>24</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-opentelemetry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-webclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core-micrometer</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor.kafka</groupId>
<artifactId>reactor-kafka</artifactId>
<version>1.3.23</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>com.github.loki4j</groupId>
<artifactId>loki-logback-appender</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-jvm</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<env>
<BP_JVM_VERSION>24</BP_JVM_VERSION>
</env>
</image>
</configuration>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>confluent</id>
<url>https://packages.confluent.io/maven/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
Now, running this Maven command mvn -U -Pnative spring-boot:build-image -DskipTests
to generate the native image, the build works fine.
However, at run time, reprodubile 100%, there is this error trace
Application run failed
org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:414)
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:400)
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:592)
at java.base@24.0.1/java.lang.Iterable.forEach(Iterable.java:75)
at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:370)
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:306)
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:997)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:621)
at org.springframework.boot.web.server.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:67)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:435)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1344)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1333)
at org.example.QuestionApplication.main(QuestionApplication.java:13)
at java.base@24.0.1/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.lang.ExceptionInInitializerError
at java.base@24.0.1/java.lang.Class.ensureInitialized(DynamicHub.java:658)
at java.base@24.0.1/java.lang.Class.ensureInitialized(DynamicHub.java:658)
at java.base@24.0.1/java.lang.Class.ensureInitialized(DynamicHub.java:658)
at java.base@24.0.1/java.lang.Class.ensureInitialized(DynamicHub.java:658)
at io.netty.util.internal.PlatformDependent.newFixedMpmcQueue(PlatformDependent.java:1177)
at io.netty.buffer.AdaptivePoolingAllocator.createSharedChunkQueue(AdaptivePoolingAllocator.java:238)
at io.netty.buffer.AdaptivePoolingAllocator.access$600(AdaptivePoolingAllocator.java:85)
at io.netty.buffer.AdaptivePoolingAllocator$MagazineGroup.<init>(AdaptivePoolingAllocator.java:369)
at io.netty.buffer.AdaptivePoolingAllocator.createMagazineGroupSizeClasses(AdaptivePoolingAllocator.java:211)
at io.netty.buffer.AdaptivePoolingAllocator.<init>(AdaptivePoolingAllocator.java:182)
at io.netty.buffer.AdaptiveByteBufAllocator.<init>(AdaptiveByteBufAllocator.java:56)
at io.netty.buffer.AdaptiveByteBufAllocator.<init>(AdaptiveByteBufAllocator.java:51)
at io.netty.buffer.AdaptiveByteBufAllocator.<init>(AdaptiveByteBufAllocator.java:47)
at io.netty.buffer.ByteBufUtil.<clinit>(ByteBufUtil.java:90)
at io.netty.buffer.ByteBufAllocator.<clinit>(ByteBufAllocator.java:24)
at io.netty.channel.DefaultChannelConfig.<init>(DefaultChannelConfig.java:60)
at io.netty.channel.epoll.EpollChannelConfig.<init>(EpollChannelConfig.java:48)
at io.netty.channel.epoll.EpollServerChannelConfig.<init>(EpollServerChannelConfig.java:42)
at io.netty.channel.epoll.EpollServerSocketChannelConfig.<init>(EpollServerSocketChannelConfig.java:34)
at io.netty.channel.epoll.EpollServerSocketChannel.<init>(EpollServerSocketChannel.java:59)
at io.netty.channel.epoll.EpollServerSocketChannel.<init>(EpollServerSocketChannel.java:45)
at reactor.netty.resources.DefaultLoopEpoll.getChannel(DefaultLoopEpoll.java:58)
at reactor.netty.resources.LoopResources.onChannel(LoopResources.java:243)
at reactor.netty.tcp.TcpResources.onChannel(TcpResources.java:251)
at reactor.netty.transport.TransportConfig.lambda$connectionFactory$1(TransportConfig.java:277)
at reactor.netty.transport.TransportConnector.doInitAndRegister(TransportConnector.java:279)
at reactor.netty.transport.TransportConnector.bind(TransportConnector.java:89)
at reactor.netty.transport.ServerTransport.lambda$bind$0(ServerTransport.java:122)
at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:61)
at reactor.core.publisher.Mono.subscribe(Mono.java:4571)
at reactor.core.publisher.Mono.block(Mono.java:1801)
at reactor.netty.transport.ServerTransport.bindNow(ServerTransport.java:157)
at reactor.netty.transport.ServerTransport.bindNow(ServerTransport.java:142)
at org.springframework.boot.reactor.netty.NettyWebServer.startHttpServer(NettyWebServer.java:171)
at org.springframework.boot.reactor.netty.NettyWebServer.start(NettyWebServer.java:115)
at org.springframework.boot.web.server.reactive.context.WebServerManager.start(WebServerManager.java:55)
at org.springframework.boot.web.server.reactive.context.WebServerStartStopLifecycle.start(WebServerStartStopLifecycle.java:41)
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:411)
... 15 more
Caused by: java.lang.RuntimeException: java.lang.NoSuchFieldException: producerIndex
at io.netty.util.internal.shaded.org.jctools.util.UnsafeAccess.fieldOffset(UnsafeAccess.java:111)
at io.netty.util.internal.shaded.org.jctools.queues.MpmcArrayQueueProducerIndexField.<clinit>(MpmcArrayQueue.java:51)
... 53 more
Caused by: java.lang.NoSuchFieldException: producerIndex
at java.base@24.0.1/java.lang.Class.checkField(DynamicHub.java:1151)
at java.base@24.0.1/java.lang.Class.getDeclaredField(DynamicHub.java:1293)
at io.netty.util.internal.shaded.org.jctools.util.UnsafeAccess.fieldOffset(UnsafeAccess.java:107)
... 54 more
jdktwentyfive@jdktwentyfive:~/IdeaProjects/springbootfouris
I can confirm that using Spring Boot 3.5.x, the same code will work.
May I ask what is the root cause of this issue?
Thank you for your time.