Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UnsupportedOperationException when starting a Maven shaded application on Java 21 with virtual threads enabled #43284

Closed
matthew-js-porter opened this issue Nov 25, 2024 · 5 comments
Assignees
Labels
type: bug A general bug
Milestone

Comments

@matthew-js-porter
Copy link

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

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Nov 25, 2024
@wilkinsona wilkinsona changed the title Maven Shade Plugin Management does not add Multi-Release Manifest Entry UnsupportedOperationException when starting a Maven shaded application on Java 21 with virtual threads enabled Nov 26, 2024
@wilkinsona wilkinsona added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Nov 26, 2024
@wilkinsona wilkinsona added this to the 3.3.x milestone Nov 26, 2024
@wilkinsona wilkinsona self-assigned this Nov 26, 2024
@wilkinsona wilkinsona modified the milestones: 3.3.x, 3.3.7 Nov 26, 2024
@matthew-js-porter
Copy link
Author

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.

  1. Download Spring Boot project from start.spring.io
  2. 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>
  1. Run mvn clean package
  2. 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.

@wilkinsona
Copy link
Member

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.

@matthew-js-porter
Copy link
Author

@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?

@wilkinsona
Copy link
Member

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.

wilkinsona added a commit that referenced this issue Dec 12, 2024
This partially reverts commit
d924e4d.

See gh-43284
@matthew-js-porter
Copy link
Author

@wilkinsona Thank you will the quick resolution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants