How to use Dependabot with Gradle’s Version Catalog

Vladyslav H.
5 min readMay 9, 2023

--

Dependabot is a tool that helps developers keep their software up to date by automatically checking for and updating any outdated or vulnerable dependencies in their code. This is important because outdated dependencies can create security vulnerabilities and other issues that can cause problems for both the developer and the end user. By using Dependabot, developers can save time and ensure their code is secure and up to date, which can lead to a more reliable and stable application.

Create a version catalog

To use Dependabot, you need to create a xyz.versions.toml file in your project's root gradle folder. This file could contain up to four different blocks: [versions], [libraries], [bundles]and [plugins].

Version catalog

The [versions] block contains all versions that are shared between several libraries, allowing you to update all dependent libraries at once.

The [libraries] block contains the actual library names with their corresponding versions. You can create them differently depending on what you want to achieve.

If you want to share a version among several libraries, you can do so by defining a version reference like this:

[versions]
retrofitVersion = "2.9.0"

[libraries]
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofitVersion" }
retrofit-converterMoshi = { group = "com.squareup.retrofit2", name = "converter-moshi", version.ref = "retrofitVersion" }
retrofit-adapterRxjava2 = { group = "com.squareup.retrofit2", name = "adapter-rxjava2", version.ref = "retrofitVersion" }

If you don’t have any version to share, you can simply define each library’s version as follows:

rxjava = "io.reactivex.rxjava2:rxjava:2.2.21"
rxjava-android = "io.reactivex.rxjava2:rxandroid:2.1.1"
rxjava-kotlin = "io.reactivex.rxjava2:rxkotlin:2.4.0"

You can make dependency management in your build.gradle files by organizing related dependencies into containers using dashes, which can be transformed into dots.

implementation(libs.androidx.fragmentKtx)
implementation(libs.rxjava.kotlin)

implementation(libs.dagger)
kapt(libs.dagger.processor)

testImplementation(libs.testing.assertj)
testImplementation(libs.testing.coroutines)
testImplementation(libs.testing.junit)
testImplementation(libs.testing.mockito)
testImplementation(libs.testing.mockito.kotlin)
testImplementation(libs.testing.strikt)
testImplementation(libs.testing.turbine)

Let’s take a look at another example of using dependabot with androidx container dependencies:

androidx-appCompat = "androidx.appcompat:appcompat:1.6.1"
androidx-constraintLayout = "androidx.constraintlayout:constraintlayout:2.1.4"
androidx-cardview = "androidx.cardview:cardview:1.0.0"
androidx-recyclerview = "androidx.recyclerview:recyclerview:1.3.0"
androidx-legacySupport = "androidx.legacy:legacy-support-v4:1.0.0"
androidx-legacySupport-coreUtils = "androidx.legacy:legacy-support-core-utils:1.0.0"
androidx-swipeRefreshLayout = "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
androidx-drawerLayout = "androidx.drawerlayout:drawerlayout:1.2.0"
androidx-coreKtx = "androidx.core:core-ktx:1.9.0"
androidx-fragmentKtx = "androidx.fragment:fragment-ktx:1.5.7"
androidx-crypto = "androidx.security:security-crypto:1.0.0"
androidx-multidex = "androidx.multidex:multidex:2.0.1"
androidx-splashscreen = "androidx.core:core-splashscreen:1.0.1"

As a team, we decided not to use additional dashes in our naming convention. Instead of having androidx-fragment-ktx, we use androidx-fragmentKtx. You can choose whatever naming convention your team prefers, but this style works well for us.

implementation(libs.androidx.fragmentKtx)
implementation(libs.androidx.lifecycle.viewmodelKtx)

Another useful feature of TOML is the [bundles] section, which allows you to group together multiple libraries or dependencies that are known to be compatible with each other. This can be particularly helpful in situations where upgrading one library may require upgrading another library to a higher version, and it's not immediately clear which versions are compatible.

While our team generally prefers to manage each dependency version individually using variables, the [bundles] section can be helpful in certain situations.

Here’s an example of how you might use [bundles] to group together the mockito-core and mockito-kotlin libraries:

[versions]
testing-mockito-core = "org.mockito:mockito-core:5.3.1"
testing-mockito-kotlin = "org.mockito.kotlin:mockito-kotlin:4.1.0"

[bundles]
testing-mockito = ["testing-mockito-core", "testing-mockito-kotlin"]

Note that this example is somewhat contrived, but it illustrates the basic syntax and usage of the [bundles] section.

Create a config file for Dependabot

In order to use Dependabot, you need to provide it with information on how often it should check your branch and create pull requests for you. To do this, you can create a configuration file in two ways. You can either create a file called .github/dependabot.yml and commit it to your repository, or you can create it via the GitHub interface.

Dependabot config file

Here is an example of the Dependabot configuration file we use:

version: 2
updates:
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "weekly"
rebase-strategy: "disabled"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
rebase-strategy: "disabled"

The directory specified in the configuration defines the location of the Gradle project, allowing it to find the libs.versions.toml file in the ./gradle directory, how often to check for updates (weekly), and whether or not to perform automatic rebasing (which we have turned off). This is because each pull request rebase/merge runs all checks again, which can be costly with a CI provider.

You can also specify the exact time when Dependabot should check for updates using the schedule.time and schedule.timezone options. For more configuration options, check out the Dependabot documentation.

Where are the settings for Dependabot located?

Once you’ve pushed to your main branch, which is develop in our case, Dependabot should be automatically activated. To confirm this, navigate to Settings/Code Security and Analysis in your GitHub project.

Dependabot settings

First Dependabot PR was created

Congratulations! We have reached the final step of the Dependabot journey. At this point, Dependabot will have created a pull request with updated versions for a dependency that you may have forgotten to update.

Dependabot is alive

The pull request generated by Dependabot contains a wealth of useful information that you would otherwise have to manually search for. It includes release notes and commits. Before adopting Dependabot, our workflow involved using a custom Gradle plugin for version catalog checks and DependencyBumper to generate pull requests for updating dependencies. We maintained a versions.gradle file in the project's root directory, which served as a centralized location for managing all dependencies.

deps.retrofit = "com.squareup.retrofit2:retrofit:2.9.0"
deps.retrofitMoshi = "com.squareup.retrofit2:converter-moshi:2.9.0"
deps.retrofitRxAdapter = "com.squareup.retrofit2:adapter-rxjava2:2.9.0"

In the end of the day, we still had to search for information ourselves and include it in pull request comments, which took additional time away from our daily work. Thanks to Dependabot, this process is now automated and streamlined, saving us valuable time and effort.

Dagger bump update

Bonus

Additionally, I’d like to share some useful commands you can use in the comment section to tell Dependabot what to do. While there are many commands available online, I’ll share the most popular ones we use:

  • @dependabot rebase: Rebase the PR.
  • @dependabot merge: Merge the pull after CI tests.
  • @dependabot squash and merge: Squash and merge after CI tests.
  • @dependabot close: Close the PR, and Dependabot won't recreate the same PR.

Thank you for taking the time to read this article! I hope you found it informative and helpful.

--

--