I'm trying to run an app with Java 17 and Spring Boot 3 using GraalVM native image support. It's a very simple app with just a GET endpoint. The app uses ehcache and Spring Boot cache to cache the result of one method. More specifically, I'm using the following Maven dependencies for cache:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version>
</dependency>
and here is my ehcache config which seems to be the problem:
import java.time.Duration;
import javax.cache.CacheManager;
import javax.cache.Caching;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.jsr107.Eh107Configuration;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@EnableCaching
@Configuration
public class EhcacheConfiguration {
@Bean
public CacheManager EhcacheManager() {
var cachingProvider = Caching.getCachingProvider();
var cacheManager = cachingProvider.getCacheManager();
cacheManager.createCache("myTestOne",
Eh107Configuration.fromEhcacheCacheConfiguration(
CacheConfigurationBuilder
.newCacheConfigurationBuilder(Integer.class,
MyObject.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.offheap(100, MemoryUnit.MB)
.build())
.withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofHours(23)))
.build()
)
);
return cacheManager;
}
}
also here is the part where I declare the spring native maven plugin:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
and I'm trying to build and run the app using docker-compose, here is the Dockerfile:
FROM ghcr.io/graalvm/graalvm-ce:ol7-java17-22.3.0 as build
RUN gu install native-image
RUN yum install -y zip unzip libstdc++-static freetype-devel
WORKDIR /app
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
RUN chmod +x ./mvnw
COPY src src
RUN ./mvnw clean package -DskipTests -Pnative
ENTRYPOINT ["sh", "-c" ,"./target/test-app-one"]
and here is the docker-compose.yml file:
services:
app-server:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
restart: always
I'm trying to start the docker-compose.yml by the following command:
docker-compose up --build
the build will finish successfully, but when it will try to start the app (on the last step of the Dockerfile), it will produce the following error. Here is the full log:
cache-test-app-one-app-server-1 | 2023-01-22T14:21:04.554Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
cache-test-app-one-app-server-1 | 2023-01-22T14:21:04.554Z INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
cache-test-app-one-app-server-1 | 2023-01-22T14:21:04.554Z INFO 1 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.5]
cache-test-app-one-app-server-1 | 2023-01-22T14:21:04.559Z INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
cache-test-app-one-app-server-1 | 2023-01-22T14:21:04.559Z INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 39 ms
cache-test-app-one-app-server-1 | 2023-01-22T14:21:04.568Z WARN 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh a
ttempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'EhcacheManager': Instantiation of supplied bean failed
cache-test-app-one-app-server-1 | 2023-01-22T14:21:04.569Z INFO 1 --- [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
cache-test-app-one-app-server-1 | 2023-01-22T14:21:04.570Z ERROR 1 --- [ main] o.s.boot.SpringApplication : Application run failed
cache-test-app-one-app-server-1 |
cache-test-app-one-app-server-1 | org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'EhcacheManager': Instantiation of supplied bean failed
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainInstanceFromSupplier(AbstractAutowireCapableBeanFactory.java:1236) ~[test-app-one
:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1210) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1157) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:961) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:915) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:584) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[test-app-one:3.0.2]
cache-test-app-one-app-server-1 | at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730) ~[test-app-one:3.0.2]
cache-test-app-one-app-server-1 | at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:432) ~[test-app-one:3.0.2]
cache-test-app-one-app-server-1 | at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[test-app-one:3.0.2]
cache-test-app-one-app-server-1 | at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) ~[test-app-one:3.0.2]
cache-test-app-one-app-server-1 | at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291) ~[test-app-one:3.0.2]
cache-test-app-one-app-server-1 | at com.test.testappone.TestAppOneApplication.main(TestAppOneApplication.java:10) ~[test-app-one:na]
cache-test-app-one-app-server-1 | Caused by: java.lang.IllegalArgumentException: The serializer: org.ehcache.impl.serialization.PlainJavaSerializer does not have a constructor that takes in a ClassLoader.
cache-test-app-one-app-server-1 | at org.ehcache.impl.config.serializer.DefaultSerializationProviderConfiguration.addSerializerFor(DefaultSerializationProviderConfiguration.java:99) ~[test-app-one:3.10
.8]
cache-test-app-one-app-server-1 | at org.ehcache.impl.config.serializer.DefaultSerializationProviderConfiguration.addSerializerFor(DefaultSerializationProviderConfiguration.java:75) ~[test-app-one:3.10
.8]
cache-test-app-one-app-server-1 | at org.ehcache.jsr107.EhcacheCachingProvider.createCacheManager(EhcacheCachingProvider.java:148) ~[test-app-one:3.10.8]
cache-test-app-one-app-server-1 | at org.ehcache.jsr107.EhcacheCachingProvider.getCacheManager(EhcacheCachingProvider.java:134) ~[test-app-one:3.10.8]
cache-test-app-one-app-server-1 | at org.ehcache.jsr107.EhcacheCachingProvider.getCacheManager(EhcacheCachingProvider.java:85) ~[test-app-one:3.10.8]
cache-test-app-one-app-server-1 | at org.ehcache.jsr107.EhcacheCachingProvider.getCacheManager(EhcacheCachingProvider.java:194) ~[test-app-one:3.10.8]
cache-test-app-one-app-server-1 | at org.ehcache.jsr107.EhcacheCachingProvider.getCacheManager(EhcacheCachingProvider.java:202) ~[test-app-one:3.10.8]
cache-test-app-one-app-server-1 | at com.test.testappone.EhcacheConfiguration.EhcacheManager(EhcacheConfiguration.java:22) ~[test-app-one:na]
cache-test-app-one-app-server-1 | at com.test.testappone.EhcacheConfiguration$$SpringCGLIB$$0.CGLIB$EhcacheManager$0(<generated>) ~[test-app-one:na]
cache-test-app-one-app-server-1 | at com.test.testappone.EhcacheConfiguration$$SpringCGLIB$$2.invoke(<generated>) ~[test-app-one:na]
cache-test-app-one-app-server-1 | at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[na:na]
cache-test-app-one-app-server-1 | at com.test.testappone.EhcacheConfiguration$$SpringCGLIB$$0.EhcacheManager(<generated>) ~[test-app-one:na]
cache-test-app-one-app-server-1 | at com.test.testappone.EhcacheConfiguration__BeanDefinitions.lambda$getEhcacheManagerInstanceSupplier$0(EhcacheConfiguration__BeanDefinitions.java:30) ~[na:na]
cache-test-app-one-app-server-1 | at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$withGenerator$0(BeanInstanceSupplier.java:173) ~[na:na]
cache-test-app-one-app-server-1 | at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:208) ~[na:na]
cache-test-app-one-app-server-1 | at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:59) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:47) ~[test-app-one:6.0.4]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:220) ~[na:na]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:208) ~[na:na]
cache-test-app-one-app-server-1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainInstanceFromSupplier(AbstractAutowireCapableBeanFactory.java:1225) ~[test-app-one
:6.0.4]
cache-test-app-one-app-server-1 | ... 18 common frames omitted
if I try to remove the caching maven dependencies and the caching functionality, the app will run and start just fine. It will also run fine as it is now if I don't run it as native image.
Here you can find the full code on my GitHub repository: https://github.com/ndil101/cache-test-app-one
Thanks to @grekier for suggesting to try the GraalVM native image tracing agent. Firstly I changed that part of code:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
into that:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
Then I run locally (on my host, not inside the docker container) the folllowing command:
./mvnw clean package -DskipTests -Pnative
Then I run the following command:
java -Dspring.aot.enabled=true -agentlib:native-image-agent=config-output-dir=/myPathOne -jar target/test-app-one-1.0.0.jar
and while the app was running I tested it by calling the simple one endpoint that it has. After that I closed the test-app-one
app via CTRL+C
.
After that, I checked on my directory: /myPathOne
(that I declaed) and there were some .json
files.
I took all those .json
files and copied them on my project, on the following path:
src/main/resources/META-INF/native-image
After that, I reverted my code, into:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
and then I run the docker-compose.yml
file, by:
docker-compose up --build
and then the app started correctly without producing any error. The endpoint also works ok.
I updated my GitHub repository with the new .json
files that make the app work.