Home > Developer Guide > Guides > Gradle Version Catalogs + BOM


Gradle Version Catalogs with Bill of Materials (BOM)

Purpose: Definitive guide to correctly using Gradle version catalogs with BOM dependencies

Context: Discovered during PR #269 Firebase BOM debugging - syntax WAS correct, library availability was the issue

Key Insight: Version catalogs + BOMs work perfectly together when configured correctly

Overview

What Are Version Catalogs?

Gradle Version Catalogs provide centralized dependency management in gradle/libs.versions.toml:

Benefits:

  • Single source of truth for dependency versions
  • Type-safe accessors in build scripts (libs.firebase.auth)
  • IDE autocomplete support
  • Easier dependency updates across multi-module projects

What Are BOMs?

Bill of Materials (BOM) is a special Maven artifact that declares compatible versions of related libraries:

Benefits:

  • Guaranteed compatible library versions
  • No need to specify individual library versions
  • Simplified dependency management
  • Transitive version resolution

The Challenge

Combining version catalogs with BOMs requires specific syntax. During PR #269 debugging, we initially suspected the syntax was wrong when builds failed, but the real issue was library availability in the BOM.

This guide documents the CORRECT patterns so future developers don’t waste hours debugging.


The Correct Pattern

Version Catalog Configuration

File: gradle/libs.versions.toml

[versions]
# BOM version declared here
firebase-bom = "33.0.0"
compose-bom = "2025.11.00"
 
[libraries]
# BOM entry WITH version
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" }
 
# Individual libraries WITHOUT versions (BOM provides them)
firebase-auth = { group = "com.google.firebase", name = "firebase-auth" }
firebase-firestore = { group = "com.google.firebase", name = "firebase-firestore" }
firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" }
 
# Compose BOM WITH version
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
 
# Compose libraries WITHOUT versions (BOM provides them)
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
compose-ui = { group = "androidx.compose.ui", name = "ui" }
compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }

Build Script Usage

File: app/build.gradle.kts

dependencies {
    // Import BOM using platform()
    implementation(platform(libs.firebase.bom))
    implementation(platform(libs.compose.bom))
 
    // Use individual libraries (versions from BOM)
    implementation(libs.firebase.auth)
    implementation(libs.firebase.firestore)
    implementation(libs.firebase.analytics)
 
    implementation(libs.compose.material3)
    implementation(libs.compose.ui)
    debugImplementation(libs.compose.ui.tooling)
}

Key Points:

  1. BOM entry: Must have version.ref or explicit version
  2. Individual libraries: Must NOT have versions
  3. Must use platform() wrapper for BOM entries
  4. Must use object notation ({ group = ..., name = ... }), NOT string notation

Why This Works

Gradle’s BOM Resolution Process

  1. platform(libs.firebase.bom) declaration:

    • Gradle imports Firebase BOM artifact
    • BOM contains version constraints for all Firebase libraries
  2. implementation(libs.firebase.auth) declaration:

    • Gradle looks for com.google.firebase:firebase-auth artifact
    • No version specified in version catalog
    • Gradle checks BOM for version constraint
    • BOM provides version (e.g., 22.1.0)
    • Gradle resolves to com.google.firebase:firebase-auth:22.1.0
  3. Version consistency:

    • All libraries managed by BOM get compatible versions
    • No version conflicts between Firebase libraries
    • Transitive dependencies also managed by BOM

Common Mistakes

Mistake 1: Using String Notation for BOM Libraries

Wrong:

[libraries]
firebase-auth = "com.google.firebase:firebase-auth"  # ❌ String notation

Correct:

[libraries]
firebase-auth = { group = "com.google.firebase", name = "firebase-auth" }  # ✅ Object notation

Why It Fails: String notation in version catalogs requires a version (e.g., "group:artifact:version"). BOM-managed dependencies can’t use string notation because they have no version.


Mistake 2: Specifying Versions for BOM-Managed Libraries

Wrong:

[versions]
firebase-auth = "22.1.0"  # ❌ Don't specify versions for BOM-managed deps
 
[libraries]
firebase-auth = { group = "com.google.firebase", name = "firebase-auth", version.ref = "firebase-auth" }

Correct:

[libraries]
firebase-auth = { group = "com.google.firebase", name = "firebase-auth" }  # ✅ No version

Why It Fails: Specifying a version overrides the BOM. This defeats the purpose of using a BOM and can cause version conflicts.


Mistake 3: Forgetting platform() Wrapper

Wrong:

dependencies {
    implementation(libs.firebase.bom)  # ❌ Missing platform()
    implementation(libs.firebase.auth)
}

Correct:

dependencies {
    implementation(platform(libs.firebase.bom))  # ✅ Wrapped in platform()
    implementation(libs.firebase.auth)
}

Why It Fails: Without platform(), Gradle treats the BOM as a regular library dependency instead of a constraint provider. Individual libraries won’t get versions from the BOM.


Mistake 4: Referencing Libraries Not in the BOM

Wrong:

[libraries]
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version = "34.6.0" }
firebase-auth-ktx = { group = "com.google.firebase", name = "firebase-auth-ktx" }  # ❌ Not in BOM 34.x!

Error:

Could not find com.google.firebase:firebase-auth-ktx:.
                                                       ^
                                                       Empty version string

Why It Fails: Firebase BOM 34.0.0+ removed all -ktx libraries. The version catalog syntax is correct, but the library doesn’t exist in the BOM.

Solution: Either use BOM 33.x (which includes -ktx) or remove -ktx suffix for BOM 34.x+.

Related: Firebase KTX Deprecation Guide


Advanced Patterns

Pattern 1: Multiple BOMs in One Project

Scenario: Using Firebase BOM and Compose BOM together

[versions]
firebase-bom = "33.0.0"
compose-bom = "2025.11.00"
 
[libraries]
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" }
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
 
firebase-auth = { group = "com.google.firebase", name = "firebase-auth" }
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
dependencies {
    implementation(platform(libs.firebase.bom))
    implementation(platform(libs.compose.bom))
 
    implementation(libs.firebase.auth)
    implementation(libs.compose.material3)
}

Works perfectly! Each BOM manages its own set of libraries.


Pattern 2: Mixing BOM and Non-BOM Dependencies

Scenario: Some libraries from BOM, others specified explicitly

[versions]
firebase-bom = "33.0.0"
retrofit = "2.9.0"
 
[libraries]
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" }
firebase-auth = { group = "com.google.firebase", name = "firebase-auth" }  # From BOM
 
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }  # Explicit version
dependencies {
    implementation(platform(libs.firebase.bom))
    implementation(libs.firebase.auth)  // Version from BOM
 
    implementation(libs.retrofit)  // Explicit version
}

Works great! BOM and non-BOM dependencies coexist happily.


Pattern 3: BOM Version Overrides (Advanced)

Scenario: Need specific version of one library, but use BOM for others

dependencies {
    implementation(platform(libs.firebase.bom))
 
    // Use BOM versions for most libraries
    implementation(libs.firebase.auth)
    implementation(libs.firebase.firestore)
 
    // Override specific library version (use with caution!)
    implementation("com.google.firebase:firebase-analytics:21.5.0")  // Explicit version
}

⚠️ Warning: Overriding BOM versions can cause compatibility issues. Only do this if you know what you’re doing.


Troubleshooting

Build Fails with “Could not find X:.”

Symptom:

Could not find com.google.firebase:firebase-auth:.
                                                   ^
                                                   Empty version string

Diagnosis Steps:

  1. Check BOM declaration:

    grep "firebase-bom" gradle/libs.versions.toml

    Should have a version: firebase-bom = "33.0.0"

  2. Check platform() usage:

    grep "platform.*firebase" app/build.gradle.kts

    Should have: implementation(platform(libs.firebase.bom))

  3. Check library exists in BOM:

    • Visit Maven Central
    • Open BOM POM file for your version
    • Search for the library name
  4. Common causes:

    • Library doesn’t exist in that BOM version (e.g., -ktx in BOM 34.x)
    • Typo in library name
    • Missing platform() wrapper
    • BOM version not specified in version catalog

IDE Shows “Cannot Resolve Symbol” for libs.X

Symptom: IntelliJ/Android Studio can’t resolve libs.firebase.auth

Solutions:

  1. Sync Gradle:

    • File > Sync Project with Gradle Files
    • Or click the “Sync Now” banner
  2. Invalidate Caches:

    • File > Invalidate Caches / Restart
    • Select “Invalidate and Restart”
  3. Check version catalog syntax:

    ./gradlew checkLibsVersions  # If you have this task
  4. Verify file location: Version catalog must be at gradle/libs.versions.toml (not gradle/libs.versions.yml or other location)


Versions from BOM Don’t Match Expectations

Symptom: Expected version X, but Gradle resolved version Y

Diagnosis:

./gradlew app:dependencies --configuration releaseRuntimeClasspath | grep firebase

Look for constraint sources:

com.google.firebase:firebase-auth:22.1.0
  variant "runtime" [
    org.gradle.category = library
    org.gradle.status   = release
  ]
  via constraint: com.google.firebase:firebase-bom:33.0.0  # ← Source of version

Common causes:

  1. Wrong BOM version: Check [versions] in version catalog
  2. Version override: Search build files for explicit versions
  3. Transitive dependency: Another library depends on different Firebase version

Real-World Example: PR #269 Debugging

The Situation

Goal: Update Gradle dependencies to latest versions

Changes Made:

  • Updated Firebase BOM from 33.0.0 to 34.6.0
  • Moved Firebase dependencies to version catalog
  • Used correct syntax (object notation, no versions)

Result:

Could not find com.google.firebase:firebase-analytics-ktx:.
Could not find com.google.firebase:firebase-crashlytics-ktx:.

The Investigation

Attempted Fixes (all failed):

  1. Fixed Compose BOM syntax (unrelated)
  2. Added firebase-crashlytics-ktx to version catalog (didn’t help)
  3. Fixed KSP version (unrelated)
  4. Downgraded Firebase BOM to 34.3.0 (still failed)
  5. Cleared Gradle caches multiple times (didn’t help)

The Breakthrough: Compared with working main branch:

  • Main uses Firebase BOM 33.0.0
  • Main uses hardcoded strings, not version catalog
  • Suspected version catalog was the issue

Further Investigation:

  • Researched Gradle docs (syntax was correct)
  • Researched Stack Overflow (conflicting advice)
  • Found GitHub issue #17117 (confirmed pattern works)
  • Checked Firebase release notesKTX deprecated in BOM 34.0.0!

The Real Problem

Not a version catalog issue! The syntax was correct all along.

The actual problem:

  • Firebase BOM 34.0.0+ removed -ktx libraries
  • Version catalog referenced firebase-auth-ktx (doesn’t exist in BOM 34.x)
  • Gradle correctly looked for versions in BOM, found nothing → empty string

The Solution

Option A: Stay on BOM 33.0.0 (chosen for PR #269)

implementation(platform("com.google.firebase:firebase-bom:33.0.0"))
implementation("com.google.firebase:firebase-auth-ktx")

Option B: Migrate to BOM 34.x without -ktx (future work)

implementation(platform("com.google.firebase:firebase-bom:34.6.0"))
implementation("com.google.firebase:firebase-auth")  // No -ktx

Lessons Learned

  1. Version catalog syntax WAS correct - don’t blame the tools first
  2. Check external dependencies - breaking changes in libraries can look like configuration issues
  3. Compare with known-working state - saved hours of debugging
  4. Read release notes - Firebase announced this change, we just didn’t see it initially

Related Documentation:


Best Practices

1. Always Use Object Notation for BOM Libraries

# ✅ Correct
firebase-auth = { group = "com.google.firebase", name = "firebase-auth" }
 
# ❌ Wrong
firebase-auth = "com.google.firebase:firebase-auth"

2. Document BOM Versions in Comments

[versions]
# Last version with KTX support (July 2025 deprecation)
firebase-bom = "33.0.0"
 
# Latest stable Compose BOM (Nov 2025)
compose-bom = "2025.11.00"
[libraries]
# Firebase BOM and dependencies
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" }
firebase-auth = { group = "com.google.firebase", name = "firebase-auth" }
firebase-firestore = { group = "com.google.firebase", name = "firebase-firestore" }
 
# Compose BOM and dependencies
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
compose-material3 = { group = "androidx.compose.material3", name = "material3" }

4. Use Bundles for Common Sets

[bundles]
firebase = ["firebase-auth", "firebase-firestore", "firebase-analytics"]
compose-ui = ["compose-ui", "compose-material3", "compose-ui-tooling"]
dependencies {
    implementation(platform(libs.firebase.bom))
    implementation(libs.bundles.firebase)  // All Firebase deps at once
}

5. Verify BOM Contents Before Upgrading

Before upgrading a BOM version:

  1. Check release notes for breaking changes
  2. Visit Maven Central and review BOM POM file
  3. Verify all your dependencies still exist in new BOM
  4. Test in a separate branch before merging

External Resources

Official Gradle Documentation:

GitHub Issues:

Related Project Documentation:


Summary

Key Takeaways:

  1. Version catalogs + BOMs work perfectly together when configured correctly
  2. Use object notation for BOM-managed dependencies
  3. Omit versions for individual libraries (BOM provides them)
  4. Must use platform() wrapper for BOM entries
  5. Verify library exists in BOM before assuming syntax issue
  6. This pattern is officially supported by Gradle

When Builds Fail:

  • Don’t immediately blame version catalog syntax
  • Check if libraries exist in the BOM version you’re using
  • Compare with known-working configuration
  • Read external dependency release notes

Status: Active - Pattern validated during PR #269 debugging

Last Updated: 2025-11-18 Related PRs: #269 (Gradle dependency updates) Week: 28