Slide 1

Slide 1 text

Four Approaches to Reducing Java Startup Time

Slide 2

Slide 2 text

Catherine Edelveis šŸ„‘ Developer Advocate at BellSoft šŸ˜Love Java, Spring, JavaFX šŸ‘©ā€šŸ’»Tech writer cat_edelveis cat-edelveis.bsky.social

Slide 3

Slide 3 text

Pasha Finkelshteyn šŸ„‘ Developer Advocate at BellSoft ā‰ˆ15 years in JVM, mostly @asm0dey.site asm0di0

Slide 4

Slide 4 text

Together, we are…

Slide 5

Slide 5 text

Together, we are… The BellSoft’s DevRel Gang!

Slide 6

Slide 6 text

About BellSoft Member of: JCP Executive Committee OpenJDK Vulnerability Group GraalVM Advisory Board Linux Foundation Cloud Native Computing Foundation Founded in 2017 by Java and Linux experts with 15+ years of experience working at Sun/Oracle.

Slide 7

Slide 7 text

About BellSoft Products: Liberica JDK Liberica Native Image Kit Alpaquita Linux Liberica is the JDK officially recommended by

Slide 8

Slide 8 text

Enough about us :) We are here today to tell you a story about…

Slide 9

Slide 9 text

YOU! Enough about us :) We are here today to tell you a story about…

Slide 10

Slide 10 text

But first, four quick questions ;)

Slide 11

Slide 11 text

Who knows one way to reduce the startup of their Java application?

Slide 12

Slide 12 text

Who knows two ways to reduce the startup of their Java application?

Slide 13

Slide 13 text

Who knows three ways to reduce the startup of their Java application?

Slide 14

Slide 14 text

Who knows four ways to reduce the startup of their Java application?

Slide 15

Slide 15 text

Once upon a time, all was well in one startup…

Slide 16

Slide 16 text

Our startup is doing well! Java 21 LTS Spring Boot 3.4 Spring AI MongoDB, Redis JTE Deploying to the cloud @GetMapping(value = "/assistant", produces = TEXT_HTML_VALUE) public JteModel assistant() { return templates.assistantView("Bot Assistant - Chat"); }

Slide 17

Slide 17 text

Starting point: Dockerfile FROM bellsoft/liberica-runtime-container:jdk-21-musl as builder ARG project ENV project=${project} WORKDIR /app ADD ${project} /app/${project} ADD ../pom.xml ./ RUN cd ${project} && ./mvnw package FROM bellsoft/liberica-runtime-container:jre-21-musl ARG project ENV project=${project} RUN apk add curl WORKDIR /app COPY --from=builder /app/${project}/target/*.jar /app/app.jar ["j " " j " "/ / j "]

Slide 18

Slide 18 text

Starting point: Dockerfile FROM bellsoft/liberica-runtime-container:jdk-21-musl as builder RUN cd ${project} && ./mvnw package ARG project ENV project=${project} WORKDIR /app ADD ${project} /app/${project} ADD ../pom.xml ./ FROM bellsoft/liberica-runtime-container:jre-21-musl ARG project ENV project=${project} RUN apk add curl WORKDIR /app COPY --from=builder /app/${project}/target/*.jar /app/app.jar ["j " " j " "/ / j "]

Slide 19

Slide 19 text

Starting point: Dockerfile FROM bellsoft/liberica-runtime-container:jre-21-musl COPY --from=builder /app/${project}/target/*.jar /app/app.jar / j ARG project ENV project=${project} WORKDIR /app ADD ${project} /app/${project} ADD ../pom.xml ./ RUN cd ${project} && ./mvnw package ARG project ENV project=${project} RUN apk add curl WORKDIR /app ENTRYPOINT ["java", "-jar", "/app/app.jar"]

Slide 20

Slide 20 text

Starting point: Dockerfile ENTRYPOINT ["java", "-jar", "/app/app.jar"] / j ARG project ENV project=${project} WORKDIR /app ADD ${project} /app/${project} ADD ../pom.xml ./ RUN cd ${project} && ./mvnw package FROM bellsoft/liberica-runtime-container:jre-21-musl ARG project ENV project=${project} RUN apk add curl WORKDIR /app COPY --from=builder /app/${project}/target/*.jar /app/app.jar

Slide 21

Slide 21 text

Starting point: Startup Total startup time of both services: 6.5 s docker compose logs chat-api bot-assistant | grep Started Started BotAssistantApplication in 2.108 seconds (process running for 3.181) Started ChatApiApplication in 2.83 seconds (process running for 3.469)

Slide 22

Slide 22 text

We are growing! So many new users! Scale the services to meet the traffic

Slide 23

Slide 23 text

We are growing! So many new users! Scale the services to meet the traffic But wait...

Slide 24

Slide 24 text

We are growing! So many new users! Scale the services to meet the traffic But wait... Why the rollout is so slow?

Slide 25

Slide 25 text

We are an agile team, we need a fast feedback loop!

Slide 26

Slide 26 text

We are an agile team, we need a fast feedback loop! Need faster rollout for faster feedback

Slide 27

Slide 27 text

We are an agile team, we need a fast feedback loop! Need faster rollout for faster feedback The longer the start, the slower the rollout

Slide 28

Slide 28 text

We are an agile team, we need a fast feedback loop! Need faster rollout for faster feedback The longer the start, the slower the rollout Maybe Java is not cut out for the cloud?

Slide 29

Slide 29 text

We are an agile team, we need a fast feedback loop! Need faster rollout for faster feedback The longer the start, the slower the rollout Maybe Java is not cut out for the cloud? Maybe it’s not too late to migrate to Go?

Slide 30

Slide 30 text

Luckily, four brave developers work at this startup: The Defender The Sage The Explorer The Rebel

Slide 31

Slide 31 text

They are very different, but have something in common: They care about their software product and can’t sit idle in the troubled time.

Slide 32

Slide 32 text

And that’s where YOU take the stage!

Slide 33

Slide 33 text

I want painless integration with no incompatibilities! The Defender

Slide 34

Slide 34 text

AppCDS Save class metadata to the disc and read it from the archive Even better with Spring AOT (load even more classes!) Considerations: Use the same JVM Use the same classpath Bigger Docker image due to the archive Legal JVM Dopes Fo Legal JVM Dopes Fo… …

Slide 35

Slide 35 text

AppCDS Save class metadata to the disc and read it from the archive Even better with Spring AOT (load even more classes!) Considerations: Use the same JVM Use the same classpath Bigger Docker image due to the archive Let's take it for a spin! Legal JVM Dopes Fo Legal JVM Dopes Fo… …

Slide 36

Slide 36 text

process-aot org.springframework.boot spring-boot-maven-plugin process-aot

Slide 37

Slide 37 text

FROM bellsoft/liberica-runtime-container:jdk-21.0.7_9-musl as builder RUN cd ${project} && ./mvnw package ARG project ENV project=${project} WORKDIR /app ADD ${project} /app/${project} ADD ../pom.xml ./ FROM bellsoft/liberica-runtime-container:jre-21.0.7_9-cds-musl as optimizer ARG project ENV project=${project} WORKDIR /app COPY --from=builder /app/${project}/target/*.jar app.jar RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted

Slide 38

Slide 38 text

FROM bellsoft/liberica-runtime-container:jre-21.0.7_9-cds-musl as optimizer COPY --from=builder /app/${project}/target/*.jar app.jar RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted ADD ../pom.xml ./ RUN cd ${project} && ./mvnw package ARG project ENV project=${project} WORKDIR /app FROM bellsoft/liberica-runtime-container:jre-21.0.7_9-cds-musl RUN apk add curl WORKDIR /app COPY --from=optimizer /app/extracted/dependencies/ ./

Slide 39

Slide 39 text

FROM bellsoft/liberica-runtime-container:jre-21.0.7_9-cds-musl COPY --from=optimizer /app/extracted/dependencies/ ./ COPY --from=optimizer /app/extracted/spring-boot-loader/ ./ COPY --from=optimizer /app/extracted/snapshot-dependencies/ ./ COPY --from=optimizer /app/extracted/application/ ./ RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted RUN apk add curl WORKDIR /app RUN java -Dspring.aot.enabled=true \ -XX:ArchiveClassesAtExit=./application.jsa \ -Dspring.context.exit=onRefresh -jar /app/app.jar ENTRYPOINT ["java", "-Dspring.aot.enabled=true", \ "-XX:SharedArchiveFile=application.jsa", \ "-jar", "/app/app.jar"]

Slide 40

Slide 40 text

RUN java -Dspring.aot.enabled=true \ -XX:ArchiveClassesAtExit=./application.jsa \ -Dspring.context.exit=onRefresh -jar /app/app.jar j j j pp j y FROM bellsoft/liberica-runtime-container:jre-21.0.7_9-cds-musl RUN apk add curl WORKDIR /app COPY --from=optimizer /app/extracted/dependencies/ ./ COPY --from=optimizer /app/extracted/spring-boot-loader/ ./ COPY --from=optimizer /app/extracted/snapshot-dependencies/ ./ COPY --from=optimizer /app/extracted/application/ ./ ENTRYPOINT ["java", "-Dspring.aot.enabled=true", \ "-XX:SharedArchiveFile=application.jsa", \ "-jar", "/app/app.jar"]

Slide 41

Slide 41 text

ENTRYPOINT ["java", "-Dspring.aot.enabled=true", \ "-XX:SharedArchiveFile=application.jsa", \ "-jar", "/app/app.jar"] j j j pp j y FROM bellsoft/liberica-runtime-container:jre-21.0.7_9-cds-musl RUN apk add curl WORKDIR /app COPY --from=optimizer /app/extracted/dependencies/ ./ COPY --from=optimizer /app/extracted/spring-boot-loader/ ./ COPY --from=optimizer /app/extracted/snapshot-dependencies/ ./ COPY --from=optimizer /app/extracted/application/ ./ RUN java -Dspring.aot.enabled=true \ -XX:ArchiveClassesAtExit=./application.jsa \ -Dspring.context.exit=onRefresh -jar /app/app.jar

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

We need to prepare ourselves and gather knowledge! The Sage

Slide 44

Slide 44 text

Project Leyden Beyond AppCDS: AOT Cache with pre-compiled code Condensers -> optimizations Considerations: Still in the makings The more condensers applied, the bigger the cache

Slide 45

Slide 45 text

Project Leyden Beyond AppCDS: AOT Cache with pre-compiled code Condensers -> optimizations Considerations: Still in the makings The more condensers applied, the bigger the cache Meanwhile, we can experiment!

Slide 46

Slide 46 text

Let’s start with what is already implemented JEP 483: Ahead-of-Time Class Loading & Linking (JDK 24) Improve startup time by making the classes of an application instantly available, in a loaded and linked state, when the HotSpot Java Virtual Machine starts. Achieve this by monitoring the application during one run and storing the loaded and linked forms of all classes in a cache for use in subsequent runs. Lay a foundation for future improvements to both startup and warmup time.

Slide 47

Slide 47 text

FROM bellsoft/liberica-runtime-container:jdk-24-musl AS builder RUN cd ${project} && ./mvnw package ARG project ENV project=${project} WORKDIR /app ADD ${project} /app/${project} ADD ../pom.xml ./ FROM bellsoft/liberica-runtime-container:jre-24-cds-musl AS optimizer ARG project ENV project=${project} WORKDIR /app COPY --from=builder /app/${project}/target/*.jar app.jar RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted FROM bellsoft/liberica-runtime-container:jre-24-cds-musl AS runner

Slide 48

Slide 48 text

FROM bellsoft/liberica-runtime-container:jre-24-cds-musl AS optimizer COPY --from=builder /app/${project}/target/*.jar app.jar RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted / pp ADD ${project} /app/${project} ADD ../pom.xml ./ RUN cd ${project} && ./mvnw package ARG project ENV project=${project} WORKDIR /app FROM bellsoft/liberica-runtime-container:jre-24-cds-musl AS runner RUN apk add curl WORKDIR /app COPY --from=optimizer /app/extracted/dependencies/ ./ COPY --from=optimizer /app/extracted/spring-boot-loader/ ./ COPY --from=optimizer /app/extracted/snapshot-dependencies/ ./ f i i / / d/ li i / /

Slide 49

Slide 49 text

FROM bellsoft/liberica-runtime-container:jre-24-cds-musl AS runner COPY --from=optimizer /app/extracted/dependencies/ ./ COPY --from=optimizer /app/extracted/spring-boot-loader/ ./ COPY --from=optimizer /app/extracted/snapshot-dependencies/ ./ COPY --from=optimizer /app/extracted/application/ ./ WORKDIR /app COPY --from=builder /app/${project}/target/*.jar app.jar RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted RUN apk add curl WORKDIR /app RUN java -Dspring.aot.enabled=true -XX:AOTMode=record \ -XX:AOTConfiguration=app.aotconf -Dspring.context.exit=onRefresh -jar /app/app.jar RUN java -Dspring.aot.enabled=true -XX:AOTMode=create \ -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -jar /app/app.jar ENTRYPOINT ["java", "-Dspring.aot.enabled=true", "-XX:AOTCache=app.aot", " j " "/ / j "]

Slide 50

Slide 50 text

RUN java -Dspring.aot.enabled=true -XX:AOTMode=record \ -XX:AOTConfiguration=app.aotconf -Dspring.context.exit=onRefresh -jar /app/app.jar RUN java -Dspring.aot.enabled=true -XX:AOTMode=create \ -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -jar /app/app.jar COPY --from=builder /app/${project}/target/*.jar app.jar RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted FROM bellsoft/liberica-runtime-container:jre-24-cds-musl AS runner RUN apk add curl WORKDIR /app COPY --from=optimizer /app/extracted/dependencies/ ./ COPY --from=optimizer /app/extracted/spring-boot-loader/ ./ COPY --from=optimizer /app/extracted/snapshot-dependencies/ ./ COPY --from=optimizer /app/extracted/application/ ./ ENTRYPOINT ["java", "-Dspring.aot.enabled=true", "-XX:AOTCache=app.aot", "-jar", "/app/app.jar"]

Slide 51

Slide 51 text

ENTRYPOINT ["java", "-Dspring.aot.enabled=true", "-XX:AOTCache=app.aot", "-jar", "/app/app.jar"] COPY --from=builder /app/${project}/target/*.jar app.jar RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted FROM bellsoft/liberica-runtime-container:jre-24-cds-musl AS runner RUN apk add curl WORKDIR /app COPY --from=optimizer /app/extracted/dependencies/ ./ COPY --from=optimizer /app/extracted/spring-boot-loader/ ./ COPY --from=optimizer /app/extracted/snapshot-dependencies/ ./ COPY --from=optimizer /app/extracted/application/ ./ RUN java -Dspring.aot.enabled=true -XX:AOTMode=record \ -XX:AOTConfiguration=app.aotconf -Dspring.context.exit=onRefresh -jar /app/app.jar RUN java -Dspring.aot.enabled=true -XX:AOTMode=create \ -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -jar /app/app.jar

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

Not enough? Let’s push it even further and use the builds of premain in Leyden! We will be happy to get your feedback on what’s broken!

Slide 54

Slide 54 text

FROM bellsoft/alpaquita-linux-base:glibc AS downloader ADD https://is.gd/tZhyPF /java.tar.gz # just a placeholder RUN tar -zxvf java.tar.gz && mv /jdk-24 /java RUN apk add tar FROM bellsoft/alpaquita-linux-base:glibc AS builder ARG project WORKDIR /app COPY --from=downloader /java /java ADD ../pom.xml ./ ADD ${project} /app/${project} ENV JAVA_HOME=/java \ project=${project} RUN cd ${project} && ./mvnw package FROM bellsoft/alpaquita-linux-base:glibc AS optimizer ARG project

Slide 55

Slide 55 text

FROM bellsoft/alpaquita-linux-base:glibc AS builder COPY --from=downloader /java /java ADD ../pom.xml ./ ADD ${project} /app/${project} ENV JAVA_HOME=/java \ RUN cd ${project} && ./mvnw package p / g / y /j g j p RUN apk add tar RUN tar -zxvf java.tar.gz && mv /jdk-24 /java ARG project WORKDIR /app project=${project} FROM bellsoft/alpaquita-linux-base:glibc AS optimizer ARG project WORKDIR /app f b ild / /${ j }/ * j j

Slide 56

Slide 56 text

FROM bellsoft/alpaquita-linux-base:glibc AS optimizer COPY --from=builder /app/${project}/target/*.jar app.jar COPY --from=downloader /java /java RUN /java/bin/java -Djarmode=tools -jar app.jar extract --layers --destination extracted _ /j \ project=${project} RUN cd ${project} && ./mvnw package ARG project WORKDIR /app ENV project=${project} FROM bellsoft/alpaquita-linux-base:glibc AS runner RUN apk add curl WORKDIR /app f d l d /j /j

Slide 57

Slide 57 text

FROM bellsoft/alpaquita-linux-base:glibc AS runner COPY --from=downloader /java /java COPY --from=optimizer /app/extracted/dependencies/ ./ COPY --from=optimizer /app/extracted/spring-boot-loader/ ./ COPY --from=optimizer /app/extracted/snapshot-dependencies/ ./ COPY --from=optimizer /app/extracted/application/ ./ ENV project=${project} RUN /java/bin/java -Djarmode=tools -jar app.jar extract --layers --destination extracted RUN apk add curl WORKDIR /app RUN /java/bin/java -XX:CacheDataStore=./application.cds \ -Dspring.context.exit=onRefresh -jar /app/app.jar ENTRYPOINT ["/java/bin/java", "-XX:CacheDataStore=./application.cds", "-jar", "/app/app.jar"]

Slide 58

Slide 58 text

RUN /java/bin/java -XX:CacheDataStore=./application.cds \ -Dspring.context.exit=onRefresh -jar /app/app.jar ENV project=${project} RUN /java/bin/java -Djarmode=tools -jar app.jar extract --layers --destination extracted FROM bellsoft/alpaquita-linux-base:glibc AS runner RUN apk add curl WORKDIR /app COPY --from=downloader /java /java COPY --from=optimizer /app/extracted/dependencies/ ./ COPY --from=optimizer /app/extracted/spring-boot-loader/ ./ COPY --from=optimizer /app/extracted/snapshot-dependencies/ ./ COPY --from=optimizer /app/extracted/application/ ./ ENTRYPOINT ["/java/bin/java", "-XX:CacheDataStore=./application.cds", "-jar", "/app/app.jar"]

Slide 59

Slide 59 text

ENTRYPOINT ["/java/bin/java", "-XX:CacheDataStore=./application.cds", "-jar", "/app/app.jar"] ENV project=${project} RUN /java/bin/java -Djarmode=tools -jar app.jar extract --layers --destination extracted FROM bellsoft/alpaquita-linux-base:glibc AS runner RUN apk add curl WORKDIR /app COPY --from=downloader /java /java COPY --from=optimizer /app/extracted/dependencies/ ./ COPY --from=optimizer /app/extracted/spring-boot-loader/ ./ COPY --from=optimizer /app/extracted/snapshot-dependencies/ ./ COPY --from=optimizer /app/extracted/application/ ./ RUN /java/bin/java -XX:CacheDataStore=./application.cds \ -Dspring.context.exit=onRefresh -jar /app/app.jar

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

I found something exciting… Can’t wait to explore! The Explorer

Slide 62

Slide 62 text

GraalVM Native Image Ahead-of-time compilation Standalone executable (.exe) No OpenJDK required to run

Slide 63

Slide 63 text

Native Image: Any Considerations? Resource-demanding build process Several minutes to build the image Static compilation instead of dynamic one

Slide 64

Slide 64 text

Native Image: Any Considerations? Resource-demanding build process Several minutes to build the image Static compilation instead of dynamic one Let the journey begin!

Slide 65

Slide 65 text

First, let’s try it locally Build a fat JAR Build the image: The image was built, let’s run it! $JAVA_HOME/bin/native-image \ -jar bot-assistant/target/bot-assistant-1.0-SNAPSHOT-spring-boot.jar

Slide 66

Slide 66 text

I never knew the journey can be so pleasant!

Slide 67

Slide 67 text

I never knew the journey can be so pleasant! Uh-oh…

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

A dragon! Exception in thread "main" java.lang.IllegalStateException: java.util.zip.ZipException: zip END header not found at org.springframework.boot.loader.ExecutableArchiveLauncher.(ExecutableArchiveLaun at org.springframework.boot.loader.JarLauncher.(JarLauncher.java:42) at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:65) Caused by: java.util.zip.ZipException: zip END header not found

Slide 70

Slide 70 text

A dragon! java.util.zip.ZipException: zip END header not found Exception in thread "main" java.lang.IllegalStateException: at org.springframework.boot.loader.ExecutableArchiveLauncher.(ExecutableArchiveLaun at org.springframework.boot.loader.JarLauncher.(JarLauncher.java:42) at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:65) Caused by: java.util.zip.ZipException: zip END header not found

Slide 71

Slide 71 text

Apparently, a known issue āž”ļø Use mvn -Pnative native:compile to build the image. Maven plugin calls native-image java -jar under the hood but uses lots of flags that solve problems like the one above.

Slide 72

Slide 72 text

Apparently, a known issue āž”ļø Use mvn -Pnative native:compile to build the image. Maven plugin calls native-image java -jar under the hood but uses lots of flags that solve problems like the one above. Alright, let's try with a plugin!

Slide 73

Slide 73 text

If you use Spring Boot Parent POM, the Native Image profile is already configured! āž”ļø Enable it by simply running mvn -Pnative native:compile

Slide 74

Slide 74 text

Another dragon! Error: Classes that should be initialized at run time got initialized during image building: com.ctc.wstx.api.CommonConfig was unintentionally initialized at build time. com.ctc.wstx.stax.WstxInputFactory was unintentionally initialized at build time. com.ctc.wstx.api.ReaderConfig was unintentionally initialized at build time. com.ctc.wstx.util.DefaultXmlSymbolTable was unintentionally initialized at build time. To see how the classes got initialized, use --trace-class-initialization=com.ctc.wstx.api.CommonConfig,com.ctc.wstx.stax.WstxInputFactory,com

Slide 75

Slide 75 text

Another dragon! Error: Classes that should be initialized at run time got initialized during image building: To see how the classes got initialized, use --trace-class-initialization=com.ctc.wstx.api.CommonConfig,com.ctc.wstx.stax.WstxInputFactory,com com.ctc.wstx.api.CommonConfig was unintentionally initialized at build time. com.ctc.wstx.stax.WstxInputFactory was unintentionally initialized at build time. com.ctc.wstx.api.ReaderConfig was unintentionally initialized at build time. com.ctc.wstx.util.DefaultXmlSymbolTable was unintentionally initialized at build time.

Slide 76

Slide 76 text

Add native profile: native org.graalvm.buildtools native-maven-plugin build-native

Slide 77

Slide 77 text

Add native profile: org.graalvm.buildtools native-maven-plugin native build-native

Slide 78

Slide 78 text

Add native profile: com.github.asm0dey.botassistant.BotAssistantApplication bot-assistant target/native

Slide 79

Slide 79 text

Add the --trace-class-initialization argument: Run native org.graalvm.buildtools native-maven-plugin build-native mvn -Pnative native:compile

Slide 80

Slide 80 text

Add the --trace-class-initialization argument: Run --trace-class-initialization=com.ctc.wstx.util.DefaultXmlSymbolT bot-assistant target/native com.github.asm0dey.botassistant.BotAssistantApplication mvn -Pnative native:compile

Slide 81

Slide 81 text

More detailed output: Error: Classes that should be initialized at run time got initialized during image building: com.ctc.wstx.api.CommonConfig was unintentionally initialized at build time. org.springframework.http.codec.xml.XmlEventDecoder caused initialization of this class with the following trace: at com.ctc.wstx.api.CommonConfig.(CommonConfig.java:59) at com.ctc.wstx.stax.WstxInputFactory.(WstxInputFactory.java:149) at java.lang.invoke.DirectMethodHandle$Holder.newInvokeSpecial(DirectMethodHandle$Holder) at java.lang.invoke.Invokers$Holder.invokeExact_MT(Invokers$Holder) at jdk.internal.reflect.DirectConstructorHandleAccessor.invokeImpl(DirectConstructorHandle at jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandl

Slide 82

Slide 82 text

More detailed output: We found the culprit! org.springframework.http.codec.xml.XmlEventDecoder caused initialization of this class Error: Classes that should be initialized at run time got initialized during image building: com.ctc.wstx.api.CommonConfig was unintentionally initialized at build time. with the following trace: at com.ctc.wstx.api.CommonConfig.(CommonConfig.java:59) at com.ctc.wstx.stax.WstxInputFactory.(WstxInputFactory.java:149) at java.lang.invoke.DirectMethodHandle$Holder.newInvokeSpecial(DirectMethodHandle$Holder) at java.lang.invoke.Invokers$Holder.invokeExact_MT(Invokers$Holder) at jdk.internal.reflect.DirectConstructorHandleAccessor.invokeImpl(DirectConstructorHandle at jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandl

Slide 83

Slide 83 text

Alright, let’s add the option to initialize org.springframework.http.codec.xml.XmlEventDecoder at runtime: Run native org.graalvm.buildtools native-maven-plugin build-native mvn -Pnative native:compile

Slide 84

Slide 84 text

Alright, let’s add the option to initialize org.springframework.http.codec.xml.XmlEventDecoder at runtime: Run --initialize-at-run-time=org.springframework.http.codec.xml.XmlE bot-assistant target/native com.github.asm0dey.botassistant.BotAssistantApplication mvn -Pnative native:compile

Slide 85

Slide 85 text

The same dragon! This is one evasive dragon… Error: Classes that should be initialized at run time got initialized during image building: com.ctc.wstx.api.CommonConfig was unintentionally initialized at build time. To see why com.ctc.w com.ctc.wstx.stax.WstxInputFactory was unintentionally initialized at build time. To see why com. com.ctc.wstx.api.ReaderConfig was unintentionally initialized at build time. To see why com.ctc.ws com.ctc.wstx.util.DefaultXmlSymbolTable was unintentionally initialized at build time. To see why To see how the classes got initialized, use --trace-class-initialization=com.ctc.wstx.api.CommonCo

Slide 86

Slide 86 text

Apparently, another known issue āž”ļø Need to add the --strict-image-heap option. This mode requires only the classes that are stored in the image heap to be marked with –initialize-at- build-time. This effectively reduces the number of configuration entries necessary to achieve build-time initialization. Note that --strict-image-heap is enabled by default in Native Image starting from GraalVM for JDK 22.

Slide 87

Slide 87 text

Let’s add the --strict-image-heap option: Run --strict-image-heap bot-assistant target/native com.github.asm0dey.botassistant.BotAssistantApplication mvn -Pnative native:compile

Slide 88

Slide 88 text

Success!

Slide 89

Slide 89 text

Success! …Or is it?

Slide 90

Slide 90 text

Success! …Or is it? The image was build, let’s run it.

Slide 91

Slide 91 text

Another dragon! 2025-04-11T12:25:33.511+03:00 ERROR 64619 --- [bot-assistant] [nio-8081-exec-2] o.a.c.c.C.[.[.[/] java.lang.UnsatisfiedLinkError: jdk.jfr.internal.JVM.isExcluded(Ljava/lang/Class;)Z [symbol: Java_jdk_jfr_internal_JVM_isExcluded or Java_jdk_jfr_internal_JVM_isExcluded__Ljava_lang_Class_2] at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.access.JNINativeLinkage.getOrF at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.JNIGeneratedMethodSupport.nativ at [email protected]/jdk.jfr.internal.JVM.isExcluded(Native Method) ~[na:na] at [email protected]/jdk.jfr.internal.MetadataRepository.register(MetadataRepository

Slide 92

Slide 92 text

Another dragon! Apparently, when we create a connection to Redis, Spring creates a custom JFR event java.lang.UnsatisfiedLinkError: jdk.jfr.internal.JVM.isExcluded(Ljava/lang/Class;)Z [symbol: Java_jdk_jfr_internal_JVM_isExcluded or Java_jdk_jfr_internal_JVM_isExcluded__Ljava_lang_Class_2] 2025-04-11T12:25:33.511+03:00 ERROR 64619 --- [bot-assistant] [nio-8081-exec-2] o.a.c.c.C.[.[.[/] at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.access.JNINativeLinkage.getOrF at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.JNIGeneratedMethodSupport.nativ at [email protected]/jdk.jfr.internal.JVM.isExcluded(Native Method) ~[na:na] at [email protected]/jdk.jfr.internal.MetadataRepository.register(MetadataRepository

Slide 93

Slide 93 text

Even the Tracing Agent doesn’t detect that! So, let’s enable JFR explicitly: Run --enable-monitoring=jfr bot-assistant target/native com.github.asm0dey.botassistant.BotAssistantApplication --strict-image-heap mvn -Pnative native:compile

Slide 94

Slide 94 text

Finally, everything works!

Slide 95

Slide 95 text

Finally, everything works! …on my machine

Slide 96

Slide 96 text

Let’s move on to building a native image in a Docker container.

Slide 97

Slide 97 text

FROM bellsoft/liberica-native-image-kit-container:jdk-21-nik-23.1.6-musl as builder ARG project ENV project=${project} WORKDIR /app ADD ${project} /app/${project} ADD ../pom.xml ./ RUN cd ${project} && ./mvnw -Pnative native:compile FROM bellsoft/alpaquita-linux-base:stream-musl ARG project ENV project=${project} RUN apk add curl WORKDIR /app ENTRYPOINT ["/app/app"] COPY --from=builder /app/${project}/target/native/${project} /app/app

Slide 98

Slide 98 text

FROM bellsoft/liberica-native-image-kit-container:jdk-21-nik-23.1.6-musl as builder RUN cd ${project} && ./mvnw -Pnative native:compile ARG project ENV project=${project} WORKDIR /app ADD ${project} /app/${project} ADD ../pom.xml ./ FROM bellsoft/alpaquita-linux-base:stream-musl ARG project ENV project=${project} RUN apk add curl WORKDIR /app ENTRYPOINT ["/app/app"] COPY --from=builder /app/${project}/target/native/${project} /app/app

Slide 99

Slide 99 text

FROM bellsoft/alpaquita-linux-base:stream-musl ENTRYPOINT ["/app/app"] COPY --from=builder /app/${project}/target/native/${project} /app/app FROM bellsoft/liberica-native-image-kit-container:jdk-21-nik-23.1.6-musl as builder ARG project ENV project=${project} WORKDIR /app ADD ${project} /app/${project} ADD ../pom.xml ./ RUN cd ${project} && ./mvnw -Pnative native:compile ARG project ENV project=${project} RUN apk add curl WORKDIR /app

Slide 100

Slide 100 text

Another dragon! 2189.3 [6/8] Compiling methods... [*************] 2189.3 2189.3 Fatal error: org.graalvm.compiler.debug.GraalError: org.graalvm.compiler.core.common.PermanentBailoutException: Compilation exceeded 300.000000 seconds during CFG traversal 2189.3 at method: Future

Slide 101

Slide 101 text

Another dragon! 2189.3 Fatal error: org.graalvm.compiler.debug.GraalError: org.graalvm.compiler.core.common.PermanentBailoutException: Compilation exceeded 300.000000 seconds during CFG traversal 2189.3 [6/8] Compiling methods... [*************] 2189.3 2189.3 at method: Future

Slide 102

Slide 102 text

The build was taking too long and never finished Solution: Increase timeout time Build the image on a powerful PC or in the CI Plug a laptop into a power socket

Slide 103

Slide 103 text

The build was taking too long and never finished Solution: Increase timeout time Build the image on a powerful PC or in the CI Plug a laptop into a power socket

Slide 104

Slide 104 text

The build was taking too long and never finished Solution: Increase timeout time Build the image on a powerful PC or in the CI Plug a laptop into a power socket Let's give it another try!

Slide 105

Slide 105 text

Another dragon! 520.7 [8/8] Creating image... [*****] 520.7 --------------------------------------------------------------------------------------------- 520.7 53.2s (13.5% of total time) in 2385 GCs | Peak RSS: 6.77GB | CPU load: 520.7 --------------------------------------------------------------------------------------------- 520.7 Produced artifacts: 520.7 /app/bot-assistant/target/native/svm_err_b_20250415T153317.420_pid287.md (build_info) 520.7 ============================================================================================= 520.7 Failed generating 'bot' after 6m 32s. 520.7 520.7 The build process encountered an unexpected error: 520.7

Slide 106

Slide 106 text

Another dragon! 520.7 The build process encountered an unexpected error: 520.7 > java.lang.RuntimeException: There was an error linking the native image: Linker command exited with 1 520.7 ============================================================================================ 520.7 Failed generating 'bot' after 6m 32s. 520.7 520.7 520.7 520.7 Linker command executed: 520.7 /usr/bin/gcc -z noexecstack -Wl,--gc-sections -Wl,--version-script,/tmp/SVM-1092355479500053 520.7

Slide 107

Slide 107 text

Linking issue: musl libc lacks required tools Option 1: add required packages (libstc++, etc.) with apk add Option 2: switch from musl-based Alpaquita to glibc-based FROM bellsoft/liberica-native-image-kit-container:jdk-21-nik-23.1.6-stream-musl as builder WORKDIR /app ADD ${project} /app/${project} ADD ../pom.xml ./ RUN cd ${project} && ./mvnw -Pnative native:compile FROM bellsoft/alpaquita-linux-base:stream-musl ARG project ENV project=${project} RUN apk add curl WORKDIR /app

Slide 108

Slide 108 text

Linking issue: musl libc lacks required tools Option 1: add required packages (libstc++, etc.) with apk add Option 2: switch from musl-based Alpaquita to glibc-based FROM bellsoft/liberica-native-image-kit-container:jdk-21-nik-23.1.6-stream-glibc as builder WORKDIR /app ADD ${project} /app/${project} ADD ../pom.xml ./ RUN cd ${project} && ./mvnw -Pnative native:compile FROM bellsoft/alpaquita-linux-base:stream-glibc ARG project ENV project=${project} RUN apk add curl WORKDIR /app

Slide 109

Slide 109 text

We did it!

Slide 110

Slide 110 text

We did it! Both services compile and run successfully

Slide 111

Slide 111 text

No content

Slide 112

Slide 112 text

No content

Slide 113

Slide 113 text

We need drastic changes, I won’t settle for half-measures! The Rebel

Slide 114

Slide 114 text

Coordinated Restore at Checkpoint (CRaC) The CRaC (Coordinated Restore at Checkpoint) Project researches coordination of Java programs with mechanisms to checkpoint (make an image of, snapshot) a Java instance while it is executing. Restoring from the image could be a solution to some of the problems with the start-up and warm-up times. The primary aim of the Project is to develop a new standard mechanism-agnostic API to notify Java programs about the checkpoint and restore events. Other research activities will include, but will not be limited to, integration with existing checkpoint/restore mechanisms and development of new ones, changes to JVM and JDK to make images smaller and ensure they are correct. https://openjdk.org/projects/crac/

Slide 115

Slide 115 text

TL;DR Pause and restart a Java application A snapshot of the current JVM state JVM is still there: dynamic performance optimization is possible after restore

Slide 116

Slide 116 text

CRaC is not a canonical part of JDK Only some vendors provide it: BellSoft, Azul Azul was the first to implement, BellSoft supports a fork

Slide 117

Slide 117 text

Project CRaC: Any Considerations? A snapshot may contain sensitive data May need to augment the code for reliable checkpoint and restore

Slide 118

Slide 118 text

Trivial example public static void main(String args[]) throws InterruptedException { // This is a part of the saved state long startTime = System.currentTimeMillis(); for(int counter: IntStream.range(1, 10000).toArray()) { Thread.sleep(1000); long currentTime = System.currentTimeMillis(); System.out.println("Counter: " + counter + "(passed " + (currentTime-startTime) + " ms)"); startTime = currentTime; } }

Slide 119

Slide 119 text

Trivial example public static void main(String args[]) throws InterruptedException { // This is a part of the saved state long startTime = System.currentTimeMillis(); for(int counter: IntStream.range(1, 10000).toArray()) { Thread.sleep(1000); long currentTime = System.currentTimeMillis(); System.out.println("Counter: " + counter + "(passed " + (currentTime-startTime) + " ms)"); startTime = currentTime; } }

Slide 120

Slide 120 text

Trivial example // This is a part of the saved state public static void main(String args[]) throws InterruptedException { long startTime = System.currentTimeMillis(); for(int counter: IntStream.range(1, 10000).toArray()) { Thread.sleep(1000); long currentTime = System.currentTimeMillis(); System.out.println("Counter: " + counter + "(passed " + (currentTime-startTime) + " ms)"); startTime = currentTime; } }

Slide 121

Slide 121 text

Trivial example long startTime = System.currentTimeMillis(); public static void main(String args[]) throws InterruptedException { // This is a part of the saved state for(int counter: IntStream.range(1, 10000).toArray()) { Thread.sleep(1000); long currentTime = System.currentTimeMillis(); System.out.println("Counter: " + counter + "(passed " + (currentTime-startTime) + " ms)"); startTime = currentTime; } }

Slide 122

Slide 122 text

Trivial example for(int counter: IntStream.range(1, 10000).toArray()) { public static void main(String args[]) throws InterruptedException { // This is a part of the saved state long startTime = System.currentTimeMillis(); Thread.sleep(1000); long currentTime = System.currentTimeMillis(); System.out.println("Counter: " + counter + "(passed " + (currentTime-startTime) + " ms)"); startTime = currentTime; } }

Slide 123

Slide 123 text

Trivial example Thread.sleep(1000); long currentTime = System.currentTimeMillis(); System.out.println("Counter: " + counter + "(passed " + (currentTime-startTime) + " ms)"); public static void main(String args[]) throws InterruptedException { // This is a part of the saved state long startTime = System.currentTimeMillis(); for(int counter: IntStream.range(1, 10000).toArray()) { startTime = currentTime; } }

Slide 124

Slide 124 text

Trivial example startTime = currentTime; public static void main(String args[]) throws InterruptedException { // This is a part of the saved state long startTime = System.currentTimeMillis(); for(int counter: IntStream.range(1, 10000).toArray()) { Thread.sleep(1000); long currentTime = System.currentTimeMillis(); System.out.println("Counter: " + counter + "(passed " + (currentTime-startTime) + " ms)"); } }

Slide 125

Slide 125 text

Docker… FROM bellsoft/liberica-runtime-container:jdk-crac-slim ADD Example.java /app/Example.java WORKDIR /app RUN javac Example.java ENTRYPOINT java -XX:CRaCCheckpointTo=/app/checkpoint Example

Slide 126

Slide 126 text

Docker… ...is not that simple FROM bellsoft/liberica-runtime-container:jdk-crac-slim ADD Example.java /app/Example.java WORKDIR /app RUN javac Example.java ENTRYPOINT java -XX:CRaCCheckpointTo=/app/checkpoint Example

Slide 127

Slide 127 text

Docker… ...is not that simple FROM bellsoft/liberica-runtime-container:jdk-crac-slim ADD Example.java /app/Example.java WORKDIR /app RUN javac Example.java ENTRYPOINT java -XX:CRaCCheckpointTo=/app/checkpoint Example

Slide 128

Slide 128 text

Docker… ...is not that simple ADD Example.java /app/Example.java FROM bellsoft/liberica-runtime-container:jdk-crac-slim WORKDIR /app RUN javac Example.java ENTRYPOINT java -XX:CRaCCheckpointTo=/app/checkpoint Example

Slide 129

Slide 129 text

Docker… ...is not that simple RUN javac Example.java FROM bellsoft/liberica-runtime-container:jdk-crac-slim ADD Example.java /app/Example.java WORKDIR /app ENTRYPOINT java -XX:CRaCCheckpointTo=/app/checkpoint Example

Slide 130

Slide 130 text

Docker… ...is not that simple ENTRYPOINT java -XX:CRaCCheckpointTo=/app/checkpoint Example FROM bellsoft/liberica-runtime-container:jdk-crac-slim ADD Example.java /app/Example.java WORKDIR /app RUN javac Example.java

Slide 131

Slide 131 text

Docker… ...is not that simple This does not create the checkpoint yet! ENTRYPOINT java -XX:CRaCCheckpointTo=/app/checkpoint Example FROM bellsoft/liberica-runtime-container:jdk-crac-slim ADD Example.java /app/Example.java WORKDIR /app RUN javac Example.java

Slide 132

Slide 132 text

And then

Slide 133

Slide 133 text

And then Build it docker build -t pre_crack -f crac2/Dockerfile crac2

Slide 134

Slide 134 text

And then Build it Run it docker build -t pre_crack -f crac2/Dockerfile crac2 docker run -d pre_crack

Slide 135

Slide 135 text

And then Build it Run it docker build -t pre_crack -f crac2/Dockerfile crac2 docker run -d pre_crack # will fail

Slide 136

Slide 136 text

And then Build it Run it docker build -t pre_crack -f crac2/Dockerfile crac2 docker run --privileged -d pre_crack

Slide 137

Slide 137 text

And then Build it Run it docker build -t pre_crack -f crac2/Dockerfile crac2 docker run --privileged -d pre_crack # will work, but security folks will hate us

Slide 138

Slide 138 text

And then Build it Run it docker build -t pre_crack -f crac2/Dockerfile crac2 docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -d pre_crack

Slide 139

Slide 139 text

And then Build it Run it docker build -t pre_crack -f crac2/Dockerfile crac2 docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -d pre_crack # Not so frightening if you think about it

Slide 140

Slide 140 text

And then Build it Run it CAP_SYS_PTRACE : we need to access the whole process tree transfer data to or from the memory of arbitrary processes using process_vm_readv(2) and process_vm_writev(2) docker build -t pre_crack -f crac2/Dockerfile crac2 docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -d pre_crack # Not so frightening if you think about it

Slide 141

Slide 141 text

And then Build it Run it CAP_SYS_PTRACE : we need to access the whole process tree transfer data to or from the memory of arbitrary processes using process_vm_readv(2) and process_vm_writev(2) CAP_CHECKPOINT_RESTORE : somehow there is a special cap for this Update /proc/sys/kernel/ns_last_pid ; Read the contents of the symbolic links in /proc/pid/map_files for other processes docker build -t pre_crack -f crac2/Dockerfile crac2 docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -d pre_crack # Not so frightening if you think about it

Slide 142

Slide 142 text

And then Checkpoint it ID=$(docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -p8080:8080 -d pre_crac # wait some time docker exec -it $ID jcmd 129 JDK.checkpoint docker commit $ID cracked docker run --rm -d \ --entrypoint java \ --network host cracked:latest \ -XX:CRaCRestoreFrom=/app/checkpoint

Slide 143

Slide 143 text

And then Checkpoint it ID=$(docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -p8080:8080 -d pre_crac # wait some time docker exec -it $ID jcmd 129 JDK.checkpoint docker commit $ID cracked docker run --rm -d \ --entrypoint java \ --network host cracked:latest \ -XX:CRaCRestoreFrom=/app/checkpoint

Slide 144

Slide 144 text

And then Checkpoint it # wait some time ID=$(docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -p8080:8080 -d pre_crac docker exec -it $ID jcmd 129 JDK.checkpoint docker commit $ID cracked docker run --rm -d \ --entrypoint java \ --network host cracked:latest \ -XX:CRaCRestoreFrom=/app/checkpoint

Slide 145

Slide 145 text

And then Checkpoint it docker exec -it $ID jcmd 129 JDK.checkpoint ID=$(docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -p8080:8080 -d pre_crac # wait some time docker commit $ID cracked docker run --rm -d \ --entrypoint java \ --network host cracked:latest \ -XX:CRaCRestoreFrom=/app/checkpoint

Slide 146

Slide 146 text

And then Checkpoint it docker commit $ID cracked ID=$(docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -p8080:8080 -d pre_crac # wait some time docker exec -it $ID jcmd 129 JDK.checkpoint docker run --rm -d \ --entrypoint java \ --network host cracked:latest \ -XX:CRaCRestoreFrom=/app/checkpoint

Slide 147

Slide 147 text

And then Now we’re ready! Checkpoint it docker commit $ID cracked ID=$(docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -p8080:8080 -d pre_crac # wait some time docker exec -it $ID jcmd 129 JDK.checkpoint docker run --rm -d \ --entrypoint java \ --network host cracked:latest \ -XX:CRaCRestoreFrom=/app/checkpoint

Slide 148

Slide 148 text

And then Now we’re ready! Checkpoint it docker commit $ID cracked ID=$(docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -p8080:8080 -d pre_crac # wait some time docker exec -it $ID jcmd 129 JDK.checkpoint docker run --rm -d \ --entrypoint java \ --network host cracked:latest \ -XX:CRaCRestoreFrom=/app/checkpoint

Slide 149

Slide 149 text

And then Now we’re ready! Checkpoint it docker commit $ID cracked ID=$(docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -p8080:8080 -d pre_crac # wait some time docker exec -it $ID jcmd 129 JDK.checkpoint --entrypoint java \ docker run --rm -d \ --network host cracked:latest \ -XX:CRaCRestoreFrom=/app/checkpoint

Slide 150

Slide 150 text

And then Now we’re ready! Checkpoint it docker commit $ID cracked ID=$(docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -p8080:8080 -d pre_crac # wait some time docker exec -it $ID jcmd 129 JDK.checkpoint --network host cracked:latest \ docker run --rm -d \ --entrypoint java \ -XX:CRaCRestoreFrom=/app/checkpoint

Slide 151

Slide 151 text

And then Now we’re ready! Checkpoint it docker commit $ID cracked ID=$(docker run --cap-add CAP_SYS_PTRACE --cap-add CAP_CHECKPOINT_RESTORE -p8080:8080 -d pre_crac # wait some time docker exec -it $ID jcmd 129 JDK.checkpoint -XX:CRaCRestoreFrom=/app/checkpoint docker run --rm -d \ --entrypoint java \ --network host cracked:latest \

Slide 152

Slide 152 text

And it just works!

Slide 153

Slide 153 text

And it just works! Until it doesn't

Slide 154

Slide 154 text

In our startup

Slide 155

Slide 155 text

In our startup bot-assistant module just works chat-api doesn’t work!

Slide 156

Slide 156 text

In our startup bot-assistant module just works chat-api doesn’t work! Only some projects are guaranteed to work.

Slide 157

Slide 157 text

In our startup bot-assistant module just works chat-api doesn’t work! Only some projects are guaranteed to work. Others… Request support from the team: is not supported for now :(

Slide 158

Slide 158 text

What are the options?

Slide 159

Slide 159 text

What are the options? Fall back to another solution

Slide 160

Slide 160 text

What are the options? Fall back to another solution Request support

Slide 161

Slide 161 text

What are the options? Fall back to another solution Request support Implement support on our own!

Slide 162

Slide 162 text

Implementing support on our own. Custom MongoClient public class MongoClientProxy implements MongoClient { volatile MongoClient delegate; public MongoClientProxy(MongoClient initialClient) { this.delegate = initialClient; } public void close() { delegate.close(); } // Delegate everything else the same way

Slide 163

Slide 163 text

Implementing support on our own. Custom MongoClient volatile MongoClient delegate; public MongoClientProxy(MongoClient initialClient) { this.delegate = initialClient; } public class MongoClientProxy implements MongoClient { public void close() { delegate.close(); } // Delegate everything else the same way

Slide 164

Slide 164 text

Implementing support on our own. Custom MongoClient public void close() { delegate.close(); } public class MongoClientProxy implements MongoClient { volatile MongoClient delegate; public MongoClientProxy(MongoClient initialClient) { this.delegate = initialClient; } // Delegate everything else the same way

Slide 165

Slide 165 text

Implementing support on our own. Custom MongoClient // Delegate everything else the same way public class MongoClientProxy implements MongoClient { volatile MongoClient delegate; public MongoClientProxy(MongoClient initialClient) { this.delegate = initialClient; } public void close() { delegate.close(); }

Slide 166

Slide 166 text

Implementing support on our own Custom CRaC Resource @Component static public class MongoClientResource implements Resource { private final MongoClientProxy mongoClientProxy; private final MongoConnectionDetails details; public MongoClientResource(MongoClient mongoClientProxy, MongoConnectionDetails details) { this.mongoClientProxy = (MongoClientProxy) mongoClientProxy; this.details = details; Core.getGlobalContext().register(this); } @Override public void beforeCheckpoint(Context context) {

Slide 167

Slide 167 text

Implementing support on our own Custom CRaC Resource public MongoClientResource(MongoClient mongoClientProxy, MongoConnectionDetails details) { this.mongoClientProxy = (MongoClientProxy) mongoClientProxy; this.details = details; Core.getGlobalContext().register(this); } static public class MongoClientResource implements Resource { private final MongoClientProxy mongoClientProxy; private final MongoConnectionDetails details; @Override public void beforeCheckpoint(Context context) { mongoClientProxy.delegate.close(); }

Slide 168

Slide 168 text

Implementing support on our own Custom CRaC Resource private final MongoClientProxy mongoClientProxy; this.mongoClientProxy = (MongoClientProxy) mongoClientProxy; @Component static public class MongoClientResource implements Resource { private final MongoConnectionDetails details; public MongoClientResource(MongoClient mongoClientProxy, MongoConnectionDetails details) { this.details = details; Core.getGlobalContext().register(this); } @Override public void beforeCheckpoint(Context context) { mongoClientProxy.delegate.close();

Slide 169

Slide 169 text

Implementing support on our own Custom CRaC Resource private final MongoConnectionDetails details; this.details = details; static public class MongoClientResource implements Resource { private final MongoClientProxy mongoClientProxy; public MongoClientResource(MongoClient mongoClientProxy, MongoConnectionDetails details) { this.mongoClientProxy = (MongoClientProxy) mongoClientProxy; Core.getGlobalContext().register(this); } @Override public void beforeCheckpoint(Context context) { mongoClientProxy.delegate.close(); }

Slide 170

Slide 170 text

Implementing support on our own Custom CRaC Resource Core.getGlobalContext().register(this); private final MongoClientProxy mongoClientProxy; private final MongoConnectionDetails details; public MongoClientResource(MongoClient mongoClientProxy, MongoConnectionDetails details) { this.mongoClientProxy = (MongoClientProxy) mongoClientProxy; this.details = details; } @Override public void beforeCheckpoint(Context context) { mongoClientProxy.delegate.close(); }

Slide 171

Slide 171 text

Implementing support on our own Custom CRaC Resource @Override public void beforeCheckpoint(Context context) { mongoClientProxy.delegate.close(); } this.mongoClientProxy = (MongoClientProxy) mongoClientProxy; this.details = details; Core.getGlobalContext().register(this); } @Override public void afterRestore(Context context) { mongoClientProxy.delegate = MongoClients.create(details.getConnectionString()); } }

Slide 172

Slide 172 text

Implementing support on our own Custom CRaC Resource @Override public void afterRestore(Context context) { mongoClientProxy.delegate = MongoClients.create(details.getConnectionString()); } this.details = details; Core.getGlobalContext().register(this); } @Override public void beforeCheckpoint(Context context) { mongoClientProxy.delegate.close(); } }

Slide 173

Slide 173 text

Implementing support on our own Custom CRaC Resource mongoClientProxy.delegate = MongoClients.create(details.getConnectionString()); this.details = details; Core.getGlobalContext().register(this); } @Override public void beforeCheckpoint(Context context) { mongoClientProxy.delegate.close(); } @Override public void afterRestore(Context context) { } }

Slide 174

Slide 174 text

Implementing support on our own Replacing MongoClient in the context @Bean public MongoClient mongoClient(MongoConnectionDetails details) { @Primary MongoClient initialClient = MongoClients.create(details.getConnectionString()); return new MongoClientProxy(initialClient); }

Slide 175

Slide 175 text

Implementing support on our own Replacing MongoClient in the context MongoClient initialClient = MongoClients.create(details.getConnectionString()); @Bean @Primary public MongoClient mongoClient(MongoConnectionDetails details) { return new MongoClientProxy(initialClient); }

Slide 176

Slide 176 text

Implementing support on our own Replacing MongoClient in the context @Primary return new MongoClientProxy(initialClient); @Bean public MongoClient mongoClient(MongoConnectionDetails details) { MongoClient initialClient = MongoClients.create(details.getConnectionString()); }

Slide 177

Slide 177 text

No content

Slide 178

Slide 178 text

And they all lived happily ever after!

Slide 179

Slide 179 text

Quick Recap šŸ›” → AppCDS for the smoothest integration šŸ§™ā€ā™‚ļø → Project Leyden EA builds to prepare to use it in the future šŸŽ’ → Native Image for fast start āœŠšŸ¼ → CRaC for almost instant start Testing locally? Use a power cord! Which hero are you?

Slide 180

Slide 180 text

This story came to an end, yours has just begun Go for it!

Slide 181

Slide 181 text

Thank you for your attention! @asm0dey.site @cat-edelveis.bsky.social asm0di0 cat_edelveis Find us at