Skip to content
Latchkey

Gradle "Failed to publish ... 401 Unauthorized" - Fix Publishing Auth in CI

Gradle authenticated to the publish target and was rejected with a 401. The publishing block sent no credentials or the wrong ones - the repository credentials {} is missing, or the CI secret holding the token is absent or stale.

What this error means

A publish/publishAllPublicationsTo...Repository task fails with Could not PUT '<url>'. Received status code 401 from server: Unauthorized. Resolution from the same repo works; only the upload fails.

gradle output
> Task :app:publishMavenPublicationToReleasesRepository FAILED
> Failed to publish publication 'maven' to repository 'releases'
   > Could not PUT 'https://nexus.example.com/.../app-1.0.0.jar'.
     Received status code 401 from server: Unauthorized

Diagnose it: get the real failure out of Gradle

Gradle summarises failures aggressively, and the top-level message frequently describes a downstream symptom rather than the cause. Re-run the failing task with diagnostics before changing build logic.

Terminal
# the actual stack, plus what Gradle decided about the build
./gradlew <task> --stacktrace --info

# is a stale daemon or cache involved?
./gradlew --stop
./gradlew <task> --no-daemon --no-build-cache

# what does Gradle think the environment is?
./gradlew -version

Common causes

No credentials on the publish repository

The maven { url = ... } repository in the publishing block has no credentials {} (or empty values), so Gradle uploads unauthenticated and the server returns 401.

Wrong or missing CI secret

The username/token is read from environment variables or Gradle properties that are not set in CI, or the token has expired, so authentication fails.

How to fix it

Add credentials from CI secrets to the publish repo

Wire the username/token into the publishing repository, sourced from CI environment variables.

build.gradle.kts
publishing {
    repositories {
        maven {
            name = "releases"
            url = uri("https://nexus.example.com/repository/maven-releases/")
            credentials {
                username = System.getenv("REPO_USER")
                password = System.getenv("REPO_TOKEN")
            }
        }
    }
}

Confirm the secret is injected into the job

Make sure the CI step actually exposes the token to the Gradle process.

.github/workflows/ci.yml
- run: ./gradlew publish
  env:
    REPO_USER: ${{ secrets.REPO_USER }}
    REPO_TOKEN: ${{ secrets.REPO_TOKEN }}

Configuration cache and CI

  • The configuration cache rejects build logic that reads mutable state at execution time, which is why enabling it surfaces errors an existing build never showed.
  • Run with --configuration-cache-problems=warn first to see the full list rather than failing on the first one.
  • A cached configuration keyed to a different environment is worse than none. Include the JDK version in your cache key.

How to prevent it

  • Always declare credentials {} on publish repositories, sourced from CI secrets.
  • Use a write/deploy-scoped token for publishing, separate from read tokens.
  • Rotate publish tokens and confirm they are injected into the job env.

Frequently asked questions

What causes Gradle "Failed to publish ... 401 Unauthorized"?
There are 2 common causes: no credentials on the publish repository and wrong or missing ci secret. The maven { url = ...
How do I fix Gradle "Failed to publish ... 401 Unauthorized"?
There are 2 fixes depending on which cause you have: add credentials from ci secrets to the publish repo and confirm the secret is injected into the job. Work through them in order, since the first is the most common.
What does Gradle "Failed to publish ... 401 Unauthorized" actually mean?
A publish/publishAllPublicationsTo...Repository task fails with Could not PUT '<url>'.
How do I stop Gradle "Failed to publish ... 401 Unauthorized" happening again?
Always declare credentials {} on publish repositories, sourced from CI secrets. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card