Home › Mobile › Kotlin Unresolved Reference After Adding a Dependency
Beginner 5 min · September 23, 2026

Kotlin Unresolved Reference After Adding a Dependency

Sync Gradle, then check module and coordinates: most red imports come from a missed sync or a dependency in the wrong module..

N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Notes here come from systems that actually shipped.

Follow
✓ Production
production tested
September 26, 2026
last updated
2,085
articles · all by Naren
Before you start⏱ 8 min
  • ✓An Android project with Gradle (Kotlin DSL or Groovy)
  • ✓Basic comfort editing app/build.gradle.kts
  • ✓Android Studio with Gradle sync available
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • A red import means Gradle hasn't delivered the artifact yet: sync the project before anything else
  • The dependency must live in the module that imports it; implementation hides it from other modules
  • Verify coordinates against Maven Central and catalog aliases for typos — one letter breaks resolution
  • Check repositories in settings.gradle and turn off Gradle offline mode for new artifacts
  • You'll clear stubborn cases with invalidate-caches plus version alignment of Kotlin, AGP, and Compose
✦ Definition~90s read
What is Kotlin Unresolved Reference After Adding Dependency?

An 'unresolved reference' in Kotlin means the compiler looked for a declaration — a class, function, or property — and found nothing on its classpath. After adding a dependency, that declaration should arrive via Gradle: the build file names an artifact (group:name:version), Gradle resolves it against the repositories in settings.gradle, downloads it into the dependency cache, and exposes it to both the Kotlin compiler and the IDE index.

★
Think of your project as a library that orders books from warehouses (repositories).

Red imports mean some link in that chain broke, and each link breaks differently.

The chain has six links worth knowing. Declaration: the dependency line must be in the module whose code imports it, with the right configuration (implementation vs api). Coordinates: group, name, and version must match a published artifact exactly. Catalog: version-catalog aliases must be defined and spelled identically at use.

Repositories: google() and mavenCentral() (or your mirror) must be listed where Gradle looks. Network: offline mode, proxies, and cache writability decide whether downloads can happen. Versions: the Kotlin plugin, AGP, and Compose compiler must form a compatible trio or generated code won't compile.

The IDE adds a seventh wrinkle — its index is a cache of the resolved classpath, not the resolution itself. Sync rebuilds the model; sometimes the index lags or corrupts, showing red on code that compiles fine (or white on code that fails). That's why the ladder runs sync first and invalidate-caches last: fix delivery before suspecting the map.

Understand the chain and every red import becomes a binary search instead of a guessing game.

Plain-English First

Think of your project as a library that orders books from warehouses (repositories). Adding a dependency places an order — but the book arrives only with delivery (Gradle sync). Wrong branch (module), misspelled title (coordinates), unused warehouse (missing repo), or a truck that won't leave (offline mode) all end one way: you reach for the book and it isn't there. Check each delivery step in order instead of reordering the same book five times.

You added one dependency line, and now half your imports are red. Unresolved reference after adding a dependency is Android's most common five-minute problem that somehow eats entire afternoons. The causes form a short list — unsynced project, wrong module, implementation-vs-api visibility, a typo in coordinates, a missing repository, stale caches, version mismatches — but they all paint the screen the same angry red, so developers thrash between fixes at random.

The confusion is structural. Gradle resolution, Kotlin compilation, and IDE indexing are three separate systems, and any of them can be the one that's actually broken. You'll invalidate caches when the problem is a typo, or retype coordinates when the problem is offline mode. Each wrong guess costs a full sync cycle, and on large projects that's ten minutes a pop. Four wrong guesses and lunch is gone.

This article orders the checklist by probability so your first guess is usually right. You'll learn the sync-first ladder, the implementation-vs-api rule, how to bulletproof version catalogs, and when stale caches or version skew are really to blame. Follow it top to bottom and most red imports die in minutes.

Sync First: Delivering the Artifact Before Debugging Anything

Gradle sync is the delivery truck between your declaration and your code. Editing a build file only changes the order; sync executes it — resolving coordinates against repositories, downloading artifacts, and rebuilding the IDE's index of available classes. Until sync completes successfully, the import is red because the class genuinely isn't available yet. This sounds obvious, but skipping the sync (or missing its failure banner) accounts for a huge share of reports.

Run sync deliberately and read its output. Android Studio's elephant icon or the 'Sync Now' banner triggers it; the Build window shows per-artifact progress and the exact failure when resolution breaks. A success with red imports remaining means the problem moved downstream to indexing or version skew. A failure names names: the artifact string it couldn't find, the repository that refused it, the network error underneath. That text is the diagnosis — copy it before clicking anything else.

Make sync cheap so developers actually run it. Keep configuration cache enabled, avoid dynamic versions (2.+) that force re-resolution, and don't stack heavy configuration-time work (network calls in build files) that turns every sync into minutes. When sync is fast, the team's first reflex becomes 'sync and read the log' instead of 'guess and retype.' That reflex alone halves the lifetime of most dependency incidents.

app/build.gradle.ktsKOTLIN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// settings.gradle.kts: repositories decide WHERE artifacts come from
dependencyResolutionManagement {
    repositories {
        google()        // AndroidX, AGP, Compose
        mavenCentral()  // most JVM libraries
    }
}

// app/build.gradle.kts: declare, sync, verify
dependencies {
    implementation("androidx.core:core-ktx:1.13.1")
}
// Then: Sync Now (elephant icon) and watch the Build window.
// Success + white import = done. Error text = your next step.
📊 Production Insight
Teams that read sync errors first fix resolution issues in one cycle; teams that retype coordinates first average three.
🎯 Key Takeaway
Editing declares, syncing delivers. Run it, read the Build output, and let failure text route your next step.

implementation vs api: Visibility Across Modules

Multi-module projects add a visibility dimension that single-module apps never meet. implementation() means 'my private dependency' — the compiler and IDE hide it from downstream modules. api() means 'part of my public contract' — downstream modules inherit it. When :feature imports a type that only :network declares with implementation, the import is red despite the artifact existing in the build. Nothing is broken; the dependency is simply private to the wrong module.

The fix starts with ownership: every module declares what it imports. If :feature uses OkHttp types directly, :feature's build file should declare OkHttp — duplicating the line is correct, not wasteful, because it documents real coupling. Promote :network's declaration to api() only when the type flows through public signatures (function parameters, return types, exposed properties) that consumers must compile against. Default to implementation everywhere else for faster builds and cleaner boundaries.

Review dependency placement like API design. Each api() declaration is a promise that constrains future upgrades — changing it breaks consumers. Audit with ./gradlew :module:dependencies and look for modules compiling against transitives they never declared; each is a red import waiting for the day the upstream module switches to implementation. Explicit declarations make the graph honest and the reds explainable.

network/build.gradle.ktsKOTLIN
1
2
3
4
5
6
7
8
9
10
11
12
13
// :network module exposes its model types to consumers -> api
// network/build.gradle.kts
dependencies {
    api("com.squareup.moshi:moshi:1.15.1") // part of our public signatures
    implementation("com.squareup.okhttp3:okhttp:4.12.0") // internal only
}

// :feature module: OkHttp stays invisible here (good), Moshi visible (intended)
// feature/build.gradle.kts
dependencies {
    implementation(project(":network"))
    // importing okhttp3.Request here would be RED -> declare it or keep it hidden
}
📊 Production Insight
Mystery reds in :feature that build fine in :app are visibility bugs nine times out of ten — check the keyword before the coordinates.
🎯 Key Takeaway
implementation hides, api exposes. Declare in the importing module; promote to api only for types in public signatures.

Version Catalog Typos and Coordinate Errors

Typos are the most embarrassing cause and among the most common. Artifact coordinates are three-part strings (group:name:version) that humans mistype constantly — transposed letters, wrong group (androidx vs android.support), version that doesn't exist. Catalog aliases add a second typo surface: the alias definition and its use must match exactly. The compiler can't distinguish 'wrong name' from 'missing library,' so both present as the same red import.

Version catalogs (gradle/libs.versions.toml) convert most of these into caught errors. Coordinates live once in the TOML file; build files reference type-safe accessors (libs.core.ktx) with IDE autocomplete. A misspelled alias fails in the catalog with a precise message instead of failing as a phantom missing class. Centralized versions also end the drift where :app uses Moshi 1.14 and :feature uses 1.15, producing duplicate-class warnings at best and runtime NoSuchMethodErrors at worst.

Adopt two habits around the catalog. First, copy coordinates from Maven Central's artifact page, never from memory or chat snippets — five seconds of verification beats thirty minutes of sync spirals. Second, pin exact versions and avoid dynamic (2.+) or snapshot ranges in release branches; dynamic versions resolve differently over time and turn green builds red with no code change. Dependabot or Renovate PRs against the catalog keep versions fresh without the chaos.

gradle/libs.versions.tomlKOTLIN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# gradle/libs.versions.toml: one source of truth
[versions]
coreKtx = "1.13.1"
moshi = "1.15.1"

[libraries]
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
moshi = { group = "com.squareup.moshi", name = "moshi", version.ref = "moshi" }

// app/build.gradle.kts: type-safe alias, typo-resistant
// dependencies {
//     implementation(libs.core.ktx)
//     implementation(libs.moshi)
// }
📊 Production Insight
One-letter transpositions in artifact names cost teams half-hours regularly — catalog autocomplete deletes the category.
🎯 Key Takeaway
Centralize in libs.versions.toml, copy coordinates from Maven Central, and pin exact versions for reproducible builds.

Stale Caches, Offline Mode, and CI-Only Failures

Sometimes the declaration is right and the machinery is stale. Gradle caches artifacts, metadata, and IDE indexes aggressively; a corrupted entry or an interrupted download can poison resolution long after the underlying problem cleared. Symptoms are distinctive: coordinates verified on Maven Central, repositories correct, offline mode off — yet sync insists the artifact is missing. A second machine building the same commit cleanly confirms the environment, not the code, is at fault.

Escalate in order of blast radius. ./gradlew :app:dependencies shows exactly what resolved and from where — look for FAILED lines naming the artifact and repository. --refresh-dependencies forces metadata re-download without wiping everything. Invalidate Caches plus clean rebuild is the heavy hammer for corrupted indexes; expect a long re-index and schedule it deliberately. Check offline mode and proxy settings before any hammer: a surprising number of 'cache corruption' cases are Gradle forbidden from reaching the network.

CI deserves its own checklist because it fails differently than laptops. Verify the CI image's repositories mirror settings.gradle, confirm the Gradle home is writable (read-only caches fake resolution failures), and ensure lockfiles or exact pins prevent dynamic-version drift between runs. Print the failing repository in CI logs on resolution errors — that single line routes the fix to infra versus code in seconds instead of hours.

terminalKOTLIN
1
2
3
4
5
6
7
8
9
# Confirm what Gradle actually resolved:
# ./gradlew :app:dependencies --configuration debugRuntimeClasspath

# Force re-resolution when a mirror or metadata looks stale:
# ./gradlew :app:build --refresh-dependencies

# Nuclear option for cache corruption (then restart + rebuild):
# File > Invalidate Caches > Invalidate and Restart
# ./gradlew clean assembleDebug
📊 Production Insight
CI-only resolution failures are environmental until proven otherwise — diff the environment before touching declarations.
🎯 Key Takeaway
Verify with dependencies output, refresh metadata before wiping caches, and check CI's repos, writability, and pins.

Kotlin Plugin and Compiler Version Skew

The Kotlin Gradle plugin version shapes what the compiler understands — language features, codegen for Compose, synthetic accessors, and multiplatform expectations. When it drifts from the stdlib version or the Compose compiler version, references to generated code go red while hand-written imports stay white. The pattern is unmistakable once you've seen it: your code is fine, @Composable functions and injected constructors are red, and no dependency error appears anywhere.

The fix is the compatibility table, not guesswork. Google publishes the Compose compiler ↔ Kotlin mapping; JetBrains documents Kotlin ↔ AGP support ranges. Pick a tested trio, declare all three in the version catalog, and upgrade them as one commit. After switching, invalidate caches and run a full clean build — half-upgraded incremental state produces phantom errors that send you chasing ghosts.

Guard the trio in review. Any PR bumping Kotlin, AGP, or the Compose compiler alone gets a request to bump the others per the table. Record the working trio in a comment at the top of libs.versions.toml so the next upgrade starts from known-good state instead of archaeology. Boring version hygiene here prevents the weirdest-looking reds in Android development.

⚠ Upgrade the Trio Together
Don't upgrade Kotlin, AGP, and the Compose compiler independently. They ship as tested trios in the compatibility table — mixing versions across releases is the fastest route to phantom reds on generated code.
📊 Production Insight
Reds confined to generated code with clean sync logs mean version skew — check the trio before touching any declaration.
🎯 Key Takeaway
Align Kotlin, AGP, and Compose compiler as a tested trio from the compatibility table; upgrade them atomically.

Keeping Red Imports Rare Across the Team

Make red imports rare by construction. Keep a short team runbook — sync, module, coordinates, repos, caches, versions — linked from the onboarding docs so every developer runs the same ladder instead of inventing their own. Add a CI job that builds with --offline after a normal build to catch undeclared transitives, and a second job on a clean agent that catches cache-dependent greens. Both are cheap and both catch what laptops hide.

Standardize the catalog aggressively. No string coordinates in module build files, no dynamic versions on release branches, Dependabot PRs reviewed weekly. When the catalog is the only place versions live, upgrades become single-file diffs with predictable blast radius, and typo-class errors collapse to near zero. New modules copy an existing build file's dependency block instead of writing declarations from scratch.

Finally, log the wins. Each time the ladder resolves an incident in minutes, note which rung fixed it in the PR or postmortem. After a quarter you'll have data showing where your team actually bleeds — usually one rung dominates — and you can automate exactly that (a sync reminder bot, a catalog lint, a CI repo check). Process improves fastest when it aims at measured pain.

📊 Production Insight
Teams with a written ladder resolve dependency reds in minutes; teams without one rediscover the same five causes every month.
🎯 Key Takeaway
Runbook the ladder, enforce catalog-only declarations in CI, and automate whichever rung your data shows hurts most.
● Production incidentPOST-MORTEMseverity: high

CI Went Red on Every Module After a Read-Only Cache Change

Symptom
CI failed on all modules with 'could not resolve' errors Monday morning, with no code changes over the weekend. Local builds passed on every machine. The team burned a day downgrading libraries and adding repositories that changed nothing.
Assumption
Everyone assumed the library was broken or yanked from Maven Central, because the error said 'could not resolve.' Two engineers tried older versions, snapshot repos, and mirrored artifacts for most of a day.
Root cause
An infrastructure update mounted CI's Gradle home read-only. New artifacts downloaded but couldn't be written to cache, so resolution failed for every fresh dependency while cached ones kept working. Local builds were unaffected, which sent the investigation toward library versions instead of the environment.
Fix
CI's Gradle user home was mounted read-only after an infra change, so new artifacts couldn't be cached — resolution failed for anything not already stored. Infra restored a writable cache path, the team pinned exact versions in the catalog, and added a CI step that prints the failing repository on resolution errors.
Key lesson
  • Read the full sync error: it names the failing repository and the real I/O cause behind 'could not resolve.'
  • Environment changes (infra, images, mounts) break builds that no code change explains — check those first on red CI.
  • Pinned versions plus a writable cache make resolution reproducible; dynamic versions make it weather-dependent.
Production debug guideFive checks from red import to green build in minutes.5 entries
Symptom · 01
Import red immediately after editing a build file
→
Fix
Click 'Sync Now' (or the elephant icon) and watch the Build window. If sync succeeds and the import turns white, you were just unsynced — done. If sync itself errors, read the error text: it names the exact artifact or repository problem, which routes you to the right step below.
Symptom · 02
Import resolves in one module but stays red in another
→
Fix
Open the build.gradle.kts of the module containing the red import and confirm the dependency is declared there. If it's only in :app but the import is in :feature, either move the declaration or change it to api() in the upstream module. Sync and rebuild the single module.
Symptom · 03
Gradle sync fails with 'could not find' or 'could not resolve'
→
Fix
Compare the declaration character-by-character against Maven Central (search group:artifact). Check catalog aliases in gradle/libs.versions.toml for typos in both the alias definition and its use. Fix, sync, and confirm the artifact downloads in the Build output.
Symptom · 04
Coordinates look right but nothing downloads
→
Fix
Open settings.gradle(.kts) and confirm google() and mavenCentral() are listed. Check Gradle settings for offline mode (must be off for new artifacts) and verify proxy/VPN allows Maven traffic. Run ./gradlew :app:dependencies --configuration debugRuntimeClasspath to see the resolution attempt.
Symptom · 05
Everything looks right, clean checkouts fail too, generated code is red
→
Fix
Run File > Invalidate Caches > Invalidate and Restart, then ./gradlew clean assembleDebug. If reds persist, compare Kotlin plugin, AGP, and Compose compiler versions against the official compatibility table and align them. Phantom reds on generated code almost always mean version skew.
Unresolved Reference Causes, Checks, and Fixes
Root CauseHow to ConfirmFixPrevention
Dependency never synced after addingImport red; Gradle sync warning in Build windowSync project with Gradle files; rebuildSync immediately after every dependency edit
implementation vs api visibility gapResolves in declaring module, red in consumerPromote to api() or redeclare in consumerDeclare deps in the module that imports them
Version catalog or coordinate typoSync error names the bad artifact stringFix alias/coordinates against Maven CentralAutocomplete catalog entries; copy coordinates
Stale caches or version mismatchClean build + fresh checkout still fails identicallyInvalidate caches, clean, align Kotlin/AGP versionsPin versions in catalog; record working combos
⚙ Quick Reference
3 commands from this guide
FileCommand / CodePurpose
appbuild.gradle.ktsdependencyResolutionManagement {Sync First
networkbuild.gradle.ktsdependencies {implementation vs api
gradlelibs.versions.toml[versions]Version Catalog Typos and Coordinate Errors

Key takeaways

1
Run the fix ladder in order
sync, module placement, coordinates, repos/offline, caches, versions.
2
Declare each dependency in the module that imports it; use implementation by default, api only for leaked types.
3
Centralize versions in a catalog and copy coordinates from Maven Central
never from memory.
4
Check repositories and offline mode before blaming the artifact; the sync log names the failing repo.
5
Align Kotlin, AGP, and Compose compiler versions via the compatibility table.
6
Invalidate caches and clean-build only after the cheap checks fail, not before.

Common mistakes to avoid

5 patterns
×

Adding the dependency to the wrong module's build file

Symptom
The import resolves in :app but not in :feature, or vice versa, and moving the declaration around feels like superstition.
Fix
Add the dependency with implementation() in the module that uses it, sync, and confirm the import resolves. Promote to api() only when the type leaks into your module's public signatures.
×

Using api() for everything 'to be safe'

Symptom
Build times creep up, unrelated modules recompile on every change, and version conflicts surface in modules that never chose the library.
Fix
Prefer implementation() everywhere; switch to api() deliberately when consumers need the types. You'll keep builds faster and avoid leaking transitive APIs you didn't mean to expose.
×

Typos in artifact names, groups, or catalog aliases

Symptom
'Could not find androidx.compose.ui:ui-tolling' style errors where one transposed letter costs half an hour.
Fix
Copy the artifact coordinates from Maven Central (group:artifact:version) rather than typing from memory, and let version catalogs autocomplete. A five-second check beats a thirty-minute sync spiral.
×

Mixing incompatible Kotlin, AGP, and Compose compiler versions

Symptom
Bizarre unresolved references to generated code (composable functions, synthetic accessors) with no dependency error in sight.
Fix
Match the Kotlin plugin version to your Kotlin stdlib and Compose compiler versions, and upgrade via the official compatibility table. After changing versions, invalidate caches and run a clean build.
×

Missing repository or offline mode blocking the download

Symptom
The dependency line looks perfect, the artifact exists online, but Gradle insists it can't resolve anything new.
Fix
Check settings.gradle for the repository list first. Then verify offline mode is off and the proxy allows Maven traffic. Sync output names the failing repository explicitly — read it before retrying.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
A new import is red after adding a dependency. What's your fix order?
Q02SENIOR
Explain implementation vs api with a concrete example.
Q03SENIOR
How do version catalogs prevent dependency mistakes?
Q04SENIOR
Unresolved references appear only on Compose generated code after a Kotl...
Q05SENIOR
CI can't resolve a dependency that works locally. How do you debug it?
Q01 of 05JUNIOR

A new import is red after adding a dependency. What's your fix order?

ANSWER
The compiler can't find the declaration: the dependency providing it isn't declared, synced, visible (implementation vs api), or downloaded. The fix ladder is sync, check module placement, verify coordinates, check repositories/offline mode, then invalidate caches. Each step eliminates one layer.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
I pressed Sync — why is the import still red?
02
When should I use implementation vs api?
03
Can Gradle offline mode cause unresolved references?
04
Why do generated-code references (Room, Hilt, protobuf) stay red?
05
Does the Kotlin plugin version really affect imports?
06
Why did a build that worked yesterday fail today with no code changes?
N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Notes here come from systems that actually shipped.

Follow
✓ Verified
production tested
September 26, 2026
last updated
2,085
articles · all by Naren
🔥

That's Kotlin. Mark it forged?

5 min read · try the examples if you haven't

←
Previous
Kotlin suspend Function Called From Non-Coroutine Context
5 / 5 · Kotlin
Next
Android app:mergeDebugResources Duplicate Resources
→