Gradle Not Generating Springboot Library Jar with embedded pom.xml in META-INF

201 Views Asked by At

I have Springboot multi module library project with Gradle 7.4 and Springboot 3.2 as below

buildscript {

    dependencies {
        classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4.21.0"
    }
}
plugins {
    id "com.diffplug.spotless" version "6.8.0"
    id 'io.spring.dependency-management' version '1.1.4'
    id 'org.springframework.boot' version '3.2.0' apply false
    id "org.openapi.generator" version "6.0.0"
    id "org.hidetake.swagger.generator" version "2.19.2"
}

ext.versions = [
        springBoot        : '3.2.0',
        springCloud       : '2022.0.4',
        micrometer        : '1.10.3',
        commonLogging     : '1.2',
]

allprojects {
    apply plugin: 'io.spring.dependency-management'
    group 'com.springboot.web.library'
    version = '0.0.3-SNAPSHOT'

    dependencyManagement {
        imports {
            mavenBom("org.springframework.boot:spring-boot-dependencies:${versions.springBoot}")
            mavenBom("org.springframework.cloud:spring-cloud-dependencies:${versions.springCloud}")
        }
        dependencies {
            //dependency group: 'io.micrometer', name: 'micrometer-registry-prometheus', version: versions.micrometer
            dependency group: 'commons-logging', name: 'commons-logging', version: versions.commonLogging
        }
    }
}

subprojects {
    apply plugin: 'java'
    apply plugin: 'maven-publish'
    apply plugin: "com.diffplug.spotless"

    java {
        toolchain {
            languageVersion = JavaLanguageVersion.of(17)
        }
        withSourcesJar()
    }

    //https://dev.to/ankityadav33/standardize-code-formatting-with-spotless-2bdh
    spotless {
        java {
            //googleJavaFormat("1.15.0")
            target fileTree('.') {
                include '**/*.java'
                exclude '**/build/**', '**/build-*/**'
            }
            importOrder()
            toggleOffOn()
            palantirJavaFormat()
            removeUnusedImports()
            trimTrailingWhitespace()
            endWithNewline()
        }
    }

    afterEvaluate {
        def spotless = tasks.findByName('spotlessApply')
        if (spotless) {
            tasks.withType(JavaCompile) {
                finalizedBy(spotless)
            }

        }
    }

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter'
        compileOnly 'org.projectlombok:lombok:1.18.22'
        annotationProcessor 'org.projectlombok:lombok:1.18.22'
        testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
    }

    repositories {
        mavenLocal()
        mavenCentral()
    }

    test {
        useJUnitPlatform()
    }
    jar {
        enabled = true
        archiveClassifier=''
    }
}

Below is my module-commons build.gradle file


plugins {
    id 'java-library'
}

dependencies {
    compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.16.0'
    compileOnly 'com.fasterxml.jackson.core:jackson-core:2.16.0'
    compileOnly 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.0'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
    testImplementation 'org.mockito:mockito-core:5.8.0'
    testImplementation 'org.projectlombok:lombok:1.18.30'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
    compileOnly 'org.projectlombok:lombok:1.18.30'
}

publishing {
    publications {
        mavenJava(MavenPublication) {
            artifact jar
        }
    }
    repositories {
        mavenLocal()
    }
}

I have the below class in module-commons

package com.springboot.web.library.commons.utils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonUtils {
    public String serialize(Object object) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.writeValueAsString(object);
    }
}

I am running the command gradle build jar module-commons:publishToMavenLocal to publish the JAR to local maven repo.

I am importing the module-commons library in springboot-demo project as implementation 'com.springboot.web.library:module-commons:0.0.3-SNAPSHOT' and below is the springboot-demo project build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.springboot.web'
version = '1.0-SNAPSHOT'

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation 'org.projectlombok:lombok:1.18.28'
    testImplementation platform('org.junit:junit-bom:5.9.1')
    testImplementation 'org.junit.jupiter:junit-jupiter'
    implementation 'org.springframework.boot:spring-boot-starter'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    implementation 'com.springboot.web.library:module-commons:0.0.3-SNAPSHOT'

}

test {
    useJUnitPlatform()
}

I am importing JsonUtils in springboot-demo project Main class as below

package com.springboot.web.library.commons;

import com.springboot.web.library.commons.utils.JsonUtils;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {

    @lombok.SneakyThrows
    public static void main(String[] args) {
        JsonUtils jsonUtils = new JsonUtils();
        jsonUtils.serialize("test");
        System.out.println("Hello world!");
    }
}
 

While running the Main class am getting below compiler error since the module-commons jar doesnt have maven.com.springboot.web.library.module-commons directory with pom.xml inside META-INF directory hence the Gradle is not resolving the jackson library thats referenced in module-commons. I have tried many solutions thats available in Github and Stackoverflow but no luck so far. I appreciate if anyone can help resolve this issue. Full project can be found here https://github.com/JavaSpringBooteer/springboot-gradle-library

D:\Git\springboot-gradle-library\springboot-demo\src\main\java\com\springboot\web\library\commons\Main.java:12: error: cannot access JsonProcessingException
        jsonUtils.serialize("test");
                           ^
  class file for com.fasterxml.jackson.core.JsonProcessingException not found

Tried solutions from Github and Stackoverflow to tweak the build.gradle

2

There are 2 best solutions below

9
On

Your project should be:

 [ Gradle Build System ]
        |
        |---[ module-commons Library ]
        |        |---[ build.gradle ]
        |        |---[ src ]
        |        |---[ dependencies (Jackson, Lombok, etc.) ]
        |
        |---[ springboot-demo Project ]
                 |---[ build.gradle ]
                 |---[ src ]
                 |---[ dependencies (including module-commons) ]

You need to modify your module-commons build script to include dependency information in the published artifact. That can be achieved by configuring the maven-publish plugin correctly.

Your module-commons/build.gradle would be:

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
            artifact sourcesJar

            pom.withXml {
                def dependenciesNode = asNode().appendNode('dependencies')
                configurations.compileClasspath.allDependencies.each { dep ->
                    def dependencyNode = dependenciesNode.appendNode('dependency')
                    dependencyNode.appendNode('groupId', dep.group)
                    dependencyNode.appendNode('artifactId', dep.name)
                    dependencyNode.appendNode('version', dep.version)
                }
            }
        }
    }
    repositories {
        mavenLocal()
    }
}

That would include the main Java component (from components.java), which makes sure the compiled classes and resources are part of the publication.
And it adds a custom section to the generated POM file that lists all compile classpath dependencies.

Run the command ./gradlew module-commons:publishToMavenLocal. After publishing the updated module-commons artifact to your local Maven repository, build and run your springboot-demo project. It should now correctly resolve the dependencies.


Duplicated tag: 'dependencies'

I suppose the pom.withXml block in your build.gradle is appending a new <dependencies> node to the existing POM structure, which already has a <dependencies> tag defined.

Let's modify the pom.withXml block in the module-commons/build.gradle to handle existing <dependencies> tags correctly.

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
            artifact sourcesJar

            pom.withXml {
                def pomNode = asNode()
                // Find or create the 'dependencies' node
                def dependenciesNode = pomNode.children().find { it.name() == 'dependencies' }
                if (dependenciesNode == null) {
                    dependenciesNode = pomNode.appendNode('dependencies')
                }

                configurations.compileClasspath.allDependencies.each { dep ->
                    if (dep.group != null && dep.version != null) {
                        def dependencyNode = dependenciesNode.appendNode('dependency')
                        dependencyNode.appendNode('groupId', dep.group)
                        dependencyNode.appendNode('artifactId', dep.name)
                        dependencyNode.appendNode('version', dep.version)
                    }
                }
            }
        }
    }
    repositories {
        mavenLocal()
    }
}

It is important to check if the group and version of a dependency are not null before appending them to avoid including invalid entries.

Apply the updated script to your module-commons/build.gradle, and run ./gradlew clean build to make sure all changes are properly applied and old artifacts are cleared.
Execute ./gradlew module-commons:publishToMavenLocal. After publishing, check the pom.xml in the local Maven repository to make sure it is correctly formed without duplicated tags.
Run your springboot-demo project to check if the issue is resolved.


As an alternative approach, instead of appending a new <dependencies> node or trying to find and enhance an existing one, you might consider clearing any existing <dependencies> node and recreating it. That will make sure no duplication occurs.

Your module-commons/build.gradle would be:

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
            artifact sourcesJar

            pom.withXml {
                def pomNode = asNode()
                // Remove existing 'dependencies' node if present
                def dependenciesNode = pomNode.children().find { it.name() == 'dependencies' }
                if (dependenciesNode != null) {
                    dependenciesNode.replaceNode {}
                }
                // Re-create the 'dependencies' node
                dependenciesNode = pomNode.appendNode('dependencies')
                configurations.compileClasspath.allDependencies.each { dep ->
                    if (dep.group != null && dep.version != null) {
                        def dependencyNode = dependenciesNode.appendNode('dependency')
                        dependencyNode.appendNode('groupId', dep.group)
                        dependencyNode.appendNode('artifactId', dep.name)
                        dependencyNode.appendNode('version', dep.version)
                    }
                }
            }
        }
    }
    repositories {
        mavenLocal()
    }
}
0
On

your Gradle build isn't generating the Spring Boot library JAR with the embedded pom.xml in the META-INF directory for your module-commons project. This embedded pom.xml is required for proper Maven resolution of dependencies when you publish a JAR to a Maven repository. To generate this pom.xml, you need to configure the MavenPublication for your module-commons project.

Here's how you can modify your module-commons build.gradle file to include the required configuration for MavenPublication:

plugins {
    id 'java-library'
    id 'maven-publish' // Add the Maven Publish plugin
}

dependencies {
    // Your dependencies
}

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java // This will include the Java component
            pom {
                // Customize the generated POM here if needed
                // For example, you can add dependencies or other metadata
                // Example:
                withXml {
                    def dependenciesNode = asNode().appendNode('dependencies')
                    dependenciesNode.appendNode('dependency')
                            .appendNode('groupId', 'com.fasterxml.jackson.core')
                            .appendNode('artifactId', 'jackson-databind')
                            .appendNode('version', '2.16.0')
                            .appendNode('scope', 'compile')
                }
            }
        }
    }
    repositories {
        mavenLocal()
    }
}

In the publishing block, we configure a MavenPublication named mavenJava that includes the Java component of your project. Inside the pom block, you can customize the generated POM as needed. In the example above, I've shown how to add a dependency to the POM for jackson-databind since it's a compile dependency for your project.

After making these changes to your module-commons build.gradle file, run gradle build module-commons:publishToMavenLocal again, and it should generate the JAR with the embedded pom.xml in the META-INF directory. This should resolve the dependency issue in your springboot-demo project.