Shared Gradle conventions for JDK-based projects.
- Applies common plugins:
java,checkstyle,jacoco,spotless,errorprone. - Configures Java toolchain, runtime launcher, and bytecode level (defaults to JDK 25 from any available vendor).
- The plugin itself uses Kotlin/JVM 21 (Gradle embeds Kotlin 2.2.x today).
- Adds
mavenCentral()by default (configurable). - Sets Checkstyle tool version and uses project-level Checkstyle files when provided (bundled defaults otherwise).
- Sets JaCoCo tool version and enforces instruction coverage.
- Configures Spotless for basic Java formatting (unused imports, trailing whitespace, newline at EOF).
- Applies Spotless license header conventions when
LICENSE_HEADERexists in the project root. - Adds common compile/test dependencies (Lombok, JSpecify, JetBrains annotations, Error Prone/NullAway, JUnit Jupiter, AssertJ).
- Configures all
Testtasks to use JUnit Platform and adds JUnit Platform launcher astestRuntimeOnly. - Enables
sourcesJarandjavadocJargeneration. - Adds
maven-publishconventions by default (mavenJavapublication +mavenLocal/GitHub Packages repositories). - Resolves
leanish.conventions.basePackagefrom project config or infers it fromsrc/main/javapackage declarations. - Adds root-only helper tasks (
installGitHooks,setupProject) and makesbuilddepend oninstallGitHooks. - Makes
checkdepend on everyJacocoCoverageVerificationtask.
Use the Gradle Plugin Portal for released versions.
For local development of unreleased changes, publish this plugin to mavenLocal() and use your snapshot version (for example, 0.3.1-SNAPSHOT).
The plugin adds mavenCentral() by default to every project where it is applied.
The canonical plugin id is io.github.leanish.java-conventions.
build.gradle.kts:
plugins {
id("io.github.leanish.java-conventions") version "0.3.0"
}settings.gradle.kts:
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
plugins {
id("io.github.leanish.java-conventions") version "0.3.0"
}
}Apply it in each build.gradle.kts:
plugins {
id("io.github.leanish.java-conventions")
}If the plugin version is not published to the Gradle Plugin Portal yet:
- Publish this plugin locally:
./gradlew publishToMavenLocal
- Ensure consumer
settings.gradle(.kts)hasmavenLocal()inpluginManagement.repositories(before remote repositories) while testing unreleased snapshots, for example:pluginManagement { repositories { mavenLocal() gradlePluginPortal() mavenCentral() } } - Do not use
pluginManagement { includeBuild("../java-conventions") }; consume by plugin id + version. - When you switch back to a released version, remove
mavenLocal()(or move it after remote repositories) to avoid resolving stale local artifacts.
If you want root-only tasks (installGitHooks, setupProject) in a multi-project build, apply the plugin in the root project too:
plugins {
id("io.github.leanish.java-conventions") version "0.3.0"
}Configure behavior through gradle.properties (or -P...):
# Repository conventions
leanish.conventions.repositories.mavenCentral.enabled=true
# Publishing conventions
leanish.conventions.publishing.enabled=true
leanish.conventions.publishing.githubOwner=acme
leanish.conventions.publishing.developer.id=acme
leanish.conventions.publishing.developer.name=Acme Team
leanish.conventions.publishing.developer.url=https://github.com/acme
# Project conventions (optional override)
leanish.conventions.basePackage=io.github.leanish
Environment variables are also supported, and they override gradle.properties / -P values:
JAVA_CONVENTIONS_MAVEN_CENTRAL_ENABLEDJAVA_CONVENTIONS_PUBLISHING_ENABLEDJAVA_CONVENTIONS_PUBLISHING_GITHUB_OWNERJAVA_CONVENTIONS_PUBLISHING_DEVELOPER_IDJAVA_CONVENTIONS_PUBLISHING_DEVELOPER_NAMEJAVA_CONVENTIONS_PUBLISHING_DEVELOPER_URLJAVA_CONVENTIONS_BASE_PACKAGEGITHUB_REPOSITORY_OWNER(highest precedence for publishing owner inference)
leanish.conventions.basePackage is optional:
- If configured, the plugin uses that value.
- If missing, the plugin infers it from
src/main/javapackage declarations, stores the inferred value in project extra properties, and logs the inference. - The plugin fails fast only when the property is blank or when it cannot infer any Java package.
Publishing repository/name/description conventions are derived from project metadata and are not configurable properties:
- GitHub repository defaults to
project.name - POM name defaults to
project.name - POM description defaults to
project.description(orproject.namewhen missing)
Publishing owner/developer metadata is optional:
leanish.conventions.publishing.githubOwnerresolves owner for GitHub URLs/repository.- Owner resolves by
GITHUB_REPOSITORY_OWNER, thenJAVA_CONVENTIONS_PUBLISHING_GITHUB_OWNER, thenleanish.conventions.publishing.githubOwner, thengroupwhen it matchesio.github.<owner>. - Developer fields (
id,name,url) are optional and independent; missing values are inferred from resolved owner when possible (id=<owner>,name=<owner>,url=https://github.com/<owner>), and thedevelopersblock is emitted only when all three resolve. - GitHub Packages credentials resolve by environment first (
GITHUB_ACTOR,GITHUB_TOKEN), then properties (gpr.user,gpr.key).
- For Gradle Plugin Portal, set
gradle.publish.keyandgradle.publish.secretin~/.gradle/gradle.properties, or useGRADLE_PUBLISH_KEYandGRADLE_PUBLISH_SECRET. - Publish with
./gradlew publishPlugins. - For local testing, use
./gradlew publishToMavenLocal. - For GitHub Packages publishing of this plugin project:
- repository is
https://maven.pkg.github.com/leanish/java-conventions - credentials resolve by environment first (
GITHUB_ACTOR,GITHUB_TOKEN), then properties (gpr.user,gpr.key) - publish with
./gradlew publishAllPublicationsToGitHubPackagesRepository.
- repository is
Replace defaults with your own:
tasks.withType<JacocoCoverageVerification>().configureEach {
violationRules {
rules.forEach { rule ->
rule.limits.forEach { limit ->
limit.minimum = "0.91".toBigDecimal()
}
}
}
}Keep defaults and append more:
tasks.withType<JavaCompile>().configureEach {
options.errorprone {
errorproneArgs.add("-Xep:MissingOverride:WARN")
}
}Clear defaults, then define your own:
spotless {
java {
clearSteps()
googleJavaFormat("1.23.0")
endWithNewline()
}
}- Default JDK version is 25; vendor is
ANY. - To change the vendor without changing the version:
java {
toolchain {
vendor.set(JvmVendorSpec.ADOPTIUM)
}
}- To change bytecode level:
tasks.withType<JavaCompile>().configureEach {
options.release.set(21)
}- The plugin enables
sourcesJarandjavadocJarby default. - Consumers can disable them in
build.gradle.kts:
tasks.named("sourcesJar") {
enabled = false
}
tasks.named("javadocJar") {
enabled = false
}- If you also want to omit these artifacts from publications, skip the
sourcesElementsandjavadocElementsvariants:
import org.gradle.api.component.AdhocComponentWithVariants
components.named<AdhocComponentWithVariants>("java") {
withVariantsFromConfiguration(configurations["sourcesElements"]) { skip() }
withVariantsFromConfiguration(configurations["javadocElements"]) { skip() }
}- Enforces instruction coverage via
jacocoTestCoverageVerification. - Default minimum is
0.85unless overridden. - Set
-DexcludeTags=integration(or any tags) to skip those tests and disable coverage verification.
- All
Testtasks calluseJUnitPlatform(). - The plugin adds
org.junit.jupiter:junit-jupiter:6.0.2andorg.assertj:assertj-core:3.27.7astestImplementation. - The plugin adds
org.junit.platform:junit-platform-launcher:6.0.2astestRuntimeOnly. - If you need different test execution behavior for specific tasks, override those tasks in your build script.
Publishing conventions are enabled by default. To disable, set
leanish.conventions.publishing.enabled=false.
When enabled, the plugin:
- Applies
maven-publish. - Creates/configures
mavenJavapublication from the Java component. - Configures POM defaults from project metadata:
- repository and SCM use
https://github.com/<githubOwner>/<project.name>when owner is resolvable - POM
nameusesproject.name - POM
descriptionusesproject.description(or falls back toproject.name)
- repository and SCM use
- Adds
mavenLocal()always. - Adds GitHub Packages (
GitHubPackages) publishing repository only when owner is resolvable.
Two supported patterns:
-
Keep conventions enabled and reconfigure existing entries.
- Reconfigure
mavenJavawithpublications.named("mavenJava", MavenPublication::class.java). - Reconfigure
GitHubPackageswithrepositories.named("GitHubPackages", MavenArtifactRepository::class.java). - Do not use
create<MavenPublication>("mavenJava")when conventions are enabled, becausemavenJavaalready exists.
- Reconfigure
-
Fully replace plugin publishing behavior.
- Set
leanish.conventions.publishing.enabled=false. - Configure
maven-publishentirely in the consumer project (publications { create(...) }, custom repositories, full POM metadata).
- Set
The plugin applies Spotless Java license headers only when LICENSE_HEADER exists in the project root.
When the file exists, the plugin:
- Applies Spotless Java license header step.
- Uses
LICENSE_HEADERfrom the project root.
When LICENSE_HEADER is missing, the plugin skips the header step and logs at INFO level.
There is no separate leanish.conventions.spotless.licenseHeader.enabled property; file presence controls behavior.
The conventions plugin applies net.ltgt.errorprone and adds Error Prone + NullAway dependencies automatically.
It:
- Adds
-XDaddTypeAnnotationsToSymbol=true. - Configures Error Prone with default arguments (including NullAway).
- Sets NullAway annotated packages from resolved
leanish.conventions.basePackage. - Disables Error Prone for
compileTestJava.
- Checkstyle uses
config/checkstyle/checkstyle.xmlandconfig/checkstyle/suppressions.xmlwhen present in the consumer project. - If either file is missing, the plugin falls back to bundled defaults (
checkstyle.xmland empty suppressions). - These files are materialized under
build/generated/checkstylefor Checkstyle only and are not packaged into JARs/publications. - The plugin does not add a toolchain resolver; ensure the configured JDK is available locally or add a resolver in the consuming project.
- Dependencies added by the plugin are additive; your project dependencies remain in effect.
- The bundled pre-commit hook runs
./gradlew spotlessApplyand./gradlew checkstyleMain checkstyleTest, and may modify files before commit.