Packing the docker image according to the spring-boot 3.3.0 documentation I find that spring-boot-loader is empty in the extracted directories
➜ distributor-starter-1.0.0-SNAPSHOT git:(main) tree
.
├── application
│ ├── distributor-starter-1.0.0-SNAPSHOT.jar
│ └── lib
│ ├── distributor-api-1.0.0-SNAPSHOT.jar
│ ├── distributor-api-impl-1.0.0-SNAPSHOT.jar
│ ├── distributor-common-1.0.0-SNAPSHOT.jar
│ ├── distributor-dal-1.0.0-SNAPSHOT.jar
│ ├── distributor-facade-1.0.0-SNAPSHOT.jar
│ ├── distributor-service-1.0.0-SNAPSHOT.jar
│ └── distributor-web-1.0.0-SNAPSHOT.jar
├── dependencies
│ └── lib
│ ├── HdrHistogram-2.2.1.jar
│ ├── HikariCP-5.1.0.jar
│ ├── LatencyUtils-2.0.3.jar
│ ├── android-json-0.0.20131108.vaadin1.jar
│ ├── aopalliance-1.0.jar
│ ├── apollo-client-2.2.0.jar
│ ├── apollo-core-2.2.0.jar
│ ├── aspectjweaver-1.9.22.jar
│ ├── bcprov-jdk18on-1.78.1.jar
│ ├── checker-qual-3.42.0.jar
│ ├── classmate-1.7.0.jar
│ ├── commons-codec-1.16.1.jar
│ ├── commons-collections4-4.4.jar
│ ├── commons-compress-1.26.2.jar
│ ├── commons-io-2.16.1.jar
│ ├── commons-lang3-3.14.0.jar
│ ├── commons-pool2-2.12.0.jar
│ ├── error_prone_annotations-2.26.1.jar
│ ├── failureaccess-1.0.2.jar
│ ├── gson-2.10.1.jar
│ ├── guava-33.2.1-jre.jar
│ ├── guice-5.0.1.jar
│ ├── hibernate-validator-8.0.1.Final.jar
│ ├── hutool-all-5.8.28.jar
│ ├── j2objc-annotations-3.0.0.jar
│ ├── jackson-annotations-2.17.1.jar
│ ├── jackson-core-2.17.1.jar
│ ├── jackson-databind-2.17.1.jar
│ ├── jackson-datatype-jdk8-2.17.1.jar
│ ├── jackson-datatype-jsr310-2.17.1.jar
│ ├── jackson-module-blackbird-2.17.1.jar
│ ├── jackson-module-parameter-names-2.17.1.jar
│ ├── jakarta.annotation-api-2.1.1.jar
│ ├── jakarta.validation-api-3.0.2.jar
│ ├── javax.inject-1.jar
│ ├── jboss-logging-3.5.3.Final.jar
│ ├── jsqlparser-4.9.jar
│ ├── jsr305-3.0.2.jar
│ ├── jul-to-slf4j-2.0.13.jar
│ ├── kafka-clients-3.7.0.jar
│ ├── lettuce-core-6.3.2.RELEASE.jar
│ ├── listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
│ ├── log4j-api-2.23.1.jar
│ ├── log4j-to-slf4j-2.23.1.jar
│ ├── logback-classic-1.5.6.jar
│ ├── logback-core-1.5.6.jar
│ ├── logstash-logback-encoder-7.4.jar
│ ├── lombok-1.18.32.jar
│ ├── lz4-java-1.8.0.jar
│ ├── micrometer-commons-1.13.0.jar
│ ├── micrometer-core-1.13.0.jar
│ ├── micrometer-jakarta9-1.13.0.jar
│ ├── micrometer-observation-1.13.0.jar
│ ├── micrometer-registry-prometheus-1.13.0.jar
│ ├── mybatis-3.5.16.jar
│ ├── mybatis-plus-3.5.6.jar
│ ├── mybatis-plus-annotation-3.5.6.jar
│ ├── mybatis-plus-core-3.5.6.jar
│ ├── mybatis-plus-extension-3.5.6.jar
│ ├── mybatis-plus-spring-boot-autoconfigure-3.5.6.jar
│ ├── mybatis-plus-spring-boot3-starter-3.5.6.jar
│ ├── mybatis-spring-3.0.3.jar
│ ├── mysql-connector-j-8.3.0.jar
│ ├── netty-buffer-4.1.110.Final.jar
│ ├── netty-codec-4.1.110.Final.jar
│ ├── netty-common-4.1.110.Final.jar
│ ├── netty-handler-4.1.110.Final.jar
│ ├── netty-resolver-4.1.110.Final.jar
│ ├── netty-transport-4.1.110.Final.jar
│ ├── netty-transport-native-unix-common-4.1.110.Final.jar
│ ├── prometheus-metrics-config-1.2.1.jar
│ ├── prometheus-metrics-core-1.2.1.jar
│ ├── prometheus-metrics-exposition-formats-1.2.1.jar
│ ├── prometheus-metrics-model-1.2.1.jar
│ ├── prometheus-metrics-shaded-protobuf-1.2.1.jar
│ ├── prometheus-metrics-tracer-common-1.2.1.jar
│ ├── reactive-streams-1.0.4.jar
│ ├── reactor-core-3.6.6.jar
│ ├── slf4j-api-2.0.13.jar
│ ├── snakeyaml-2.2.jar
│ ├── snappy-java-1.1.10.5.jar
│ ├── spring-aop-6.1.8.jar
│ ├── spring-beans-6.1.8.jar
│ ├── spring-boot-3.3.0.jar
│ ├── spring-boot-actuator-3.3.0.jar
│ ├── spring-boot-actuator-autoconfigure-3.3.0.jar
│ ├── spring-boot-autoconfigure-3.3.0.jar
│ ├── spring-boot-configuration-metadata-3.3.0.jar
│ ├── spring-boot-jarmode-tools-3.3.0.jar
│ ├── spring-boot-properties-migrator-3.3.0.jar
│ ├── spring-context-6.1.8.jar
│ ├── spring-context-support-6.1.8.jar
│ ├── spring-core-6.1.8.jar
│ ├── spring-data-commons-3.3.0.jar
│ ├── spring-data-keyvalue-3.3.0.jar
│ ├── spring-data-redis-3.3.0.jar
│ ├── spring-expression-6.1.8.jar
│ ├── spring-jcl-6.1.8.jar
│ ├── spring-jdbc-6.1.8.jar
│ ├── spring-kafka-3.2.0.jar
│ ├── spring-messaging-6.1.8.jar
│ ├── spring-oxm-6.1.8.jar
│ ├── spring-retry-2.0.6.jar
│ ├── spring-tx-6.1.8.jar
│ ├── spring-web-6.1.8.jar
│ ├── spring-webmvc-6.1.8.jar
│ ├── tomcat-embed-core-10.1.24.jar
│ ├── tomcat-embed-el-10.1.24.jar
│ ├── tomcat-embed-websocket-10.1.24.jar
│ └── zstd-jni-1.5.5-6.jar
├── snapshot-dependencies
│ └── lib
│ ├── common-1.0.0-SNAPSHOT.jar
│ ├── common-util-1.0.0-SNAPSHOT.jar
│ ├── common-web-1.0.0-SNAPSHOT.jar
│ ├── spring-boot-starter-apollo-1.0.0-SNAPSHOT.jar
│ ├── spring-boot-starter-cache-1.0.0-SNAPSHOT.jar
│ ├── spring-boot-starter-kafka-1.0.0-SNAPSHOT.jar
│ ├── spring-boot-starter-logger-1.0.0-SNAPSHOT.jar
│ ├── spring-boot-starter-mybatis-1.0.0-SNAPSHOT.jar
│ ├── spring-boot-starter-starrocks-1.0.0-SNAPSHOT.jar
│ └── spring-support-1.0.0-SNAPSHOT.jar
└── spring-boot-loader
8 directories, 128 files
This causes an error on startup
➜ distributor-starter-1.0.0-SNAPSHOT git:(main) java -jar application/distributor-starter-1.0.0-SNAPSHOT.jar
Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication
at com.xxx.canal.distributor.Application.main(Application.java:12)
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
... 1 more
I tried the latest Spring-Boo-3.3.1-SNAPSHOT and the error is still there
Comment From: refeccd
Also I seem to have found a problem in the documentation. If you do a docker multi-stage build as per the docs, shouldn't there be no application.jar in the final ENTRYPOINT?
Comment From: wilkinsona
I can't reproduce the behavior that you have described. The empty spring-boot-loader directory is to be expected as the loader is not used with the CDS-friendly layout in Spring Boot 3.3. The failure looks to me like the application's dependencies are not in the correct place relative to the application.jar file that refers to them from its Class-Path manifest entry.
If you do a docker multi-stage build as per the docs, shouldn't there be no application.jar in the final ENTRYPOINT?
No, there should be. It contains the application's own classes and, through its Class-Path manifest entry, adds all of the application's dependencies to the classpath.
If you would like us to spend some more time investigating, please spend some time providing a complete yet minimal sample that reproduces the problem. You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.
Comment From: andre161292
We were just running into this error too.
After some digging, https://github.com/spring-projects/spring-boot/commit/c22548982abf0b9c5c02406fe789b0423fcfa02d seems to be the breaking change here, as the JarLauncher was relocated from org.springframework.boot.loader.JarLauncher to org.springframework.boot.loader.launch.JarLauncher.
The fix for us was to change the docker entrypoint from
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
to
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
Comment From: wilkinsona
Thanks, @andre161292, but that's not the same problem. The change to the name of the JarLauncher was made in Spring Boot 3.2. The change described in this issue is specific to Spring Boot 3.3 and its CDS support. You can also see above that the application is being launched using java -jar where the launcher's class name is not specified.
Comment From: refeccd
I'm sorry it was due to my own negligence. I wrongly assumed that the structure after copying was the same as the structure after extracting. So I ignored the part of the dockerfile that was copied from builder, and directly executed application.jar with java -jar, which reported a ClassNotFoundException. I wrote the entire dockerfile in its entirety and it runs fine
Comment From: wilkinsona
Thanks for letting us know.
Comment From: azalesky
The empty spring-boot-loader directory is to be expected
Why spring-boot-loader directory is created and mentioned in documentation if it is expected to be empty?
Comment From: wilkinsona
Because it's one of the default layers. Whether or not it has any content depends on the specific type of extraction that you use. This is similar to the snapshot-dependencies directory which may also be empty if the application does not have any snapshot dependencies.
Comment From: azalesky
Thank you for fast response.
Extraction with option --launcher created non empty spring-boot-loader directory.