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

Document using the Shadow plugin as an alternative to Boot's fat jars when using Gradle #1828

Closed
btiernay opened this issue Nov 5, 2014 · 14 comments
Labels
type: documentation A documentation update
Milestone

Comments

@btiernay
Copy link

btiernay commented Nov 5, 2014

I found the following is required in Gradle to get a fat jar working when cannot use the spring-boot-gradle-plugin plugin:

import com.github.jengelman.gradle.plugins.shadow.transformers.*

// Can't use Boot's nested jar packaging due to environment constrained classloader
apply plugin: 'com.github.johnrengelman.shadow'

shadowJar {
  zip64 true

  exclude 'META-INF/*.SF'
  exclude 'META-INF/*.DSA'
  exclude 'META-INF/*.RSA'

  // Required for Spring
  mergeServiceFiles()
  transform(AppendingTransformer) { resource = 'reference.conf'  }
  transform(AppendingTransformer) { resource = 'META-INF/spring.handlers'  }
  transform(AppendingTransformer) { resource = 'META-INF/spring.schemas'  }
  transform(AppendingTransformer) { resource = 'META-INF/spring.tooling'  }
  transform(PropertiesFileTransformer) { paths = ['META-INF/spring.factories' ] }

  // ...
}

The docs should strongly recommend the shadow / shade plugins for environmentally constrained classloaders¹ because resource transformation is a requirement. Without it, essential beans such as converters, property placeholder configurers, etc. go missing from Spring Boot's autoconfiguration (i.e. spring.factories). The result is very difficult to diagnose.

¹environmentally constrained classloaders: A system classloader not under your control.

@btiernay btiernay changed the title Update documention for Gradle / Maven builds not using Boot plugins Update documention for Gradle / Maven builds not using the Boot plugins Nov 5, 2014
@snicoll
Copy link
Member

snicoll commented Nov 5, 2014

what do you mean not using the boot plugin? Are you creating a fat jar yourself?

@btiernay
Copy link
Author

btiernay commented Nov 5, 2014

Yes, this is a requirement in certain environments such as Hadoop, Spark, Storm, etc. since they don't know how to load nested jars. Please see #1668 for more details on this topic.

@btiernay
Copy link
Author

btiernay commented Nov 5, 2014

@philwebb philwebb added the type: documentation A documentation update label Nov 5, 2014
@btiernay
Copy link
Author

Just hit an issue in a new Spring Boot version. I needed to amend the above to have:

transform(PropertiesFileTransformer) { paths = ['META-INF/spring.factories' ] }

This seemed to have resolved my issue.

@unilama
Copy link

unilama commented Jul 7, 2016

Final working example should be like that, strategy is required if you are using several components that contains spring.factories:

import com.github.jengelman.gradle.plugins.shadow.transformers.*
shadowJar {

    // Required for Spring
    mergeServiceFiles()
    append 'META-INF/spring.handlers'
    append 'META-INF/spring.schemas'
    append 'META-INF/spring.tooling'
    transform(PropertiesFileTransformer) {
        paths = ['META-INF/spring.factories' ]
        mergeStrategy = "append"
    }

}

I hope it will save someone's lot of time :)

@philwebb
Copy link
Member

We're cleaning out the issue tracker and closing issues that we've not seen much demand to fix. Feel free to comment with additional justifications if you feel that this one should not have been closed.

@philwebb philwebb added the status: declined A suggestion or change that we don't feel we should currently apply label Mar 22, 2018
@wilkinsona
Copy link
Member

I think there might be some merit to doing something in this area. Spring Cloud Function (and FaaS in general) would benefit from an easy way to produce a shadowed jar. We really need something that's equivalent to the Maven Shade Plugin's configuration from the starter parent pom. That could take the form of documentation, or it could possibly take the form of some code in Boot's gradle plugin that sets up the various transformers.

@wilkinsona wilkinsona reopened this Apr 24, 2018
@wilkinsona wilkinsona added for: team-attention An issue we'd like other members of the team to review and removed status: declined A suggestion or change that we don't feel we should currently apply labels Apr 24, 2018
@wilkinsona wilkinsona removed the for: team-attention An issue we'd like other members of the team to review label May 4, 2018
@wilkinsona wilkinsona added this to the 2.0.x milestone May 4, 2018
@wilkinsona wilkinsona changed the title Update documention for Gradle / Maven builds not using the Boot plugins Document using the Shadow plugin as an alternative to Boot's fat jars when using Gradle Oct 9, 2018
@wilkinsona
Copy link
Member

With the spring-boot-docs project, there's no easy way for us to test that the Shadow plugin configuration works as expected and I don't want to add an untested snippet. I will add a link to the Shadow plugin alongside the other alternatives though.

@wilkinsona wilkinsona modified the milestones: 2.0.x, 2.0.7 Nov 27, 2018
@vinothkumarcyantriks

This comment has been minimized.

@raj-saxena
Copy link

@unilama thanks for your comment. I was looking for this as Google's Dataflow also doesn't work with Spring's Fat-jar.
For KotlinDSL, here's what I had to do to make it work -

plugins {
    id("com.github.johnrengelman.shadow") version "5.2.0"
}

...

tasks.withType<ShadowJar> {
    isZip64 = true
    // Required for Spring
    mergeServiceFiles()
    append("META-INF/spring.handlers")
    append("META-INF/spring.schemas")
    append("META-INF/spring.tooling")
    transform(PropertiesFileTransformer().apply {
        paths = listOf("META-INF/spring.factories")
        mergeStrategy = "append"
    })
}

@adrian-baker
Copy link

adrian-baker commented Aug 12, 2021

Another aspect of the Spring Gradle plugin that needs manually recreating is the exclusion of the developmentOnly from the resulting jar. By default shadowJar consumes the runtimeClasspath configuration, and the plugin applies runtimeClasspath.extendsFrom(developmentOnly), which means the examples above include developmentOnly dependencies in the resulting shadowedJar, which is likely not desired.

I've found this the simplest way to recreate that:

shadowJar {
  configurations = [project.configurations.productionRuntimeClasspath]

Hopefully my reading of #26686 is correct and the productionRuntimeClasspath configuration created by the plugin is considered "API":

@ShalabhR
Copy link

ShalabhR commented Nov 8, 2022

Has this issue been addressed in spring-boot 2.7.5 ? I was using the shadowJar task defined above with my app that used spring-boot v2.6.6 but after upgrading to 2.7.5 looks like it stopped working and I had to remove shadowJar. Can anyone confirm if there was an effort to fix in 2.7.5 ?

@wilkinsona
Copy link
Member

@ShalabhR This was a documentation issue, adding to Boot's reference documentation a link to the Shadow plugin. As such, there was nothing to fix. If you have a build that works with 2.6.6 and fails with 2.7.5, you may have found a regression. If you open a new issue and provide a minimal sample that reproduces the problem, we'll take a look.

@Djaytan
Copy link

Djaytan commented Aug 28, 2024

Because I struggled several hours solving my issue, here is what I have done with Spring Boot v3.0+ (but may work for v2.7+ as well... To be tested):

Spring Boot 2.7 introduced a new ‘META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports’ file for registering auto-configurations, while maintaining backwards compatibility with registration in ‘spring.factories’. With Spring Boot 3.0 release, support for registering auto-configurations in ‘spring.factories’ has been removed in favor of the imports file.

=> https://stackoverflow.com/q/74871072/10165346

This means that it is required to merge the org.springframework.boot.autoconfigure.AutoConfiguration.imports files as well. Starting from there, auto-configuration starts to work again!

This leads me to the following Gradle configuration for the shadowJar task (Kotlin DSL):

shadowJar {
  // Required for proper Spring Boot shading
  mergeServiceFiles()
  append("META-INF/spring.handlers")
  append("META-INF/spring.schemas")
  append("META-INF/spring.tooling")
  append("META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports")
  transform(PropertiesFileTransformer().apply {
    paths = listOf("META-INF/spring.factories")
    mergeStrategy = "append"
  })
}

Hope it will help.

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

No branches or pull requests

10 participants