skip to content

Custom Artifacts and Classifiers

Attaching extra files, such as a fat jar, to a publication and configuring their classifier and extension. Interviewers ask because a stray classifier is how a publication ends up unusable to consumers.

on this pageshow

questions

5

Where in the build script do you attach a custom artifact, and what's the minimal correct snippet to add a fat JAR to a Maven publication?

level: juniorimportance: must knowfreq 48%

answer

  1. publishing { publications { ... } }
  2. on the publication, call artifact(...)
  3. from(components.java) = main jar
  4. pass tasks.named("fatJar")
  5. add classifier to avoid clash

basics

~10 s

Inside publishing { publications { ... } }, on the publication, call artifact(tasks.named("fatJar")). That single line adds the fat JAR as an extra file on that publication's coordinates.

solid answer

~30 s

You attach it inside the `maven-publish` plugin's DSL: `publishing { publications { create<MavenPublication>("maven") { from(components["java"]); artifact(tasks.named("fatJar")) } } }`. The `from(components.java)` adds the standard jar and dependency metadata; the `artifact(...)` call adds your extra file. You typically give it a classifier so it doesn't collide with the main jar. Make sure the `maven-publish` (and usually `java`) plugins are applied, and that the `fatJar` task is a `Jar`/`Zip` so Gradle can read its archive output and wire the build dependency. That's all that's required for `./gradlew publish` to upload both files.

code

kotlin · 15 lines
kotlin
plugins {
    `java-library`
    `maven-publish`
}

publishing {
    publications {
        create<MavenPublication>("maven") {
            from(components["java"])
            artifact(tasks.named("fatJar")) {
                classifier = "all"
            }
        }
    }
}

go deeper

for a junior

Know the exact DSL location and the one-line artifact(tasks.named(...)) call with a classifier.

for a middle

Explain the interplay with from(components.java) and why a classifier is needed.

for a senior

Note plugin prerequisites, that the task form auto-wires the build, and collision avoidance.

for a principal

Standardize publication setup (conventions plugin) so all modules attach extra artifacts consistently.

## Plugins and the DSL location Custom artifacts are added through the **`maven-publish`** plugin (or `ivy-publish`). Apply it, then configure the `publishing` extension: ```kotlin plugins { `java-library` `maven-publish` } publishing { publications { create<MavenPublication>("maven") { from(components["java"]) // main jar + dependencies artifact(tasks.named("fatJar")) { // extra file classifier = "all" } } // repositories { ... } where to publish } } ``` Key points for a junior to get right: - The `artifact(...)` call lives **on the publication**, inside `publications { yourPub { ... } }` — not at the top level, not inside `dependencies`. - `from(components["java"])` provides the *main* artifact and POM dependencies; `artifact(...)` is purely additive. - Pass the **task** (`tasks.named("fatJar")`), not a path, so the file is found and the build is ordered correctly. - Add a `classifier` if the extra file would otherwise clash with the main jar (same empty classifier + `jar` extension). ## What gets produced After `./gradlew publish` you'll see, in the repo, both `mylib-1.0.jar` and `mylib-1.0-all.jar` (plus the POM/metadata). The fat jar shares the coordinates; the `all` classifier distinguishes it. ## Common first mistakes - Putting `artifact(...)` outside the publication block (compile/DSL error). - Omitting the classifier and colliding with the main jar. - Forgetting to apply `maven-publish` so there's no `publishing` extension at all.

  • Which plugin must be applied for the publishing DSL to exist?
    `maven-publish` (or `ivy-publish` for Ivy). Without it there is no `publishing` extension and `publications`/`artifact(...)` are unavailable.
  • What does `from(components.java)` add versus `artifact(...)`?
    `from(components.java)` adds the main jar plus dependency metadata to the publication; `artifact(...)` only attaches one extra file. They're complementary.

saying these in an interview costs you the question

  • Putting `artifact(...)` inside `dependencies { }` instead of on the publication.
  • Forgetting to apply `maven-publish`.
  • Omitting a classifier so the fat jar collides with the main jar.

context

open as a page

How do you attach an extra file (for example, a fat/uber JAR produced by a custom task) to a Maven publication in Gradle?

level: middleimportance: must knowfreq 62%

basics

~10 s

Inside the publication block call artifact(...), passing the task that produces the file, e.g. artifact(tasks.named("fatJar")). Gradle wires the task as a build dependency and uploads its output as an extra artifact.

open as a page

What is the difference between the classifier and the extension on a ConfigurablePublishArtifact, and what happens if two artifacts collide on both?

level: middleimportance: should knowfreq 40%

basics

~20 s

The classifier is the suffix in the filename (e.g. -all), the extension is the file type (e.g. jar, zip). Together with coordinates they uniquely identify a file; two artifacts with the same classifier+extension clash and publishing fails.

open as a page

When you attach a raw File (not a task) as a publication artifact, why might the publish task fail, and how do you fix the build wiring lazily?

level: seniorimportance: should knowfreq 33%

basics

~20 s

A bare File carries no task dependency, so the publish task can run before the file is generated and fails (missing file). Fix it by adding builtBy(theTask) or by passing a Provider<RegularFile> from the task's output, which carries the dependency.

open as a page

An extra artifact attached via publication.artifact(...) appears in the POM but a Gradle consumer resolving by attributes can't 'see' it. Why, and what are the implications?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Raw artifact(...) files are listed in the POM by classifier, so Maven consumers can request them, but they aren't described as a variant in Gradle Module Metadata. Gradle's attribute-based resolution only selects variants, so it ignores these loose files.

open as a page