diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts
index 06939c884c2ca85e9dc44d40c783b71fb000e5e0..e0d77944dfdb79adcd158ebfff1d449712d8e061 100644
--- a/lib/modules/manager/api.ts
+++ b/lib/modules/manager/api.ts
@@ -47,6 +47,7 @@ import * as kubernetes from './kubernetes';
 import * as kustomize from './kustomize';
 import * as leiningen from './leiningen';
 import * as maven from './maven';
+import * as mavenWrapper from './maven-wrapper';
 import * as meteor from './meteor';
 import * as mint from './mint';
 import * as mix from './mix';
@@ -133,6 +134,7 @@ api.set('kubernetes', kubernetes);
 api.set('kustomize', kustomize);
 api.set('leiningen', leiningen);
 api.set('maven', maven);
+api.set('maven-wrapper', mavenWrapper);
 api.set('meteor', meteor);
 api.set('mint', mint);
 api.set('mix', mix);
diff --git a/lib/modules/manager/maven-wrapper/artifacts.spec.ts b/lib/modules/manager/maven-wrapper/artifacts.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb340197f27b1fb20ea8f92b701f75f677562180
--- /dev/null
+++ b/lib/modules/manager/maven-wrapper/artifacts.spec.ts
@@ -0,0 +1,311 @@
+import type { Stats } from 'fs';
+import os from 'os';
+import type { StatusResult } from 'simple-git';
+import { join } from 'upath';
+import { envMock, mockExecAll } from '../../../../test/exec-util';
+import { env, fs, git, mockedFunction, partial } from '../../../../test/util';
+import { GlobalConfig } from '../../../config/global';
+import { resetPrefetchedImages } from '../../../util/exec/docker';
+import { getPkgReleases } from '../../datasource';
+import { updateArtifacts } from '.';
+
+jest.mock('../../../util/fs');
+jest.mock('../../../util/git');
+jest.spyOn(os, 'platform').mockImplementation(() => 'darwin');
+jest.mock('../../../util/exec/env');
+jest.mock('../../datasource');
+process.env.CONTAINERBASE = 'true';
+
+function mockMavenFileChangedInGit(fileName = 'maven-wrapper.properties') {
+  git.getRepoStatus.mockResolvedValueOnce(
+    partial<StatusResult>({
+      modified: [`maven.mvn/wrapper/${fileName}`],
+    })
+  );
+}
+
+describe('modules/manager/maven-wrapper/artifacts', () => {
+  beforeEach(() => {
+    GlobalConfig.set({ localDir: join('/tmp/github/some/repo') });
+    jest.resetAllMocks();
+    fs.statLocalFile.mockResolvedValue(
+      partial<Stats>({
+        isFile: () => true,
+        mode: 0o555,
+      })
+    );
+
+    resetPrefetchedImages();
+
+    env.getChildProcessEnv.mockReturnValue({
+      ...envMock.basic,
+      LANG: 'en_US.UTF-8',
+      LC_ALL: 'en_US',
+    });
+    mockedFunction(getPkgReleases).mockResolvedValueOnce({
+      releases: [
+        { version: '8.0.1' },
+        { version: '11.0.1' },
+        { version: '16.0.1' },
+        { version: '17.0.0' },
+      ],
+    });
+  });
+
+  afterEach(() => {
+    GlobalConfig.reset();
+  });
+
+  it('Should not update if there is no dep with maven:wrapper', async () => {
+    const execSnapshots = mockExecAll({ stdout: '', stderr: '' });
+    const updatedDeps = await updateArtifacts({
+      packageFileName: 'maven-wrapper',
+      newPackageFileContent: '',
+      updatedDeps: [{ depName: 'not-mavenwrapper' }],
+      config: {},
+    });
+    expect(updatedDeps).toBeNull();
+    expect(execSnapshots).toBeEmptyArray();
+  });
+
+  it('Docker should use java 8 if version is lower then 2.0.0', async () => {
+    mockMavenFileChangedInGit();
+    const execSnapshots = mockExecAll();
+    GlobalConfig.set({ localDir: './', binarySource: 'docker' });
+    const updatedDeps = await updateArtifacts({
+      packageFileName: 'maven',
+      newPackageFileContent: '',
+      updatedDeps: [{ depName: 'maven-wrapper' }],
+      config: {
+        currentValue: '2.0.0',
+        newValue: '3.3.1',
+        constraints: undefined,
+      },
+    });
+
+    const expected = [
+      {
+        file: {
+          contents: undefined,
+          path: 'maven.mvn/wrapper/maven-wrapper.properties',
+          type: 'addition',
+        },
+      },
+    ];
+
+    expect(execSnapshots[2].cmd).toContain('java 8.0.1');
+    expect(updatedDeps).toEqual(expected);
+  });
+
+  it('Should update when it is maven wrapper', async () => {
+    mockMavenFileChangedInGit();
+    const execSnapshots = mockExecAll({ stdout: '', stderr: '' });
+    const updatedDeps = await updateArtifacts({
+      packageFileName: 'maven',
+      newPackageFileContent: '',
+      updatedDeps: [{ depName: 'maven-wrapper' }],
+      config: { currentValue: '3.3.1', newValue: '3.3.1' },
+    });
+
+    const expected = [
+      {
+        file: {
+          contents: undefined,
+          path: 'maven.mvn/wrapper/maven-wrapper.properties',
+          type: 'addition',
+        },
+      },
+    ];
+    expect(updatedDeps).toEqual(expected);
+    expect(execSnapshots).toEqual([
+      {
+        cmd: './mvnw wrapper:wrapper',
+        options: {
+          cwd: '/tmp/github',
+          encoding: 'utf-8',
+          env: {
+            HOME: '/home/user',
+            HTTPS_PROXY: 'https://example.com',
+            HTTP_PROXY: 'http://example.com',
+            LANG: 'en_US.UTF-8',
+            LC_ALL: 'en_US',
+            NO_PROXY: 'localhost',
+            PATH: '/tmp/path',
+          },
+          maxBuffer: 10485760,
+          timeout: 900000,
+        },
+      },
+    ]);
+  });
+
+  it('Should not update deps when maven-wrapper.properties is not in git change', async () => {
+    mockMavenFileChangedInGit('not-maven-wrapper.properties');
+    const execSnapshots = mockExecAll({ stdout: '', stderr: '' });
+    const updatedDeps = await updateArtifacts({
+      packageFileName: 'maven',
+      newPackageFileContent: '',
+      updatedDeps: [{ depName: 'maven-wrapper' }],
+      config: { newValue: '3.3.1' },
+    });
+    expect(updatedDeps).toEqual([]);
+    expect(execSnapshots).toEqual([
+      {
+        cmd: './mvnw wrapper:wrapper',
+        options: {
+          cwd: '/tmp/github',
+          encoding: 'utf-8',
+          env: {
+            HOME: '/home/user',
+            HTTPS_PROXY: 'https://example.com',
+            HTTP_PROXY: 'http://example.com',
+            LANG: 'en_US.UTF-8',
+            LC_ALL: 'en_US',
+            NO_PROXY: 'localhost',
+            PATH: '/tmp/path',
+          },
+          maxBuffer: 10485760,
+          timeout: 900000,
+        },
+      },
+    ]);
+  });
+
+  it('updates with docker', async () => {
+    mockMavenFileChangedInGit();
+    GlobalConfig.set({ localDir: './', binarySource: 'docker' });
+    const execSnapshots = mockExecAll({ stdout: '', stderr: '' });
+    const result = await updateArtifacts({
+      packageFileName: 'maven',
+      newPackageFileContent: '',
+      updatedDeps: [{ depName: 'maven-wrapper' }],
+      config: { currentValue: '3.3.0', newValue: '3.3.1' },
+    });
+    expect(result).toEqual([
+      {
+        file: {
+          contents: undefined,
+          path: 'maven.mvn/wrapper/maven-wrapper.properties',
+          type: 'addition',
+        },
+      },
+    ]);
+    expect(execSnapshots).toMatchObject([
+      {
+        cmd: 'docker pull renovate/sidecar',
+        options: { encoding: 'utf-8' },
+      },
+      { cmd: 'docker ps --filter name=renovate_sidecar -aq' },
+      {
+        cmd:
+          'docker run --rm --name=renovate_sidecar --label=renovate_child ' +
+          '-v "./":"./" ' +
+          '-e BUILDPACK_CACHE_DIR ' +
+          '-e CONTAINERBASE_CACHE_DIR ' +
+          '-w "../.." ' +
+          'renovate/sidecar' +
+          ' bash -l -c "' +
+          'install-tool java 17.0.0 ' +
+          '&& ' +
+          './mvnw wrapper:wrapper"',
+        options: {
+          cwd: '../..',
+          encoding: 'utf-8',
+          env: {
+            HOME: '/home/user',
+            HTTPS_PROXY: 'https://example.com',
+            HTTP_PROXY: 'http://example.com',
+            LANG: 'en_US.UTF-8',
+            LC_ALL: 'en_US',
+            NO_PROXY: 'localhost',
+            PATH: '/tmp/path',
+          },
+          maxBuffer: 10485760,
+          timeout: 900000,
+        },
+      },
+    ]);
+  });
+
+  it('Should return null when cmd is not found', async () => {
+    mockMavenFileChangedInGit('also-not-maven-wrapper.properties');
+    jest.spyOn(os, 'platform').mockImplementation(() => 'win32');
+    const execSnapshots = mockExecAll({ stdout: '', stderr: '' });
+    fs.statLocalFile.mockResolvedValue(null);
+    const updatedDeps = await updateArtifacts({
+      packageFileName: 'maven',
+      newPackageFileContent: '',
+      updatedDeps: [{ depName: 'maven-wrapper' }],
+      config: { newValue: '3.3.1' },
+    });
+    expect(updatedDeps).toBeNull();
+    expect(execSnapshots).toMatchObject([]);
+  });
+
+  it('Should throw an error when it cant execute', async () => {
+    mockMavenFileChangedInGit();
+    mockExecAll(new Error('temporary-error'));
+    const updatedDeps = await updateArtifacts({
+      packageFileName: 'maven',
+      newPackageFileContent: '',
+      updatedDeps: [{ depName: 'maven-wrapper' }],
+      config: { currentValue: '3.0.0', newValue: '3.3.1' },
+    });
+
+    expect(updatedDeps).toEqual([
+      {
+        artifactError: {
+          lockFile: 'maven',
+          stderr: 'temporary-error',
+        },
+      },
+    ]);
+  });
+
+  it('updates with binarySource install', async () => {
+    const execSnapshots = mockExecAll({ stdout: '', stderr: '' });
+    mockMavenFileChangedInGit();
+    GlobalConfig.set({
+      localDir: join('/tmp/github/some/repo'),
+      binarySource: 'install',
+    });
+    const updatedDeps = await updateArtifacts({
+      packageFileName: 'maven',
+      newPackageFileContent: '',
+      updatedDeps: [{ depName: 'maven-wrapper' }],
+      config: { currentValue: '3.0.0', newValue: '3.3.1' },
+    });
+
+    expect(execSnapshots).toMatchObject([
+      { cmd: 'install-tool java 17.0.0' },
+      {
+        cmd: './mvnw wrapper:wrapper',
+        options: {
+          cwd: '/tmp/github',
+          encoding: 'utf-8',
+          env: {
+            HOME: '/home/user',
+            HTTPS_PROXY: 'https://example.com',
+            HTTP_PROXY: 'http://example.com',
+            LANG: 'en_US.UTF-8',
+            LC_ALL: 'en_US',
+            NO_PROXY: 'localhost',
+            PATH: '/tmp/path',
+          },
+          maxBuffer: 10485760,
+          timeout: 900000,
+        },
+      },
+    ]);
+
+    expect(updatedDeps).toEqual([
+      {
+        file: {
+          contents: undefined,
+          path: 'maven.mvn/wrapper/maven-wrapper.properties',
+          type: 'addition',
+        },
+      },
+    ]);
+  });
+});
diff --git a/lib/modules/manager/maven-wrapper/artifacts.ts b/lib/modules/manager/maven-wrapper/artifacts.ts
new file mode 100644
index 0000000000000000000000000000000000000000..38c319a7353222e042929faa635d2eec12c312bd
--- /dev/null
+++ b/lib/modules/manager/maven-wrapper/artifacts.ts
@@ -0,0 +1,221 @@
+import type { Stats } from 'fs';
+import os from 'os';
+import is from '@sindresorhus/is';
+import { dirname, join } from 'upath';
+import { GlobalConfig } from '../../../config/global';
+import { logger } from '../../../logger';
+import { exec } from '../../../util/exec';
+import type { ExecOptions } from '../../../util/exec/types';
+import { chmodLocalFile, readLocalFile, statLocalFile } from '../../../util/fs';
+import { getRepoStatus } from '../../../util/git';
+import type { StatusResult } from '../../../util/git/types';
+import mavenVersioning from '../../versioning/maven';
+import type {
+  UpdateArtifact,
+  UpdateArtifactsConfig,
+  UpdateArtifactsResult,
+} from '../types';
+
+interface MavenWrapperPaths {
+  wrapperExecutableFileName: string;
+  localProjectDir: string;
+  wrapperFullyQualifiedPath: string;
+}
+
+async function addIfUpdated(
+  status: StatusResult,
+  fileProjectPath: string
+): Promise<UpdateArtifactsResult | null> {
+  if (status.modified.includes(fileProjectPath)) {
+    return {
+      file: {
+        type: 'addition',
+        path: fileProjectPath,
+        contents: await readLocalFile(fileProjectPath),
+      },
+    };
+  }
+  return null;
+}
+
+export async function updateArtifacts({
+  packageFileName,
+  newPackageFileContent,
+  updatedDeps,
+  config,
+}: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> {
+  try {
+    logger.debug({ updatedDeps }, 'maven-wrapper.updateArtifacts()');
+
+    if (!updatedDeps.some((dep) => dep.depName === 'maven-wrapper')) {
+      logger.info(
+        'Maven wrapper version not updated - skipping Artifacts update'
+      );
+      return null;
+    }
+
+    const cmd = await createWrapperCommand(packageFileName);
+
+    if (!cmd) {
+      logger.info('No mvnw found - skipping Artifacts update');
+      return null;
+    }
+    await executeWrapperCommand(cmd, config, packageFileName);
+
+    const status = await getRepoStatus();
+    const artifactFileNames = [
+      '.mvn/wrapper/maven-wrapper.properties',
+      '.mvn/wrapper/maven-wrapper.jar',
+      '.mvn/wrapper/MavenWrapperDownloader.java',
+      'mvnw',
+      'mvnw.cmd',
+    ].map(
+      (filename) =>
+        packageFileName.replace('.mvn/wrapper/maven-wrapper.properties', '') +
+        filename
+    );
+    const updateArtifactsResult = (
+      await getUpdatedArtifacts(status, artifactFileNames)
+    ).filter(is.truthy);
+
+    logger.debug(
+      { files: updateArtifactsResult.map((r) => r.file?.path) },
+      `Returning updated maven-wrapper files`
+    );
+    return updateArtifactsResult;
+  } catch (err) {
+    logger.debug({ err }, 'Error setting new Maven Wrapper release value');
+    return [
+      {
+        artifactError: {
+          lockFile: packageFileName,
+          stderr: err.message,
+        },
+      },
+    ];
+  }
+}
+
+async function getUpdatedArtifacts(
+  status: StatusResult,
+  artifactFileNames: string[]
+): Promise<UpdateArtifactsResult[]> {
+  const updatedResults: UpdateArtifactsResult[] = [];
+  for (const artifactFileName of artifactFileNames) {
+    const updatedResult = await addIfUpdated(status, artifactFileName);
+    if (updatedResult !== null) {
+      updatedResults.push(updatedResult);
+    }
+  }
+  return updatedResults;
+}
+
+/**
+ * Find compatible java version for maven.
+ * see https://maven.apache.org/developers/compatibility-plan.html
+ * @param mavenWrapperVersion current maven version
+ * @returns A Java semver range
+ */
+export function getJavaConstraint(
+  mavenWrapperVersion: string | null | undefined
+): string | null {
+  const major = mavenWrapperVersion
+    ? mavenVersioning.getMajor(mavenWrapperVersion)
+    : null;
+
+  if (major && major >= 3) {
+    return '^17.0.0';
+  }
+
+  return '^8.0.0';
+}
+
+async function executeWrapperCommand(
+  cmd: string,
+  config: UpdateArtifactsConfig,
+  packageFileName: string
+): Promise<void> {
+  logger.debug(`Updating maven wrapper: "${cmd}"`);
+  const { wrapperFullyQualifiedPath } = getMavenPaths(packageFileName);
+  const execOptions: ExecOptions = {
+    cwdFile: wrapperFullyQualifiedPath,
+    docker: {},
+    toolConstraints: [
+      {
+        toolName: 'java',
+        constraint:
+          config.constraints?.java ?? getJavaConstraint(config.currentValue),
+      },
+    ],
+  };
+
+  try {
+    await exec(cmd, execOptions);
+  } catch (err) {
+    logger.error({ err }, 'Error executing maven wrapper update command.');
+    throw err;
+  }
+}
+
+async function createWrapperCommand(
+  packageFileName: string
+): Promise<string | null> {
+  const {
+    wrapperExecutableFileName,
+    localProjectDir,
+    wrapperFullyQualifiedPath,
+  } = getMavenPaths(packageFileName);
+
+  return await prepareCommand(
+    wrapperExecutableFileName,
+    localProjectDir,
+    await statLocalFile(wrapperFullyQualifiedPath),
+    'wrapper:wrapper'
+  );
+}
+
+function mavenWrapperFileName(): string {
+  if (
+    os.platform() === 'win32' &&
+    GlobalConfig.get('binarySource') !== 'docker'
+  ) {
+    return 'mvnw.cmd';
+  }
+  return './mvnw';
+}
+
+function getMavenPaths(packageFileName: string): MavenWrapperPaths {
+  const wrapperExecutableFileName = mavenWrapperFileName();
+  const localProjectDir = join(dirname(packageFileName), '../../');
+  const wrapperFullyQualifiedPath = join(
+    localProjectDir,
+    wrapperExecutableFileName
+  );
+  return {
+    wrapperExecutableFileName,
+    localProjectDir,
+    wrapperFullyQualifiedPath,
+  };
+}
+
+async function prepareCommand(
+  fileName: string,
+  cwd: string | undefined,
+  pathFileStats: Stats | null,
+  args: string | null
+): Promise<string | null> {
+  // istanbul ignore if
+  if (pathFileStats?.isFile() === true) {
+    // if the file is not executable by others
+    if (os.platform() !== 'win32' && (pathFileStats.mode & 0o1) === 0) {
+      // add the execution permission to the owner, group and others
+      logger.warn('Maven wrapper is missing the executable bit');
+      await chmodLocalFile(join(cwd, fileName), pathFileStats.mode | 0o111);
+    }
+    if (args === null) {
+      return fileName;
+    }
+    return `${fileName} ${args}`;
+  }
+  return null;
+}
diff --git a/lib/modules/manager/maven-wrapper/extract.spec.ts b/lib/modules/manager/maven-wrapper/extract.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..db4d124b2b9ed0d13c7a647334b4226576e2c1cf
--- /dev/null
+++ b/lib/modules/manager/maven-wrapper/extract.spec.ts
@@ -0,0 +1,72 @@
+import { extractPackageFile } from '.';
+
+const onlyWrapperProperties =
+  'wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar';
+const onlyMavenProperties =
+  'distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip';
+
+const wrapperAndMavenProperties = `distributionUrl=https://artifactory.tools.bol.com/artifactory/maven-bol/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip\nwrapperUrl=https://artifactory.tools.bol.com/artifactory/maven-bol/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar`;
+
+describe('modules/manager/maven-wrapper/extract', () => {
+  describe('extractPackageFile()', () => {
+    it('extracts version for property file with distribution type "bin" in distributionUrl', () => {
+      const res = extractPackageFile(wrapperAndMavenProperties);
+      expect(res?.deps).toEqual([
+        {
+          currentValue: '3.8.4',
+          replaceString:
+            'https://artifactory.tools.bol.com/artifactory/maven-bol/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip',
+          datasource: 'maven',
+          depName: 'maven',
+          packageName: 'org.apache.maven:apache-maven',
+          versioning: 'maven',
+        },
+        {
+          currentValue: '3.1.0',
+          replaceString:
+            'https://artifactory.tools.bol.com/artifactory/maven-bol/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar',
+          datasource: 'maven',
+          depName: 'maven-wrapper',
+          packageName: 'org.apache.maven.wrapper:maven-wrapper',
+          versioning: 'maven',
+        },
+      ]);
+    });
+
+    // takari or maven wrapper ??
+    it('extracts version for property file with only a wrapper url', () => {
+      const res = extractPackageFile(onlyWrapperProperties);
+      expect(res?.deps).toEqual([
+        {
+          currentValue: '0.5.6',
+          replaceString:
+            'https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar',
+          datasource: 'maven',
+          depName: 'maven-wrapper',
+          packageName: 'org.apache.maven.wrapper:maven-wrapper',
+          versioning: 'maven',
+        },
+      ]);
+    });
+
+    it('extracts version for property file with only a maven url', () => {
+      const res = extractPackageFile(onlyMavenProperties);
+      expect(res?.deps).toEqual([
+        {
+          currentValue: '3.5.4',
+          replaceString:
+            'https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip',
+          datasource: 'maven',
+          depName: 'maven',
+          packageName: 'org.apache.maven:apache-maven',
+          versioning: 'maven',
+        },
+      ]);
+    });
+
+    it('it should return null when there is no string matching the maven properties regex', () => {
+      const res = extractPackageFile('nowrapper');
+      expect(res).toBeNull();
+    });
+  });
+});
diff --git a/lib/modules/manager/maven-wrapper/extract.ts b/lib/modules/manager/maven-wrapper/extract.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ad052e870fee1e9accdbee9fdff040709e613eea
--- /dev/null
+++ b/lib/modules/manager/maven-wrapper/extract.ts
@@ -0,0 +1,68 @@
+import { logger } from '../../../logger';
+import { newlineRegex, regEx } from '../../../util/regex';
+import { MavenDatasource } from '../../datasource/maven';
+import { id as versioning } from '../../versioning/maven';
+import type { PackageDependency, PackageFile } from '../types';
+import type { MavenVersionExtract, Version } from './types';
+
+// https://regex101.com/r/IcOs7P/1
+const DISTRIBUTION_URL_REGEX = regEx(
+  '^(?:distributionUrl\\s*=\\s*)(?<url>\\S*-(?<version>\\d+\\.\\d+(?:\\.\\d+)?(?:-\\w+)*)-(?<type>bin|all)\\.zip)\\s*$'
+);
+
+const WRAPPER_URL_REGEX = regEx(
+  '^(?:wrapperUrl\\s*=\\s*)(?<url>\\S*-(?<version>\\d+\\.\\d+(?:\\.\\d+)?(?:-\\w+)*)(?:.jar))'
+);
+
+function extractVersions(fileContent: string): MavenVersionExtract {
+  const lines = fileContent?.split(newlineRegex) ?? [];
+  const maven = extractLineInfo(lines, DISTRIBUTION_URL_REGEX) ?? undefined;
+  const wrapper = extractLineInfo(lines, WRAPPER_URL_REGEX) ?? undefined;
+  return { maven, wrapper };
+}
+
+function extractLineInfo(lines: string[], regex: RegExp): Version | null {
+  for (const line of lines) {
+    if (line.match(regex)) {
+      const match = regex.exec(line);
+      if (match?.groups) {
+        return {
+          url: match.groups.url,
+          version: match.groups.version,
+        };
+      }
+    }
+  }
+  return null;
+}
+
+export function extractPackageFile(fileContent: string): PackageFile | null {
+  logger.trace('maven-wrapper.extractPackageFile()');
+  const extractResult = extractVersions(fileContent);
+  const deps = [];
+
+  if (extractResult.maven?.version) {
+    const maven: PackageDependency = {
+      depName: 'maven',
+      packageName: 'org.apache.maven:apache-maven',
+      currentValue: extractResult.maven?.version,
+      replaceString: extractResult.maven?.url,
+      datasource: MavenDatasource.id,
+      versioning,
+    };
+    deps.push(maven);
+  }
+
+  if (extractResult.wrapper?.version) {
+    const wrapper: PackageDependency = {
+      depName: 'maven-wrapper',
+      packageName: 'org.apache.maven.wrapper:maven-wrapper',
+      currentValue: extractResult.wrapper?.version,
+      replaceString: extractResult.wrapper?.url,
+      datasource: MavenDatasource.id,
+      versioning,
+    };
+    deps.push(wrapper);
+  }
+  return deps.length ? { deps } : null;
+}
diff --git a/lib/modules/manager/maven-wrapper/index.ts b/lib/modules/manager/maven-wrapper/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9fc60c9a4a0ce9bf817b5a4cf1bcbeb86bf11658
--- /dev/null
+++ b/lib/modules/manager/maven-wrapper/index.ts
@@ -0,0 +1,12 @@
+import { MavenDatasource } from '../../datasource/maven';
+import { id as versioning } from '../../versioning/maven';
+
+export { extractPackageFile } from './extract';
+export { updateArtifacts } from './artifacts';
+
+export const defaultConfig = {
+  fileMatch: ['(^|\\/).mvn/wrapper/maven-wrapper.properties$'],
+  versioning,
+};
+
+export const supportedDatasources = [MavenDatasource.id];
diff --git a/lib/modules/manager/maven-wrapper/readme.md b/lib/modules/manager/maven-wrapper/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..c28108936164d2cbd097f3ab860fcd9adfbe760e
--- /dev/null
+++ b/lib/modules/manager/maven-wrapper/readme.md
@@ -0,0 +1,2 @@
+Configuration for Maven Wrapper updates.
+Changes here affect how Renovate updates the version of Maven in the wrapper, not how it uses the wrapper.
diff --git a/lib/modules/manager/maven-wrapper/types.ts b/lib/modules/manager/maven-wrapper/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ef6bae40b030c527d84be39cb42fcbd988cee3bc
--- /dev/null
+++ b/lib/modules/manager/maven-wrapper/types.ts
@@ -0,0 +1,9 @@
+export interface Version {
+  url: string;
+  version: string;
+}
+
+export interface MavenVersionExtract {
+  maven?: Version;
+  wrapper?: Version;
+}