Failing to publish bootjar in multi module

I have spring boot based multi module project, which was defined in sourcesets. We are upgrade from java11 to java 17 and gradle 6.9 to 7.2. It was defined using sourcesets, where we are seggregating into multi module project. We got it working most of it except publishing bootJar from service application.

Structure is

- rootProject
   -----build.gradle
   -----library1
          -----build.gradle
   -----library2
          -----build.gradle
   -----Service
          -----build.gradle.


Only service project bootjar is not publishing, other libraries are publishing successfully.
All publishing module is in rootProject. And I have disable jar in service as it is not geenrating fatjar or bootjar. So when I do that, it is skipping publishing as well.
Any solution that can help me with able to build all modules and publishing all modules. And have to generate bootjar / fatjar and then publish it from service file.
If enable jar task, then it is publishing that, but there is no use of that jar as it is not bootjar.

Rootproject-build.gradle

 import com.github.jk1.license.LicenseReportPlugin

buildscript {
    ext {
        springBootVersion = '2.7.0'
        springCloudVersion = '2021.0.3'
        logStashLogbackEncoderVersion = '7.0'
    }
    repositories {
        mavenLocal()
        gradlePluginPortal()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("io.spring.gradle:dependency-management-plugin:1.0.11.RELEASE")
        classpath("com.github.jk1:gradle-license-report:1.17")
        classpath("org.owasp:dependency-check-gradle:7.3.0")
        classpath "com.gorylenko.gradle-git-properties:gradle-git-properties:2.4.1"
    }
}

plugins {
    id("org.sonarqube") version "4.3.1.3277"
    id("checkstyle")
}

allprojects {

    apply plugin: 'java'
    apply plugin: 'maven-publish'
    apply plugin: 'io.spring.dependency-management'
    apply plugin: 'jacoco'
    apply plugin: LicenseReportPlugin
    apply plugin: 'org.owasp.dependencycheck'
    apply plugin: 'eclipse'

    group = 'com.example'
    version = '1.6.0-SNAPSHOT'
    sourceCompatibility = sourceJavaVersion

    checkstyle {
        toolVersion '10.9.3'
        maxWarnings = 0
        ignoreFailures false
    }

    repositories {
        mavenLocal()
        mavenCentral()
    }

    dependencyManagement {
        imports {
            mavenBom "org.springframework.boot:spring-boot-dependencies:${springBootVersion}"
            mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
        }
    }
}
    
configurations {
    implementation.exclude module: 'spring-boot-starter-tomcat'
    implementation.exclude group: 'org.apache.tomcat'
    compile.exclude module: 'javax.persistence'
    compile.exclude module: 'spring-boot-starter-reactor-netty'
}

test {
    finalizedBy jacocoTestReport
    useJUnitPlatform()
}

subprojects {
    publishing {
        publications {
            mavenJava(org.gradle.api.publish.maven.MavenPublication) {
                groupId = "${group}"
                artifactId = "${artifactId}"
                version = "${version}"
                from components.java
            }
        }
    }
}

And service project build.gradle looks like this: I left the commented out lines, so you can see what are the other thinsg I have tried

plugins {
    id 'java'
    id("org.springframework.boot")
}
apply plugin: 'maven-publish'
bootJar {
    enabled = true
}

group = 'com.example'
version = '1.6.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {

    implementation("net.jodah:expiringmap:0.5.8")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-undertow")
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-aop:${springBootVersion}")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation('org.springframework.boot:spring-boot-starter-json')
    implementation('org.springframework.boot:spring-boot-starter-validation')

    implementation("org.springframework.cloud:spring-cloud-starter-config")
    implementation("org.springframework.cloud:spring-cloud-stream-binder-kafka")
    implementation("org.springframework.kafka:spring-kafka")
    implementation("org.springframework.cloud:spring-cloud-stream")
    implementation("org.springframework.cloud:spring-cloud-starter-sleuth")

    implementation('org.hibernate:hibernate-java8:5.6.15.Final')
    implementation('org.hibernate:hibernate-envers:5.6.15.Final')
    implementation('org.hibernate:hibernate-core:5.6.15.Final')
    implementation("org.hibernate.validator:hibernate-validator")

    implementation("javax.xml.bind:jaxb-api:2.3.0")
    implementation("org.apache.commons:commons-lang3:3.12.0")
    implementation("commons-io:commons-io:2.11.0")
    implementation("io.springfox:springfox-swagger2:2.8.0")
    implementation("org.eclipse.persistence:eclipselink:2.7.1")
    implementation("net.logstash.logback:logstash-logback-encoder:$logStashLogbackEncoderVersion")
    implementation 'org.flywaydb:flyway-core:8.4.4'
    implementation 'org.flywaydb:flyway-mysql:8.4.4'
    implementation("org.modelmapper:modelmapper:2.1.0")
    implementation("org.mariadb.jdbc:mariadb-java-client:2.7.3")
    implementation('org.springdoc:springdoc-openapi-webmvc-core:1.8.0')
    implementation("org.springframework.retry:spring-retry:1.2.5.RELEASE")

    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation('org.keycloak:keycloak-spring-boot-starter:13.0.1')
    implementation('org.keycloak:keycloak-spring-security-adapter')
    implementation('com.sun.xml.ws:jaxws-ri:2.3.7')

    testImplementation('org.junit.jupiter:junit-jupiter:5.4.2')
    testImplementation("org.springframework.cloud:spring-cloud-stream-test-support")
    testImplementation("org.springframework.boot:spring-boot-starter-test")

    testRuntimeOnly('com.h2database:h2:2.2.224')

    testImplementation platform('org.junit:junit-bom:5.10.0')
    testImplementation 'org.junit.jupiter:junit-jupiter'
}



//task createBootJar(type: Jar) {
//    archiveBaseName = 'flexifin-bankaccount-verification-service'
//    archiveClassifier = 'plain'
//    manifest {
//        attributes 'Main-Class': 'com.example.SpringBootApplication'
//    }
//    from {
//        // Configure the task to include necessary files
//    }
//}

//publish.dependsOn(bootJar)

jar {
    enabled = false
}
//bootJar {
//    enabled = true
////    archiveClassifier = 'plain'
//}


test {
    useJUnitPlatform()
}


project.tasks.publish.dependsOn bootJar
project.tasks.publishMavenJavaPublicationToMavenLocal.dependsOn bootJar

Some general notes if you care:

  • Don’t use ext / extra properties, they are usually just a sign for doing something not properly. In this case just use normal local variables, or even better, use a version catalog.
  • Don’t use mavenLocal(), especially not as first repository and especially not without content filter. The main effects of mavenLocal() are that your builds get unnecessary slow and flaky, and it is broken by design in Maven already. See The case for mavenLocal() for more information.
  • Don’t use mavenCentral() after gradlePluginPortal(). gradlePluginPortal() redirects to Maven Central for anything it does not host itself already, so once mavenCentral() is reached, it will never serve anything. Either put it before gradlePluginPortal() or leave it out.
  • Do not use the legacy way of adding plugins to the buildscript classpath manually and then applying them the legacy apply plugin: ... way, but use the plugins { ... } block, with apply false if you just want to add to the classpath, without if you also want to apply it to the current project.
  • Do not use the Spring dependency management plugin, it is a relict from times when Gradle did not have built-in BOM support. By now it does more harm than good and even its maintainer recommends not to use it anymore but instead use the built-in BOM support using platform(...).
  • Do not do highly-discouraged bad-practice cross-project configuration. This includes among other things usage of allprojects { ... }, subprojects { ... }, project(...) { ... } and similar constructs in build scripts. Instead if you want to centralize and reuse build logic, have a look at convention plugins in buildSrc or - what I prefer - an included build, for example implemented as precompiled script plugins.
  • Never apply 3rd party plugins by their classname except if you have a really really good reason, otherwise use the plugin id.
  • Do not use sourceCompatibility / targetCompatibility, they have very serious drawbacks, especially when not setting the bootstrap classpath explicitly. Instead use the JVM toolchains feature to define which toolchain should be used to decouple the Java version used to run Gradle from the Java version used to build and run your code. And if you need or want a newer toolchain than your target compatibility, then additionally to the toolchain use the release option instead of *Compatibility which mitigates the problems the latter have.
  • Do not manipulate the group, artifact, and version of a publication. 1. it is pointless in your case as you set them to their default values. 2. If you would change them, this later bites you if you for example want to use the build in a composite build and then need manual dependency substitution and so on which would simply work out-of-the box if done properly.
  • The compile.exclude lines are pointless. There is no configuration with name compile. It was deprecated since many many years and finally removed in Gradle 7.0. Your first of those two lines actually does create that configuration due to Groovy DSL magic. Also something the Kotlin DSL would have told you as there this does not happen that easily automagically.
  • While we are at it, more a personal recommendation, consider switching to Kotlin DSL. By now it is the default DSL, you immediately get type-safe build scripts, actually helpful error messages if you mess up the syntax, and amazingly better IDE support if you use a good IDE like IntelliJ IDEA or Android Studio.
  • There is not much point in enabling the bootJar task, as it should not be disabled.
  • There is not much point in disabling the jar task, because if its result is used somewhere, the jar will still be used if it is already on disk, and if not, then you just get an error that the jar is missing. If you don’t want the jar to be built or published, then take care to fix the source of the problem, not do some band-aid disable.
  • Making random tasks dependsOn bootJar will never help with anything. It will be build then, but still not published. Actually, practically any explicit dependsOn where on the left-hand side is not a lifecycle task is a code smell, showing that somewhere you did not properly wire outputs to inputs, which would then automatically bring the necessary task dependencies everywhere necessary implicitly.

If you just want to upload the bootJar to the maven repository and do not really care about proper metadata, just so that someone can manually download it, then you can do as the Spring Boot manual instructs and just directly publish the bootJar task as artifact instead of the java component.

If you want to have proper metadata or publish it in addition to the normal jar as extra variant, get the java component, cast it to AdHocComponentWithVariants and add a variant for the boot jar with attributes as needed, as shown in the user manual.

Really appreciate your efforts, learnt a lot.
I need to upload bootJar file as a normal task that we do in the pipeline. If I don’t disable jar file, its generating 2 files one with normal and another with -plain. If I run publish task, it uploads -plain jar file.
And cant define publication task as bootJar as I need metadata.

I know I am asking too much, but if possible can you share any example code to cast java components to AdHocComponentWithVariants . I tried my best to get info, but could not get more info.

Also another question, can I define publication seperately for each module ?

If I don’t disable jar file, its generating 2 files one with normal and another with -plain. If I run publish task, it uploads -plain jar file.

As I said, it only publishes the plain jar because you configured it that way.
And it only builds the plain jar because you publish it.
If you do not configure to publish the jar, it will not be built.

And cant define publication task as bootJar as I need metadata.

The question is which metadata.
If only the POM with g:a:v, that will still be generated, otherwise it would not be a legal Maven publish.
The question is more whether you need more metadata published and so on.
If not, as I said, easiest is to just do like shown in the Spring Boot manual in this case.

Publishing a fat jar to a Maven repository per-se is bad practice actually.
Maven repositories are for hosting libraries that other projects use, and those should practically never be made fat.

I tried my best to get info, but could not get more info.

If your best does not even include copying that term and pasting it to the search on the Gradle docs which brings this as first result: Customizing publishing, you should maybe improve your research skills. :wink:
(Casting per-se is of course not necessary as long as you are in the duck-typed Groovy DSL)

Also another question, can I define publication seperately for each module ?

Of course, just do it.

Hey Bjorn,

thank you soo much, I was able to upload the jar file by following this - Publishing your Application :: Spring Boot.
yeah deinitely need to improve checking search results :smile:

Thank you sooo much.

1 Like