Skill
Community

build-conventions

Install
1
Install the plugin
$
npx claudepluginhub jsamuelsen11/claude-config --plugin ccfg-java

Want just this skill?

Then install: npx claudepluginhub u/[userId]/[slug]

Description

This skill should be used when creating or editing pom.xml, build.gradle.kts, managing Java dependencies, configuring Maven/Gradle plugins, or organizing multi-module projects.

Tool Access

This skill uses the workspace's default tool permissions.

Skill Content

Maven and Gradle Build Conventions

This skill defines comprehensive build conventions for Java projects, covering Gradle Kotlin DSL, Maven POM configuration, dependency management, plugins, multi-module layouts, and build performance.

Gradle Kotlin DSL

Prefer Gradle Kotlin DSL Over Groovy DSL

Use build.gradle.kts (Kotlin DSL) for type safety, IDE auto-completion, and refactoring support.

// CORRECT: build.gradle.kts (Kotlin DSL)
plugins {
    java
    id("org.springframework.boot") version "3.3.0"
    id("io.spring.dependency-management") version "1.1.5"
}

group = "com.example"
version = "1.0.0"

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("org.postgresql:postgresql")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<Test> {
    useJUnitPlatform()
}
// WRONG: build.gradle (Groovy DSL) for new projects
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.3.0'
}

// Less type safety, worse IDE support
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
}

Complete Spring Boot build.gradle.kts

A comprehensive build file for a Spring Boot application.

// CORRECT: Complete build.gradle.kts for Spring Boot
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent

plugins {
    java
    jacoco
    id("org.springframework.boot") version "3.3.0"
    id("io.spring.dependency-management") version "1.1.5"
    id("com.diffplug.spotless") version "6.25.0"
}

group = "com.example"
version = "1.0.0"

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    // Spring Boot starters
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("org.springframework.boot:spring-boot-starter-actuator")

    // Database
    runtimeOnly("org.postgresql:postgresql")
    runtimeOnly("org.flywaydb:flyway-core")

    // Testing
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.testcontainers:junit-jupiter")
    testImplementation("org.testcontainers:postgresql")
    testImplementation("org.assertj:assertj-core")

    // Development only
    developmentOnly("org.springframework.boot:spring-boot-devtools")
    annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
}

tasks.withType<Test> {
    useJUnitPlatform()
    testLogging {
        events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
        exceptionFormat = TestExceptionFormat.FULL
        showStandardStreams = false
    }
}

tasks.jacocoTestReport {
    dependsOn(tasks.test)
    reports {
        xml.required = true
        html.required = true
    }
}

tasks.jacocoTestCoverageVerification {
    violationRules {
        rule {
            limit {
                counter = "LINE"
                value = "COVEREDRATIO"
                minimum = "0.90".toBigDecimal()
            }
        }
    }
}

spotless {
    java {
        googleJavaFormat("1.22.0")
        removeUnusedImports()
        trimTrailingWhitespace()
        endWithNewline()
    }
}

tasks.check {
    dependsOn(tasks.jacocoTestCoverageVerification)
}

BOM and Dependency Management

Use BOM Imports with platform()

Import Bills of Materials (BOMs) to align dependency versions consistently.

// CORRECT: BOM imports with platform() in Gradle
dependencies {
    implementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.0"))
    implementation(platform("org.testcontainers:testcontainers-bom:1.19.7"))
    implementation(platform("software.amazon.awssdk:bom:2.25.0"))

    // No version needed when managed by BOM
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("software.amazon.awssdk:s3")
    implementation("software.amazon.awssdk:dynamodb")
    testImplementation("org.testcontainers:postgresql")
    testImplementation("org.testcontainers:kafka")
}
// WRONG: Specifying versions already managed by a BOM
dependencies {
    implementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.0"))

    // Version is redundant and can cause conflicts
    implementation("org.springframework.boot:spring-boot-starter-web:3.3.0")
}

BOM Import in Maven

Use dependencyManagement to import BOMs in Maven.

<!-- CORRECT: BOM import in Maven pom.xml -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.3.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>testcontainers-bom</artifactId>
            <version>1.19.7</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- No version needed -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>postgresql</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Dependency Scopes

Use Correct Gradle Dependency Configurations

Choose the right dependency configuration for each dependency.

// CORRECT: Proper dependency scopes in Gradle
dependencies {
    // api: exposes dependency to consumers (use sparingly, only in libraries)
    api("com.google.guava:guava:33.1.0-jre")

    // implementation: internal dependency, not leaked to consumers (default choice)
    implementation("com.fasterxml.jackson.core:jackson-databind")
    implementation("org.slf4j:slf4j-api")

    // runtimeOnly: needed at runtime but not at compile time
    runtimeOnly("org.postgresql:postgresql")
    runtimeOnly("ch.qos.logback:logback-classic")

    // compileOnly: needed at compile time only (e.g., annotations)
    compileOnly("org.projectlombok:lombok")
    annotationProcessor("org.projectlombok:lombok")

    // testImplementation: test compile and runtime
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.assertj:assertj-core")

    // testRuntimeOnly: test runtime only
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
// WRONG: Using implementation when api or runtimeOnly is correct
dependencies {
    // This leaks PostgreSQL driver to consumers (should be runtimeOnly)
    implementation("org.postgresql:postgresql")

    // This hides a public API type from consumers (should be api)
    implementation("com.google.guava:guava:33.1.0-jre")
    // If your public API returns a Guava type, consumers can't compile
}

Maven Dependency Scopes

Use correct Maven scopes for dependencies.

<!-- CORRECT: Maven dependency scopes -->
<dependencies>
    <!-- compile (default): available everywhere -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- runtime: not needed for compilation -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- provided: supplied by the container -->
    <dependency>
        <groupId>jakarta.servlet</groupId>
        <artifactId>jakarta.servlet-api</artifactId>
        <scope>provided</scope>
    </dependency>

    <!-- test: only for testing -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Code Formatting with Spotless

Configure Spotless with google-java-format

Use the Spotless plugin with google-java-format for consistent code formatting.

// CORRECT: Spotless configuration in build.gradle.kts
plugins {
    id("com.diffplug.spotless") version "6.25.0"
}

spotless {
    java {
        googleJavaFormat("1.22.0")
        removeUnusedImports()
        trimTrailingWhitespace()
        endWithNewline()
        targetExclude("build/**")
    }
    kotlinGradle {
        ktlint("1.2.1")
    }
}

// Run formatting check before build
tasks.check {
    dependsOn(tasks.named("spotlessCheck"))
}
# Apply formatting
./gradlew spotlessApply

# Check formatting (CI)
./gradlew spotlessCheck

Spotless in Maven

Configure Spotless via the Maven plugin.

<!-- CORRECT: Spotless Maven plugin -->
<plugin>
    <groupId>com.diffplug.spotless</groupId>
    <artifactId>spotless-maven-plugin</artifactId>
    <version>2.43.0</version>
    <configuration>
        <java>
            <googleJavaFormat>
                <version>1.22.0</version>
                <style>GOOGLE</style>
            </googleJavaFormat>
            <removeUnusedImports />
            <trimTrailingWhitespace />
            <endWithNewline />
        </java>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
            <phase>verify</phase>
        </execution>
    </executions>
</plugin>

Static Analysis Plugins

Configure Checkstyle

Use Checkstyle for enforcing coding standards.

// CORRECT: Checkstyle in build.gradle.kts
plugins {
    id("checkstyle")
}

checkstyle {
    toolVersion = "10.15.0"
    configFile = file("config/checkstyle/checkstyle.xml")
    isIgnoreFailures = false
    maxWarnings = 0
}

tasks.withType<Checkstyle> {
    reports {
        xml.required = true
        html.required = true
    }
}
<!-- config/checkstyle/checkstyle.xml (Google checks) -->
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
    "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
    "https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
    <module name="TreeWalker">
        <module name="GoogleStyle"/>
    </module>
</module>

Configure SpotBugs

Use SpotBugs for finding potential bugs through static analysis.

// CORRECT: SpotBugs in build.gradle.kts
plugins {
    id("com.github.spotbugs") version "6.0.9"
}

spotbugs {
    toolVersion = "4.8.4"
    effort = com.github.spotbugs.snom.Effort.MAX
    reportLevel = com.github.spotbugs.snom.Confidence.MEDIUM
}

tasks.withType<com.github.spotbugs.snom.SpotBugsTask> {
    reports.create("html") {
        required = true
    }
    reports.create("xml") {
        required = false
    }
}

Configure JaCoCo

Use JaCoCo for code coverage measurement and enforcement.

// CORRECT: JaCoCo in build.gradle.kts
plugins {
    jacoco
}

jacoco {
    toolVersion = "0.8.11"
}

tasks.jacocoTestReport {
    dependsOn(tasks.test)
    reports {
        xml.required = true
        html.required = true
        csv.required = false
    }
    classDirectories.setFrom(files(classDirectories.files.map {
        fileTree(it) {
            exclude(
                "**/config/**",
                "**/dto/**",
                "**/*Application*",
                "**/*Config*",
                "**/*Properties*"
            )
        }
    }))
}

tasks.jacocoTestCoverageVerification {
    violationRules {
        rule {
            limit {
                counter = "LINE"
                value = "COVEREDRATIO"
                minimum = "0.90".toBigDecimal()
            }
            limit {
                counter = "BRANCH"
                value = "COVEREDRATIO"
                minimum = "0.80".toBigDecimal()
            }
        }
    }
}

tasks.check {
    dependsOn(tasks.jacocoTestCoverageVerification)
}

Multi-Module Projects

Gradle Multi-Module with Convention Plugins

Use convention plugins in buildSrc for shared configuration in multi-module Gradle projects.

my-project/
  buildSrc/
    build.gradle.kts
    src/main/kotlin/
      java-conventions.gradle.kts
      spring-conventions.gradle.kts
  app/
    build.gradle.kts
  domain/
    build.gradle.kts
  infrastructure/
    build.gradle.kts
  settings.gradle.kts
  build.gradle.kts
// buildSrc/build.gradle.kts
plugins {
    `kotlin-dsl`
}

repositories {
    gradlePluginPortal()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-gradle-plugin:3.3.0")
    implementation("io.spring.gradle:dependency-management-plugin:1.1.5")
    implementation("com.diffplug.spotless:spotless-plugin-gradle:6.25.0")
}
// buildSrc/src/main/kotlin/java-conventions.gradle.kts
plugins {
    java
    jacoco
    id("com.diffplug.spotless")
}

group = "com.example"

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter")
    testImplementation("org.assertj:assertj-core")
    testImplementation("org.mockito:mockito-junit-jupiter")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

spotless {
    java {
        googleJavaFormat("1.22.0")
        removeUnusedImports()
    }
}
// buildSrc/src/main/kotlin/spring-conventions.gradle.kts
plugins {
    id("java-conventions")
    id("org.springframework.boot")
    id("io.spring.dependency-management")
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
// app/build.gradle.kts
plugins {
    id("spring-conventions")
}

dependencies {
    implementation(project(":domain"))
    implementation(project(":infrastructure"))
    implementation("org.springframework.boot:spring-boot-starter-web")
}
// domain/build.gradle.kts
plugins {
    id("java-conventions")
}

// Domain module has no Spring dependency
// infrastructure/build.gradle.kts
plugins {
    id("spring-conventions")
}

dependencies {
    implementation(project(":domain"))
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("org.postgresql:postgresql")
}
// settings.gradle.kts
rootProject.name = "my-project"

include("app", "domain", "infrastructure")

Maven Multi-Module with Parent POM

Use a parent POM for shared configuration in Maven multi-module projects.

<!-- Parent pom.xml -->
<?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.3.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>my-project</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>app</module>
        <module>domain</module>
        <module>infrastructure</module>
    </modules>

    <properties>
        <java.version>21</java.version>
        <testcontainers.version>1.19.7</testcontainers.version>
        <assertj.version>3.25.3</assertj.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.testcontainers</groupId>
                <artifactId>testcontainers-bom</artifactId>
                <version>${testcontainers.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>domain</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>infrastructure</artifactId>
                <version>${project.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>com.diffplug.spotless</groupId>
                    <artifactId>spotless-maven-plugin</artifactId>
                    <version>2.43.0</version>
                    <configuration>
                        <java>
                            <googleJavaFormat>
                                <version>1.22.0</version>
                            </googleJavaFormat>
                        </java>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.jacoco</groupId>
                    <artifactId>jacoco-maven-plugin</artifactId>
                    <version>0.8.11</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>
<!-- app/pom.xml (child module) -->
<?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>com.example</groupId>
        <artifactId>my-project</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>app</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>domain</artifactId>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>infrastructure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Version Catalogs

Use libs.versions.toml for Centralized Versions

Use Gradle version catalogs for centralized dependency version management.

# gradle/libs.versions.toml
[versions]
spring-boot = "3.3.0"
spring-dependency-management = "1.1.5"
testcontainers = "1.19.7"
assertj = "3.25.3"
mockito = "5.11.0"
spotless = "6.25.0"
spotbugs = "6.0.9"
jacoco = "0.8.11"
postgresql = "42.7.3"
flyway = "10.11.0"
jackson = "2.17.0"

[libraries]
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" }
spring-boot-starter-data-jpa = { module = "org.springframework.boot:spring-boot-starter-data-jpa" }
spring-boot-starter-validation = { module = "org.springframework.boot:spring-boot-starter-validation" }
spring-boot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator" }
spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test" }
spring-boot-devtools = { module = "org.springframework.boot:spring-boot-devtools" }
spring-boot-configuration-processor = { module = "org.springframework.boot:spring-boot-configuration-processor" }

postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
flyway-core = { module = "org.flywaydb:flyway-core", version.ref = "flyway" }

testcontainers-bom = { module = "org.testcontainers:testcontainers-bom", version.ref = "testcontainers" }
testcontainers-junit-jupiter = { module = "org.testcontainers:junit-jupiter" }
testcontainers-postgresql = { module = "org.testcontainers:postgresql" }

assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" }
mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" }

[bundles]
spring-web = [
    "spring-boot-starter-web",
    "spring-boot-starter-validation",
    "spring-boot-starter-actuator",
]
testing = [
    "spring-boot-starter-test",
    "assertj-core",
    "mockito-junit-jupiter",
]
testcontainers = [
    "testcontainers-junit-jupiter",
    "testcontainers-postgresql",
]

[plugins]
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
spring-dependency-management = { id = "io.spring.dependency-management", version.ref = "spring-dependency-management" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
spotbugs = { id = "com.github.spotbugs", version.ref = "spotbugs" }
// build.gradle.kts using version catalog
plugins {
    java
    alias(libs.plugins.spring.boot)
    alias(libs.plugins.spring.dependency.management)
    alias(libs.plugins.spotless)
}

dependencies {
    implementation(libs.bundles.spring.web)
    implementation(libs.spring.boot.starter.data.jpa)
    runtimeOnly(libs.postgresql)
    runtimeOnly(libs.flyway.core)

    testImplementation(platform(libs.testcontainers.bom))
    testImplementation(libs.bundles.testing)
    testImplementation(libs.bundles.testcontainers)

    developmentOnly(libs.spring.boot.devtools)
    annotationProcessor(libs.spring.boot.configuration.processor)
}
// WRONG: Hardcoded versions in build.gradle.kts when catalog exists
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web:3.3.0")  // Use catalog
    testImplementation("org.assertj:assertj-core:3.25.3")  // Use catalog
}

Build Wrappers

Always Use Gradle Wrapper

Commit the Gradle wrapper to version control for reproducible builds.

# Generate Gradle wrapper
gradle wrapper --gradle-version 8.7

# Files to commit
# gradlew          (Unix script)
# gradlew.bat      (Windows script)
# gradle/wrapper/gradle-wrapper.jar
# gradle/wrapper/gradle-wrapper.properties
# .gitignore for Gradle projects
.gradle/
build/
!gradle/wrapper/gradle-wrapper.jar
!gradle/wrapper/gradle-wrapper.properties
# CORRECT: Use wrapper for all commands
./gradlew build
./gradlew test
./gradlew bootRun

# WRONG: Using system Gradle directly
gradle build   # May use different version

Always Use Maven Wrapper

Commit the Maven wrapper for reproducible builds.

# Generate Maven wrapper
mvn wrapper:wrapper -Dmaven=3.9.6

# Files to commit
# mvnw            (Unix script)
# mvnw.cmd        (Windows script)
# .mvn/wrapper/maven-wrapper.jar
# .mvn/wrapper/maven-wrapper.properties
# CORRECT: Use wrapper for all commands
./mvnw clean install
./mvnw test
./mvnw spring-boot:run

# WRONG: Using system Maven directly
mvn clean install   # May use different version

Publishing to Maven Central

Configure Publishing in Gradle

Set up publishing for Maven Central via Sonatype OSSRH.

// CORRECT: Publishing configuration in build.gradle.kts
plugins {
    `maven-publish`
    signing
}

java {
    withJavadocJar()
    withSourcesJar()
}

publishing {
    publications {
        create<MavenPublication>("mavenJava") {
            from(components["java"])

            pom {
                name = "My Library"
                description = "A description of my library"
                url = "https://github.com/username/my-library"

                licenses {
                    license {
                        name = "The Apache License, Version 2.0"
                        url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
                    }
                }

                developers {
                    developer {
                        id = "username"
                        name = "Developer Name"
                        email = "dev@example.com"
                    }
                }

                scm {
                    connection = "scm:git:git://github.com/username/my-library.git"
                    developerConnection = "scm:git:ssh://github.com:username/my-library.git"
                    url = "https://github.com/username/my-library"
                }
            }
        }
    }

    repositories {
        maven {
            name = "OSSRH"
            url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
            credentials {
                username = findProperty("ossrhUsername") as String? ?: ""
                password = findProperty("ossrhPassword") as String? ?: ""
            }
        }
    }
}

signing {
    sign(publishing.publications["mavenJava"])
}

Publishing in Maven

Configure Maven Central publishing in pom.xml.

<!-- CORRECT: Distribution management in pom.xml -->
<distributionManagement>
    <snapshotRepository>
        <id>ossrh</id>
        <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
    </snapshotRepository>
    <repository>
        <id>ossrh</id>
        <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
    </repository>
</distributionManagement>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.3.1</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <goals>
                        <goal>jar-no-fork</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-javadoc-plugin</artifactId>
            <version>3.6.3</version>
            <executions>
                <execution>
                    <id>attach-javadocs</id>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-gpg-plugin</artifactId>
            <version>3.2.2</version>
            <executions>
                <execution>
                    <id>sign-artifacts</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>sign</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Build Performance

Enable Gradle Build Cache

Enable build caching for faster incremental builds.

# gradle.properties
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.jvmargs=-Xmx2g -XX:+UseParallelGC
org.gradle.configureondemand=true
// settings.gradle.kts - enable local build cache
buildCache {
    local {
        isEnabled = true
        directory = File(rootDir, ".gradle/build-cache")
        removeUnusedEntriesAfterDays = 30
    }
}
# WRONG: gradle.properties without performance tuning
# (missing all performance flags)

Enable Parallel Builds in Maven

Configure Maven for parallel builds.

# CORRECT: Parallel Maven builds
./mvnw -T 1C clean install        # 1 thread per CPU core
./mvnw -T 4 clean install         # 4 threads
./mvnw -T 1C -pl app -am install  # Build specific module with dependencies

# CORRECT: Skip tests for faster builds during development
./mvnw -DskipTests clean package  # Skip test execution
./mvnw -Dmaven.test.skip clean package  # Skip compilation and execution
<!-- .mvn/maven.config for default options -->
-T 1C
--no-transfer-progress

Avoid Unnecessary Rebuilds

Configure proper inputs and outputs to avoid unnecessary work.

// CORRECT: Custom task with proper inputs/outputs for caching
tasks.register<Exec>("generateOpenApi") {
    inputs.file("src/main/resources/openapi.yaml")
    outputs.dir("build/generated/openapi")
    commandLine("./gradlew", "openApiGenerate")
}
// WRONG: Task always runs because no inputs/outputs defined
tasks.register<Exec>("generateOpenApi") {
    commandLine("./gradlew", "openApiGenerate")  // Runs every time
}

Complete pom.xml Example

Full Maven Project Configuration

A comprehensive pom.xml for a Spring Boot application.

<?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.3.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>my-service</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>my-service</name>
    <description>My Spring Boot Service</description>

    <properties>
        <java.version>21</java.version>
        <testcontainers.version>1.19.7</testcontainers.version>
        <spotless.version>2.43.0</spotless.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.testcontainers</groupId>
                <artifactId>testcontainers-bom</artifactId>
                <version>${testcontainers.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- Database -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
        </dependency>

        <!-- Testing -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>postgresql</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>com.diffplug.spotless</groupId>
                <artifactId>spotless-maven-plugin</artifactId>
                <version>${spotless.version}</version>
                <configuration>
                    <java>
                        <googleJavaFormat>
                            <version>1.22.0</version>
                        </googleJavaFormat>
                        <removeUnusedImports />
                    </java>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>check</goal>
                        </goals>
                        <phase>verify</phase>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.11</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>report</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>check</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>check</goal>
                        </goals>
                        <configuration>
                            <rules>
                                <rule>
                                    <element>BUNDLE</element>
                                    <limits>
                                        <limit>
                                            <counter>LINE</counter>
                                            <value>COVEREDRATIO</value>
                                            <minimum>0.90</minimum>
                                        </limit>
                                    </limits>
                                </rule>
                            </rules>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Existing Repository Compatibility

Respect Established Build Conventions

When contributing to existing repositories, follow their established build tool and conventions.

# Discover build conventions
ls build.gradle.kts build.gradle pom.xml 2>/dev/null   # Build tool
cat gradle/libs.versions.toml 2>/dev/null               # Version catalog
cat gradle.properties 2>/dev/null                        # Build properties
ls buildSrc/ 2>/dev/null                                 # Convention plugins
cat .mvn/maven.config 2>/dev/null                        # Maven defaults
# Before making changes:
# - Check if project uses Maven or Gradle
# - Check Gradle DSL: Kotlin (.kts) or Groovy
# - Look for version catalogs (libs.versions.toml)
# - Look for convention plugins (buildSrc/)
# - Check for Spotless/Checkstyle configuration
# - Verify Java version in build configuration
# - Follow the established dependency declaration style

This skill ensures consistent, performant, and maintainable build configurations across Java projects using Maven or Gradle. Apply these conventions for reproducible builds, proper dependency management, and enforced code quality standards.

Stats
Stars0
Forks0
Last CommitFeb 10, 2026

Similar Skills