The plugin management for the Maven Shade Plugin does not add the Manifest Entry for Multi Release Jars. Does this make sense to add given this is leveraged in Spring Framework for Virtual Threads support
Comment From: matthew-js-porter
Hey @wilkinsona thanks for looking at this issue! I spent some time validating the changes made to fix this and noticed the Main-Class manifest entry gets removed with these changes.
Steps to reproduce.
- Download Spring Boot project from start.spring.io
- Add pluginManagement for maven-shade-plugin to match the changes in the PR
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.4.0</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports</resource>
</transformer>
<transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>${start-class}</Main-Class>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
- Run
mvn clean package - extract the jar and look at the MANIFEST.MF and observe Main-Class entry is missing.
Replacing:
<manifestEntries>
<Main-Class>${start-class}</Main-Class>
<Multi-Release>true</Multi-Release>
</manifestEntries>
with:
<mainClass>${start-class}</mainClass>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
fixes the issue.
Comment From: wilkinsona
It's not intended that you use both spring-boot-maven-plugin and maven-shade-plugin in the same project. When you do so, the former will create a Spring Boot uber jar with the launcher in the root of the jar, application code in BOOT-INF/classes, and dependencies in BOOT-INF/lib. If the shade plugin is then applied as well you'll end up with the application's dependencies unzipped in the root of the jar as well as being in BOOT-INF/lib.
Maven ignores the <mainClass> element when it's declared as a sibling of manifestEntries and I correct its positioning as part of fixing this issue. It only works for you in 3.4.0 as you were building a half-shaded half-Spring Boot uber jar. The uber jar half of this means that it has set the main class to point to Spring boot launcher and Spring Boot's launcher is then used to launch the app, negating the benefits of shading.
You can verify this by using Spring Boot 3.4.0 and outputting the thread context class loader in your application's main method. You should see that it's org.springframework.boot.loader.launch.LaunchedClassLoader@378bf509.
This is the recommended configuration when using the Shade plugin:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://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>3.4.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>gh-43284</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gh-43284</name>
<description>Demo project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
<start-class>com.example.gh_43284.Gh43284Application</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>
With this configuration you'll see that the TCCL is jdk.internal.loader.ClassLoaders$AppClassLoader indicating that Spring Boot's launcher is no longer involved.
Comment From: matthew-js-porter
@wilkinsona thanks for detailed explanation! that all makes sense. However, when using the maven shade plugin with addition with the spring-boot-thin-layout, as seen here in the AWS Spring Cloud Function Sample this would cause an issue right?
Comment From: wilkinsona
I think that sample's arguably misconfigured as the shaded jar (with the -aws classifier) contains all of the shaded dependencies but it's still using the thin launcher and org.springframework.boot.loader.wrapper.ThinJarWrapper as its entry point. That doesn't look right to me.
That said, I didn't need to correct the configuration of the <Main-Class> manifest entry to address this specific issue so I'm going to revert that part of the change as there does seem to be some risk of regression that I'd rather avoid. Thanks for raising it.
Comment From: matthew-js-porter
@wilkinsona Thank you will the quick resolution!