diff --git a/docs/usage/java.md b/docs/usage/java.md
index e58dbe408117b24f98c7ba75bbeffa9e234dbef2..aa8c51072dc721abbcb7389c89591def53acf834 100644
--- a/docs/usage/java.md
+++ b/docs/usage/java.md
@@ -16,12 +16,18 @@ Renovate detects versions that are specified in a string `'group:artifact:versio
 
 Renovate can update `build.gradle`/`build.gradle.kts` files in the root of the repository.
 It also updates any `*.gradle`/`*.gradle.kts` files in a subdirectory as multi-project configurations.
+Renovate also tries to find updates for dependencies whose version is defined in a `*.properties` file, and scans for `*.versions.toml` files and for `*.toml` files inside the `gradle` folder to keep [catalogs](https://docs.gradle.org/current/userguide/platforms.html) up to date.
 
 Renovate does not support:
 
 - Projects which do not have either a `build.gradle` or `build.gradle.kts` in the repository root
 - Android projects that require extra configuration to run (e.g. setting the Android SDK)
-- Gradle versions prior to version 5.0.
+- Gradle versions older than version 5.0
+- Catalogs defined inside a `build.gradle` or `build.gradle.kts` file rather than in TOML
+- Catalogs with version ranges
+- Catalogs using the `required`, `strictly`, `preferred`, `reject`, and `rejectAll` version declarations
+- Catalogs with custom names that do not end in `.toml`
+- Catalogs outside the `gradle` folder whose names do not end in `.versions.toml`
 
 ## Gradle Wrapper
 
diff --git a/lib/manager/gradle/index.ts b/lib/manager/gradle/index.ts
index 086aab095fdef340987f7f217c1b2b72d0c27eb4..087bf18a9a37ea208adf47f8ca4964189cd613f7 100644
--- a/lib/manager/gradle/index.ts
+++ b/lib/manager/gradle/index.ts
@@ -29,7 +29,12 @@ export function updateDependency(
 export const language = LANGUAGE_JAVA;
 
 export const defaultConfig = {
-  fileMatch: ['\\.gradle(\\.kts)?$', '(^|/)gradle.properties$'],
+  fileMatch: [
+    '\\.gradle(\\.kts)?$',
+    '(^|/)gradle.properties$',
+    '(^|\\/)gradle\\/.+\\.toml$',
+    '\\.versions\\.toml$',
+  ],
   timeout: 600,
   versioning: gradleVersioning.id,
 };
diff --git a/lib/manager/gradle/shallow/__fixtures__/1/libs.versions.toml b/lib/manager/gradle/shallow/__fixtures__/1/libs.versions.toml
new file mode 100644
index 0000000000000000000000000000000000000000..60a452bda6ada9e159b7dba7aedb53511ee9a722
--- /dev/null
+++ b/lib/manager/gradle/shallow/__fixtures__/1/libs.versions.toml
@@ -0,0 +1,17 @@
+[versions]
+detekt = "1.17.0"
+kotest = "4.6.0"
+publish-on-central = "0.5.0"
+
+[libraries]
+detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
+kotest-assertions-core-jvm = { module = "io.kotest:kotest-assertions-core-jvm", version.ref = "kotest" }
+kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
+mockito = { group = "org.mockito", name = "mockito-core", version = "3.10.0" }
+
+[bundles]
+kotest = [ "kotest-runner-junit5", "kotest-assertions-core-jvm" ]
+
+[plugins]
+detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
+publish-on-central = { id = "org.danilopianini.publish-on-central", version.ref = "publish-on-central" }
diff --git a/lib/manager/gradle/shallow/__fixtures__/2/libs.versions.toml b/lib/manager/gradle/shallow/__fixtures__/2/libs.versions.toml
new file mode 100644
index 0000000000000000000000000000000000000000..8308a0c81674fa228b7124ee93f5086ed25eaef6
--- /dev/null
+++ b/lib/manager/gradle/shallow/__fixtures__/2/libs.versions.toml
@@ -0,0 +1,13 @@
+[versions]
+kotlin = "1.5.21"
+retrofit = "2.8.2"
+
+[libraries]
+okHttp = "com.squareup.okhttp3:okhttp:4.9.0"
+okio = { module = "com.squareup.okio:okio", version = "2.8.0" }
+picasso = { group = "com.squareup.picasso", name = "picasso", version = "2.5.1" }
+retrofit2-retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
+
+[plugins]
+kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version = "1.5.21" }
+kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
diff --git a/lib/manager/gradle/shallow/extract.spec.ts b/lib/manager/gradle/shallow/extract.spec.ts
index 1456652cd4e7556c0b086e77a8ea7d46ebf6dd8d..55f36525df3f7ded915fb5e97f2bfb93a27a37a9 100644
--- a/lib/manager/gradle/shallow/extract.spec.ts
+++ b/lib/manager/gradle/shallow/extract.spec.ts
@@ -1,5 +1,5 @@
 import { extractAllPackageFiles } from '..';
-import { fs } from '../../../../test/util';
+import { fs, loadFixture } from '../../../../test/util';
 
 jest.mock('../../../util/fs');
 
@@ -136,4 +136,182 @@ describe('manager/gradle/shallow/extract', () => {
       },
     ]);
   });
+
+  it('works with dependency catalogs', async () => {
+    const tomlFile = loadFixture('1/libs.versions.toml');
+    const fsMock = {
+      'gradle/libs.versions.toml': tomlFile,
+    };
+    mockFs(fsMock);
+    const res = await extractAllPackageFiles({} as never, Object.keys(fsMock));
+    expect(res).toMatchObject([
+      {
+        packageFile: 'gradle/libs.versions.toml',
+        deps: [
+          {
+            depName: 'io.gitlab.arturbosch.detekt:detekt-formatting',
+            groupName: 'io.gitlab.arturbosch.detekt',
+            currentValue: '1.17.0',
+            managerData: {
+              fileReplacePosition: 21,
+              packageFile: 'gradle/libs.versions.toml',
+            },
+          },
+          {
+            depName: 'io.kotest:kotest-assertions-core-jvm',
+            groupName: 'io.kotest',
+            currentValue: '4.6.0',
+            managerData: {
+              fileReplacePosition: 39,
+              packageFile: 'gradle/libs.versions.toml',
+            },
+          },
+          {
+            depName: 'io.kotest:kotest-runner-junit5',
+            groupName: 'io.kotest',
+            currentValue: '4.6.0',
+            managerData: {
+              fileReplacePosition: 39,
+              packageFile: 'gradle/libs.versions.toml',
+            },
+          },
+          {
+            depName: 'org.mockito:mockito-core',
+            groupName: 'org.mockito',
+            currentValue: '3.10.0',
+            managerData: {
+              fileReplacePosition: 460,
+              packageFile: 'gradle/libs.versions.toml',
+            },
+          },
+          {
+            depName: 'io.gitlab.arturbosch.detekt',
+            depType: 'plugin',
+            currentValue: '1.17.0',
+            commitMessageTopic: 'plugin detekt',
+            lookupName:
+              'io.gitlab.arturbosch.detekt:io.gitlab.arturbosch.detekt.gradle.plugin',
+            managerData: {
+              fileReplacePosition: 21,
+              packageFile: 'gradle/libs.versions.toml',
+            },
+            registryUrls: [
+              'https://repo.maven.apache.org/maven2',
+              'https://plugins.gradle.org/m2/',
+            ],
+          },
+          {
+            depName: 'org.danilopianini.publish-on-central',
+            depType: 'plugin',
+            currentValue: '0.5.0',
+            commitMessageTopic: 'plugin publish-on-central',
+            lookupName:
+              'org.danilopianini.publish-on-central:org.danilopianini.publish-on-central.gradle.plugin',
+            managerData: {
+              fileReplacePosition: 68,
+              packageFile: 'gradle/libs.versions.toml',
+            },
+            registryUrls: [
+              'https://repo.maven.apache.org/maven2',
+              'https://plugins.gradle.org/m2/',
+            ],
+          },
+        ],
+      },
+    ]);
+  });
+
+  it("can run Javier's example", async () => {
+    const tomlFile = loadFixture('2/libs.versions.toml');
+    const fsMock = {
+      'gradle/libs.versions.toml': tomlFile,
+    };
+    mockFs(fsMock);
+    const res = await extractAllPackageFiles({} as never, Object.keys(fsMock));
+    expect(res).toMatchObject([
+      {
+        packageFile: 'gradle/libs.versions.toml',
+        deps: [
+          {
+            depName: 'com.squareup.okhttp3:okhttp',
+            groupName: 'com.squareup.okhttp3',
+            currentValue: '4.9.0',
+            managerData: {
+              fileReplacePosition: 99,
+              packageFile: 'gradle/libs.versions.toml',
+            },
+          },
+          {
+            depName: 'com.squareup.okio:okio',
+            groupName: 'com.squareup.okio',
+            currentValue: '2.8.0',
+            managerData: {
+              fileReplacePosition: 161,
+              packageFile: 'gradle/libs.versions.toml',
+            },
+          },
+          {
+            depName: 'com.squareup.picasso:picasso',
+            groupName: 'com.squareup.picasso',
+            currentValue: '2.5.1',
+            managerData: {
+              fileReplacePosition: 243,
+              packageFile: 'gradle/libs.versions.toml',
+            },
+          },
+          {
+            depName: 'com.squareup.retrofit2:retrofit',
+            groupName: 'com.squareup.retrofit2',
+            currentValue: '2.8.2',
+            managerData: {
+              fileReplacePosition: 41,
+              packageFile: 'gradle/libs.versions.toml',
+            },
+          },
+          {
+            depName: 'org.jetbrains.kotlin.jvm',
+            depType: 'plugin',
+            currentValue: '1.5.21',
+            commitMessageTopic: 'plugin kotlinJvm',
+            lookupName:
+              'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin',
+            managerData: {
+              fileReplacePosition: 415,
+              packageFile: 'gradle/libs.versions.toml',
+            },
+            registryUrls: [
+              'https://repo.maven.apache.org/maven2',
+              'https://plugins.gradle.org/m2/',
+            ],
+          },
+          {
+            depName: 'org.jetbrains.kotlin.plugin.serialization',
+            depType: 'plugin',
+            currentValue: '1.5.21',
+            commitMessageTopic: 'plugin kotlinSerialization',
+            lookupName:
+              'org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin',
+            managerData: {
+              fileReplacePosition: 21,
+              packageFile: 'gradle/libs.versions.toml',
+            },
+            registryUrls: [
+              'https://repo.maven.apache.org/maven2',
+              'https://plugins.gradle.org/m2/',
+            ],
+          },
+        ],
+      },
+    ]);
+  });
+
+  it('ignores an empty TOML', async () => {
+    const tomlFile = '';
+    const fsMock = {
+      'gradle/libs.versions.toml': tomlFile,
+    };
+    mockFs(fsMock);
+    const res = await extractAllPackageFiles({} as never, Object.keys(fsMock));
+    expect(res).toBeNull();
+  });
 });
diff --git a/lib/manager/gradle/shallow/extract.ts b/lib/manager/gradle/shallow/extract.ts
index 7dabe5e1ee59d1dcee4a1bca5c7a63c1271087de..cd9a3161f800a5234a13dc6d8b59e3f410aeb249 100644
--- a/lib/manager/gradle/shallow/extract.ts
+++ b/lib/manager/gradle/shallow/extract.ts
@@ -11,12 +11,14 @@ import type {
   PackageFile,
 } from '../../types';
 import type { GradleManagerData } from '../types';
+import { parseCatalog } from './extract/catalog';
 import { parseGradle, parseProps } from './parser';
 import type { PackageVariables, VariableRegistry } from './types';
 import {
   getVars,
   isGradleFile,
   isPropsFile,
+  isTOMLFile,
   reorderFiles,
   toAbsolutePath,
 } from './utils';
@@ -64,6 +66,13 @@ export async function extractAllPackageFiles(
         const { vars, deps } = parseProps(content, packageFile);
         updateVars(vars);
         extractedDeps.push(...deps);
+      } else if (isTOMLFile(packageFile)) {
+        try {
+          const updatesFromCatalog = parseCatalog(packageFile, content);
+          extractedDeps.push(...updatesFromCatalog);
+        } catch (error) {
+          logger.warn({ error }, 'TOML parsing error');
+        }
       } else if (isGradleFile(packageFile)) {
         const vars = getVars(registry, dir);
         const {
diff --git a/lib/manager/gradle/shallow/extract/catalog.ts b/lib/manager/gradle/shallow/extract/catalog.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0812932b95731cbe056b8b12f59ae38c97ccf143
--- /dev/null
+++ b/lib/manager/gradle/shallow/extract/catalog.ts
@@ -0,0 +1,83 @@
+import { parse } from '@iarna/toml';
+import { PackageDependency } from '../../../types';
+import { GradleManagerData } from '../../types';
+import type { GradleCatalog, GradleCatalogPluginDescriptor } from '../types';
+
+function findIndexAfter(
+  content: string,
+  sliceAfter: string,
+  find: string
+): number {
+  const slicePoint = content.indexOf(sliceAfter) + sliceAfter.length;
+  return slicePoint + content.slice(slicePoint).indexOf(find);
+}
+
+export function parseCatalog(
+  packageFile: string,
+  content: string
+): PackageDependency<GradleManagerData>[] {
+  const tomlContent = parse(content) as GradleCatalog;
+  const versions = tomlContent.versions || {};
+  const libs = tomlContent.libraries || {};
+  const libStartIndex = content.indexOf('libraries');
+  const libSubContent = content.slice(libStartIndex);
+  const versionStartIndex = content.indexOf('versions');
+  const versionSubContent = content.slice(versionStartIndex);
+  const extractedDeps: PackageDependency<GradleManagerData>[] = [];
+  for (const libraryName of Object.keys(libs)) {
+    const libDescriptor = libs[libraryName];
+    const group: string =
+      typeof libDescriptor === 'string'
+        ? libDescriptor.split(':')[0]
+        : libDescriptor.group || libDescriptor.module?.split(':')[0];
+    const name: string =
+      typeof libDescriptor === 'string'
+        ? libDescriptor.split(':')[1]
+        : libDescriptor.name || libDescriptor.module?.split(':')[1];
+    const version = libDescriptor.version || libDescriptor.split(':')[2];
+    const currentVersion =
+      typeof version === 'string' ? version : versions[version.ref];
+    const fileReplacePosition =
+      typeof version === 'string'
+        ? libStartIndex +
+          findIndexAfter(libSubContent, libraryName, currentVersion)
+        : versionStartIndex +
+          findIndexAfter(versionSubContent, version.ref, currentVersion);
+    const dependency = {
+      depName: `${group}:${name}`,
+      groupName: group,
+      currentValue: currentVersion,
+      managerData: { fileReplacePosition, packageFile },
+    };
+    extractedDeps.push(dependency);
+  }
+  const plugins = tomlContent.plugins || {};
+  const pluginsStartIndex = content.indexOf('[plugins]');
+  const pluginsSubContent = content.slice(pluginsStartIndex);
+  for (const pluginName of Object.keys(plugins)) {
+    const pluginDescriptor = plugins[
+      pluginName
+    ] as GradleCatalogPluginDescriptor;
+    const pluginId = pluginDescriptor.id;
+    const version = pluginDescriptor.version;
+    const currentVersion: string =
+      typeof version === 'string' ? version : versions[version.ref];
+    const fileReplacePosition =
+      typeof version === 'string'
+        ? pluginsStartIndex +
+          findIndexAfter(pluginsSubContent, pluginId, currentVersion)
+        : versionStartIndex +
+          findIndexAfter(versionSubContent, version.ref, currentVersion);
+    const dependency = {
+      depType: 'plugin',
+      depName: pluginId,
+      lookupName: `${pluginId}:${pluginId}.gradle.plugin`,
+      registryUrls: ['https://plugins.gradle.org/m2/'],
+      currentValue: currentVersion,
+      commitMessageTopic: `plugin ${pluginName}`,
+      managerData: { fileReplacePosition, packageFile },
+    };
+    extractedDeps.push(dependency);
+  }
+  return extractedDeps;
+}
diff --git a/lib/manager/gradle/shallow/types.ts b/lib/manager/gradle/shallow/types.ts
index 4d71036dbbba370ff81b59291917c13ddd9a9c2b..a1867f968ceaf0e4059fc898cb034ff658e54498 100644
--- a/lib/manager/gradle/shallow/types.ts
+++ b/lib/manager/gradle/shallow/types.ts
@@ -61,3 +61,32 @@ export interface ParseGradleResult {
   urls: string[];
   vars: PackageVariables;
 }
+
+export interface GradleCatalog {
+  versions?: Map<string, string>;
+  libraries?: Map<
+    string,
+    GradleCatalogModuleDescriptor | GradleCatalogArtifactDescriptor | string
+  >;
+  plugins?: Map<string, GradleCatalogPluginDescriptor>;
+}
+
+export interface GradleCatalogModuleDescriptor {
+  module: string;
+  version: string | VersionPointer;
+}
+
+export interface GradleCatalogArtifactDescriptor {
+  name: string;
+  group: string;
+  version: string | VersionPointer;
+}
+
+export interface GradleCatalogPluginDescriptor {
+  id: string;
+  version: string | VersionPointer;
+}
+
+export interface VersionPointer {
+  ref: string;
+}
diff --git a/lib/manager/gradle/shallow/utils.ts b/lib/manager/gradle/shallow/utils.ts
index 2d8080ef1a43678b304a671c1ab7784b4a1263c7..75bf22405d4c56908222db936b9090f3dd5f935c 100644
--- a/lib/manager/gradle/shallow/utils.ts
+++ b/lib/manager/gradle/shallow/utils.ts
@@ -81,6 +81,11 @@ export function isPropsFile(path: string): boolean {
   return filename === 'gradle.properties';
 }
 
+export function isTOMLFile(path: string): boolean {
+  const filename = upath.basename(path).toLowerCase();
+  return filename.endsWith('.toml');
+}
+
 export function toAbsolutePath(packageFile: string): string {
   return upath.join(packageFile.replace(/^[/\\]*/, '/'));
 }