Upgrade dependency versions

Upgrading your dependencies gives you access to their latest features, bug fixes, and improvements. To upgrade your dependencies, you need to understand how Gradle resolves the versions you request, the risks that are involved, and steps you can take to mitigate those risks.

Consider your upgrade strategy

The most important step to any upgrade is risk analysis. Determine how comfortable you are with each dependency you upgrade. There are many considerations when defining your upgrade strategy, including:

Build a library

Are you building an application that users download and run on a device? Or are you building a library to assist other developers build their applications?

If you're building an application, your focus should be keeping your application up-to-date and stable.

If you're building a library, your focus should be on other developers' applications. Your upgrades affect your consumers. If you upgrade one of your dependencies, that version becomes a candidate for Gradle's dependency resolution, possibly breaking the application's use of that dependency.

First - minimize your library's dependencies where possible. The fewer dependencies you have, the lower the impact on your consumer's dependency resolution.

Be sure to follow semantic versioning to help indicate the types of changes you're making. For example, AndroidX follows semantic versioning and adds a pre-release versioning scheme. Try to avoid major version upgrades to avoid breaking your consumers.

Consider creating a release-candidate (RC) of your library for users to test early.

See Backward compatibility guidelines for library authors for details on keeping your library's Application Binary Interface (ABI) compatible. Use integration tests and tools like Binary compatibility validator to ensure your ABI changes match your intended version change.

If you release fixes in a patch to lower versions of your library, your consumers don't need to upgrade your library to the next major or minor version unless they want new features. Avoid upgrading transitive dependencies in these upgrades.

If your library upgrade requires breaking changes that may be particularly painful for your consumers, consider releasing them as a new artifact so the old and new versions can coexist and allow a more gradual rollout.

Note: If an upgrade to one of your dependencies contains a major API change, you'll probably want to upgrade it in a major or minor release and make any necessary changes. If you don't, users of your library might do so, causing incompatibilities between your library and that dependency. This might apply even if there is no change to your library. You can release a new version just to upgrade that dependency.

Release cycle

How often do you release your application or library?

Shorter development and release cycles

  • There's less time to upgrade.
  • You can quickly get behind.
  • Frequent small upgrades can ease the workload.
  • If a library upgrade becomes problematic, you can more quickly roll back that upgrade.
  • Tools like Dependabot and Renovate lessen the workload, but be sure to analyze the results to check for risks.

Longer development and release cycles

  • There's more time to make and test upgrades.
  • Newer versions of dependencies are more likely to be released during your cycle.
  • Rolling back upgrades and releasing your application or library takes longer.

Keep up with the latest features

Do you prefer to use the latest available features and APIs, or only upgrade when you need a feature or bug fix?

Consider the tradeoffs of frequent upgrades. Future upgrades are easier (fewer changes to integrate), but you're taking upgrade risks more often.

Testing upgrades to pre-release (alpha, beta, release candidate) versions of libraries can help readiness when stable releases are available.

New dependency

If you're adding a new dependency, consider a strong review process that examines that library for all risk criteria to ensure they have been properly evaluated. Don't allow new dependencies to be added without review.

Dedicated team

Do you have a dedicated build team? Do your software engineers maintain the build? A dedicated team often can spend more time analyzing upgrade risks and testing out new versions to ensure the build works properly before engineers use the new versions.

Type of upgrade

Some upgrades are more important than others. Think about which are most important to you.

Build tool upgrades, such as Gradle and Gradle plugins, typically have lower impact to your users, and much of the risk is internal to your build. The build itself helps validate these changes. Library and SDK upgrades are more difficult to validate, and they present a higher risk to your users.

Android Gradle Plugin (AGP) — the tooling used to build your Android application or library. This is the most critical upgrade you can make, as it often includes or enables performance improvements, bug fixes, new lint rules, and support for new Android platform versions.

Gradle — you'll often need to upgrade Gradle when you upgrade AGP or another Gradle plugin.

Other Gradle plugins — Sometimes Gradle's plugin API changes. When you upgrade Gradle, check for upgrades to plugins that you use.

Kotlin and Java — Some libraries and plugins require minimum versions of Kotlin or Java, or you want to take advantage of new language features, APIs, or performance improvements.

Android Platform — Play Store requires regular Android SDK upgrades. You should test new versions of the Android SDK as soon as possible. Some SDK upgrades require changes to your application, such as new permissions or use of new APIs.

Libraries — Do you want to prioritize libraries based on their closeness to your overall architecture?

  • Platform and Architecture related libraries, such as AndroidX, often change to take advantage of new features or help abstract changes in the platform. Upgrade these libraries at least whenever you upgrade the Android platform or other architecture-related libraries.
  • Other library upgrades can be spread out or delayed unless you need a new feature or specific bug fixes.

Android Studio — keeping Android Studio up to date gives you access to the latest features and bug fixes in the underlying IntelliJ IDEA platform and tools to work with the latest Android SDKs.

Available tooling

There are many tools and plugins available to assist with your upgrades. Tools like Dependabot and Renovate automatically upgrade library versions in your build, but be sure to analyze the results to check for risks.

Strategies for specific types of upgrades

Upgrading some types of dependencies might have a cascading effect, requiring other types of dependencies to be upgraded. We discuss relationships between build elements in Tool and library interdependencies.

Build dependencies and their relationships
Figure 1. Build relationships.

When upgrading each type of component, consider how the upgrade impacts other components in the build.

Android Gradle Plugin (AGP)

Android Studio includes an AGP upgrade assistant that can assist with these tasks.

If you use the assistant or perform the upgrade manually, consider the following:

Look at the AGP release notes.

Upgrade Gradle to at least the version listed.

Upgrade Android Studio to a version that supports the chosen AGP version.

Use versions of Android Studio and AGP that support the Android SDK you want to use.

Check compatibility with SDK Build Tools, NDK, and JDK.

If you develop a Gradle plugin (for internal or public use) that extends or uses data from AGP, check whether you need to upgrade your plugin. Sometimes AGP deprecates and later removes APIs, causing incompatibilities with previous plugins.

Kotlin compiler, language and runtime

Check the Kotlin release notes for known issues and incompatibilities.

If you use Jetpack Compose:

  • If the new Kotlin version is lower than 2.0.0:
  • If the new Kotin version is 2.0.0 or higher:

If you use Kotlin Symbol Processing (KSP), see KSP Quickstart for setup and KSP Releases for available versions. Note that you must use a version of KSP that matches the Kotlin version. For example, if you're using Kotlin 2.0.21, you can use any version of the KSP plugin that starts with 2.0.21, such as 2.0.21-1.0.25. You usually won't need to upgrade the KSP processors (such as the Room compiler, which appears as a ksp dependency in your build files); the KSP plugin abstracts much of the compiler API, and the KSP API used by processors is stable.

Upgrade all other Kotlin Compiler Plugins that you're using. The Kotlin Compiler Plugin API often changes between releases, and the plugins must use a compatible API. If the plugin is listed in Compiler plugins, you must use the same version as the Kotlin compiler. For any other compile plugins, check their documentation for the appropriate mapping.

Note that compiler plugins that are not maintained alongside the Kotlin compiler itself often experience release delays as they wait for the compiler plugin API to stabilize. Before upgrading Kotlin, check that all compiler plugins you use have matching upgrades available.

Finally, on some occasions, the Kotlin language changes, requiring you to update your code. This most often happens if you're trying out experimental features. If your code doesn't build properly after upgrading the Kotlin compiler, check for language changes or runtime library breakage in the Kotlin release notes.

Kotlin Compiler Plugins

If you need to upgrade a Kotlin compiler plugin, upgrade to the matching version of Kotlin being used.

Most Kotlin compiler plugins either use the same version as the Kotlin compiler, or they start with the required version of the Kotlin compiler. For example, if the plugin version is 2.0.21-1.0.25, you must use version 2.0.21 of the Kotlin compiler.

Changing the Kotlin compiler version sometimes requires other changes.

Libraries

Libraries are the most commonly upgraded dependency in your build. You'll see available upgrades in the Android Studio editor, or if you use some dependency tools and plugins.

Some libraries specify a minimum compileSdk or minSdk required to use the library. If you don't use at least the specified compileSdk, your builds fail. However, the minSdk of your application is automatically set to the maximum of all minSdk values specified in your library dependencies and build files.

Some libraries also specify a minimum Kotlin version for use. Update the version of Kotlin in your build files to be at least the specified version.

Gradle

Sometimes new versions of Gradle deprecate existing APIs, removing those APIs in a future release. If you develop a Gradle plugin, upgrade your plugin as soon as possible, especially if that plugin is public.

Some Gradle upgrades require locating new versions of plugins that you use. Note that these plugins can lag in their development as they upgrade the plugins to match the latest Gradle plugin APIs.

To upgrade Gradle:

  • Read the release notes for the version you want to use.
  • Upgrade the Gradle version in gradle/wrapper/gradle-wrapper.properties.
  • Upgrade the Gradle wrapper jar and scripts by running ./gradlew wrapper --gradle-version latest.
  • Upgrade your Gradle plugins.
  • Upgrade the JDK used to run Gradle.

Gradle plugins

Upgraded Gradle plugins sometimes use new or changed Gradle APIs, which in turn require a Gradle upgrade or possibly changes to their configuration in your build files. In either case, you'll see build warnings or errors to indicate incompatibility.

Whenever upgrading plugins, upgrade Gradle.

Android SDK

Android Studio includes an Android SDK upgrade assistant that can help with these tasks.

If you use the assistant or perform the upgrade manually, consider the following:

Each release of the Android SDK contains new features and APIs, bug fixes and behavior changes. The Play Store requires updating your targetSdk, but consider updating targetSdk earlier than the deadlines to allow more time to make any necessary changes.

Before upgrading the Android SDK, carefully read the release notes. Pay close attention to the behavior changes section, which includes:

  • New permissions that you'll need to request at install or runtime.
  • Deprecated APIs and their replacements.
  • Breaking changes in APIs or behavior.
  • New Kotlin or Java APIs, which may impact your code.

The behavior changes section can be quite long, but pay close attention as it often contains critical changes you need to make to your application.

You must upgrade targetSdk to meet Play Store requirements. Upgrading compileSdk is optional, giving you access to new APIs. Note that some libraries, like AndroidX, include a minimum compileSdk requirement.

To take advantage of new SDK features during development and ensure compatibility during your build, upgrade the Android Gradle plugin (AGP) and Android Studio. These include new and improved tools for new SDKs. See Minimum versions of tools for Android API level.

When upgrading the Android SDK, upgrade any AndroidX libraries that you use. AndroidX often uses new and updated APIs for better compatibility and performance across Android SDK versions.

Android Studio

You can generally upgrade Android Studio at any time. You might see messages prompting you to upgrade AGP or upgrade the Android SDK. These upgrades are strongly recommended but not required.

If you later want to use Android Studio to upgrade AGP or the Android SDK, you can find these options on the Tools menu:

Java

If you have Java source code in your Android application, you may want to take advantage of newer Java APIs.

Each Android SDK version supports a subset of Java APIs and language features. AGP provides compatibility for lower Android SDK versions using a process called desugaring.

Android SDK release notes specify which Java level is supported and potential issues. Some of these issues may impact Kotlin source code as well, because Kotlin has access to the same Java APIs. Be sure to pay close attention to JDK API sections that appear in the behavioral changes section of the release notes, even if you have no Java source code.

JDK usage is specified in several places in your build scripts. See Java versions in Android build for more information.

Upgrade analysis

Upgrading a dependency can introduce risks in the form of API and behavior changes, new requirements for use, new security issues, or even license changes. For example, do you need to:

  • Change code for API changes?
  • Add new permission checks?
  • Create additional tests or modify existing tests for behavior changes?

Consider that the dependency you've upgraded has upgraded the versions of its dependencies. This can quickly spider into a massive set of changes.

If you use a tool such as Renovate or Dependabot to automate your upgrades, be aware that they don't do any analysis for you; they upgrade to the latest library versions. Don't assume that everything will work properly after these types of automatic upgrades.

The key to successful upgrades is upgrade analysis:

  1. Determine dependency differences from before and after your upgrades.
  2. Examine each change and determine the risks involved.
  3. Mitigate risks, or accept or reject changes.

Determine dependency differences

The first step in your upgrade analysis is to determine how your dependencies change. Take advantage of version control (VCS, such as Git) and the Dependency Guard plugin to quickly see changes. Your goal is to create a before and after snapshot and compare them.

Set up and create your first baseline

Before starting your upgrade, make sure your project builds successfully.

Ideally, resolve as many warnings as possible, or create baselines to track which warnings you've already seen.

These warning baselines make it easier to see new warnings introduced as you upgrade your dependencies.

Create a dependency baseline by setting up and running Dependency Guard. In your gradle/libs.versions.toml version catalog, add:

[versions]
dependencyGuard = "0.5.0"

[plugins]
dependency-guard = { id = "com.dropbox.dependency-guard", version.ref = "dependencyGuard" }

And add the following to your app's build file:

Kotlin

plugins {
    alias(libs.plugins.dependency.guard)
}

dependencyGuard {
    configuration("releaseRuntimeClasspath")
}

Groovy

plugins {
    alias(libs.plugins.dependency.guard)
}

dependencyGuard {
    configuration('releaseRuntimeClasspath')
}

The releaseRuntimeClasspath configuration is a likely target, but if you want to use a different configuration, run ./gradlew dependencyGuard without a listed configuration in your build file to see all available configurations.

After setup, run ./gradlew dependencyGuard to generate a report in app/dependencies/releaseRuntimeClasspath.txt. This is your baseline report. Commit this to your version control system (VCS) to save it.

Keep in mind that Dependency Guard only captures the list of library dependencies. There are other dependencies in your build files, like the Android SDK and JDK versions. Committing to your VCS before your dependency changes allows your VCS diff to highlight those changes as well.

Upgrade and compare against your baseline

Once you have a baseline, upgrade dependencies and other build changes you wanted to test. Don't upgrade your source code or resources at this point.

Run ./gradlew lint to see new lint warnings or errors. Address any important issues and then update your warning baseline by running ./gradlew lint -Dlint.baselines.continue=true. If you have used other tools to capture warning baselines, such as Kotlin Warning Baseline or Kotlin Warnings Baseline Generator, address new warnings and update their baselines as well.

Run ./gradlew dependencyGuard to update your baseline report. Then run your VCS diff to see nonlibrary changes. It's likely to include many more library upgrades than you thought.

Analyze risks

Once you know what has changed, consider the possible risks of each upgraded library. This helps focus your testing or deeper investigation of changes. Define a set of risks to analyze for your project to ensure consistent analysis.

Some considerations:

Major version bumps

Did the major version number change?

In semantic versioning, the first number is known as the major version. For example, if the version of a library upgraded from 1.2.3 to 2.0.1, the major version has changed. This is typically an indication that the library developer has made incompatible changes between the versions, such as removing or changing parts of the API.

When you see this, pay extra attention to the affected libraries when looking at any of the following considerations.

If your code uses any experimental APIs (which often require you to opt-in using annotations or build-file specifications), even minor or patch version changes, such as upgrading from 1.2.3 to 1.3.1 or 1.2.3 to 1.2.5, might present additional risks.

Nonstable API

Some library releases may include nonstable APIs. These are usually APIs that are works in progress or depend on another unstable API.

While typically limited to previews, such as alpha, dev, or experimental releases, some libraries include APIs marked experimental or unstable.

If possible, avoid such APIs. If you need to use them, be sure to record your usage and watch for changes or removals in later releases.

Dynamic behavior

Some libraries behave differently based on external factors. For example, a library that communicates with a server depends on changes in that server.

  • Does the library need to match a certain server version?
  • Can the library connect to different versions of a server?
  • Does some other external factor affect proper behavior of the library?

Manifest merging

Libraries published as Android Archives (AARs) can contain resources and manifests that are merged into your application. These can add new permissions and Android components, such as activities or broadcast receivers, that run indirectly.

Runtime updates

Some libraries use features that can be updated outside your application's control. A library might use Play Services, which is upgraded independently of the Android SDK. Other libraries may bind to services in independently updated external applications (often using AIDL).

How many versions are you skipping?

The longer you wait to upgrade a library, the more potential risks. If you see a version changing significantly, such as 1.2.3 to 1.34.5, pay extra attention to this library.

Migration guides

Check whether the library has a migration guide. This can significantly reduce your risk analysis and mitigation planning.

Note that the presence of such a guide is a good indicator that the developer has focused on compatibility and considered your upgrade mitigation.

Release notes

See the release notes (if provided) for each changed library. Look for indications of breaking changes or new requirements, such as added permissions.

READMEs

Some README files for a library note potential risks, especially if the library doesn't provide release notes. Look for _known issues_, especially known security concerns.

Check known vulnerabilities

The Play SDK Index tracks vulnerabilities for many popular SDKs. The Play Console reports whether you use one of the listed SDKs with known vulnerabilities. When editing build files in Android Studio, the IDE checks the SDK index and flags use of vulnerable library versions.

The National Institute of Standards and Technology (NIST) maintains a large National Vulnerability Database (NVD). The Dependency Check Gradle plugin checks your used dependencies against the NVD.

To use Dependency Check, request an NVD API key, set up the Gradle plugin, and run ./gradlew dependencyCheckAnalyze. Note that this can take a long time to run.

Version conflicts

Are versions resolving as expected? Look for conflicts, especially major version differences. See Gradle dependency resolution for details on how you can look for conflicts. In particular, search for -> in the ./gradlew app:dependencies report.

When possible, work with the authors of a dependency to deconflict their dependencies. If your company allows, contribute changes to the library (upstreaming) to help improve the library's compatibility.

Check licenses

Look for changes in licenses when upgrading a library. The library itself could change to a license that's no longer compatible with your application or library. New transitive dependencies could also introduce incompatible licenses. See Validate licenses for details on checking the current set of licenses across your dependencies.

Maintenance and
quality risks

For libraries with public repositories:

  • How many contributors are maintaining the library?
  • When was the last upgrade, and how often does the library change?
  • What does the issue backlog (if available) look like? Skim it to get a feeling for potential problems and the technical debt of the library.
  • How well do unit tests cover the library?
  • Are there known anti-patterns in the codebase?
  • Is the library well documented?
  • Are there many _fixme_ comments in the codebase?

Open versus closed source

If a library is open source, it'll be easier to debug issues than with closed source, whether the issues are in your code or the library code.

Minimize closed source dependencies and apply additional scrutiny during their evaluation. Are there good alternatives that fit your use case? What service-level agreements are available for closed source libraries? If you choose to use a closed source dependency, be prepared to write additional test cases to help limit risks.

Run a build

Build your project. Look for new errors or warnings. If you can identify which library is causing them, note that as a risk for upgrading that library.

If you see any new depreciation warnings, add those as specific risks for the library producing them. These can be removed in later releases. If you want to continue using that library, dedicate time to convert from using deprecated APIs to their replacements, or note the deprecations to keep an eye on those functions and whether they are later removed.

Use lint to detect API issues

Android lint can catch many issues in your application, including some that are the result of changing versions of dependencies or the Android SDK. For example, if you upgrade your compileSdk and use its new APIs, lint reports those that aren't available in prior SDK versions.

Lint runs in the Android Studio editor, reporting issues as you make changes. But it's not normally run as part of your build in Studio or when you run a command-line build unless you use the build or lint targets.

If you use Continuous Integration (CI), run gradlew build or gradlew lint during your CI builds (or at least on your nightly builds) to catch these types of errors.

If you don't use CI, be sure to at least occasionally run gradlew lint.

Pay particular attention to lint errors and warnings. Some libraries are shipped with their own lint checks, helping ensure proper use of their API. Some new versions of a library include new lint warnings and errors, resulting in new reports when you build.

Mitigate risks

After determining upgrade risks, decide how you want to mitigate them:

  • Accept some risks as is. Some risks are low enough to be acceptable, especially when upgrade time and resources are limited.
  • Reject some risks outright. Some upgrades might feel too risky, especially if you have limited time or resources to mitigate them at this point. If you need to triage, focus on upgrades that are necessary for bugs you have encountered or new features you need.
  • Mitigate remaining risks
    • Consider batching your upgrades into smaller, independent sets of changes. This reduces overall risk and allows partial rollback.
    • Investigate the changes in detail.
    • Test your app to check for unexpected changes. Add new tests where needed to build confidence in the upgrade.
    • Look at the source (if available) when something questionable is found.
    • Make required changes in your source or build.

Document your decisions. If risks from an upgrade become issues when running your application, documentation of your risk analysis can reduce the necessary error analysis.

Validate licenses

Library developers license the libraries for your use. You are required to adhere to the terms of the license or you cannot use the library. Some licenses are very permissive, often requiring only attribution of the library and surfacing the text of its license to end users. Some are considered viral; if you use those libraries, you must apply the same license to your application or library.

Licenses can change with any release. Whenever you upgrade, you should verify that the dependencies you're using are licensed in a compatible way with your application or library.

If a license is not compatible (or has changed to be no longer compatible), you cannot use that version of the library. You can:

  • Reach out to the library owner and request continuation of the existing license or dual licensing to keep allowing the old license.
  • Work with your legal team to determine if you can change your license to be compatible.
  • Find another library with a compatible license and modify your application as needed.
  • Fork the last compatible version of the library (if that license allows derivatives and the changes are not retroactive) and make your own changes.