diff --git a/lib/manager/common.ts b/lib/manager/common.ts
index 3bc5c0a9595ad4619bebda5ade5709fe81dd7c15..e525f8db74697dc6b892bb5901090c25c639651f 100644
--- a/lib/manager/common.ts
+++ b/lib/manager/common.ts
@@ -222,9 +222,9 @@ export interface UpdateArtifact {
   config: UpdateArtifactsConfig;
 }
 
-export interface UpdateDependencyConfig {
+export interface UpdateDependencyConfig<T = Record<string, any>> {
   fileContent: string;
-  upgrade: Upgrade;
+  upgrade: Upgrade<T>;
 }
 
 export interface ManagerApi {
diff --git a/lib/manager/gradle-lite/__snapshots__/parser.spec.ts.snap b/lib/manager/gradle-lite/__snapshots__/parser.spec.ts.snap
new file mode 100644
index 0000000000000000000000000000000000000000..0f74ccdb37146327f04044c2cca0eb2707c8ac4f
--- /dev/null
+++ b/lib/manager/gradle-lite/__snapshots__/parser.spec.ts.snap
@@ -0,0 +1,95 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`manager/gradle-lite/parser parses fixture from "gradle" manager 1`] = `
+Array [
+  Object {
+    "currentValue": "1.5.2.RELEASE",
+    "depName": "org.springframework.boot:spring-boot-gradle-plugin",
+    "groupName": "springBootVersion",
+    "managerData": Object {
+      "fileReplacePosition": 53,
+      "packageFile": "build.gradle",
+    },
+  },
+  Object {
+    "currentValue": "1.2.3",
+    "depName": "com.github.jengelman.gradle.plugins:shadow",
+    "managerData": Object {
+      "fileReplacePosition": 388,
+      "packageFile": "build.gradle",
+    },
+  },
+  Object {
+    "currentValue": "0.1",
+    "depName": "com.fkorotkov:gradle-libraries-plugin",
+    "managerData": Object {
+      "fileReplacePosition": 452,
+      "packageFile": "build.gradle",
+    },
+  },
+  Object {
+    "currentValue": "0.2.3",
+    "depName": "gradle.plugin.se.patrikerdes:gradle-use-latest-versions-plugin",
+    "managerData": Object {
+      "fileReplacePosition": 539,
+      "packageFile": "build.gradle",
+    },
+  },
+  Object {
+    "currentValue": "3.1.1",
+    "depName": "org.apache.openjpa:openjpa",
+    "managerData": Object {
+      "fileReplacePosition": 592,
+      "packageFile": "build.gradle",
+    },
+  },
+  Object {
+    "currentValue": "6.0.9.RELEASE",
+    "depName": "org.grails:gorm-hibernate5-spring-boot",
+    "managerData": Object {
+      "fileReplacePosition": 1785,
+      "packageFile": "build.gradle",
+    },
+  },
+  Object {
+    "currentValue": "6.0.5",
+    "depName": "mysql:mysql-connector-java",
+    "managerData": Object {
+      "fileReplacePosition": 1841,
+      "packageFile": "build.gradle",
+    },
+  },
+  Object {
+    "currentValue": "1.0-groovy-2.4",
+    "depName": "org.spockframework:spock-spring",
+    "managerData": Object {
+      "fileReplacePosition": 1899,
+      "packageFile": "build.gradle",
+    },
+  },
+  Object {
+    "currentValue": "1.3",
+    "depName": "org.hamcrest:hamcrest-core",
+    "managerData": Object {
+      "fileReplacePosition": 2004,
+      "packageFile": "build.gradle",
+    },
+  },
+  Object {
+    "currentValue": "3.1",
+    "depName": "cglib:cglib-nodep",
+    "managerData": Object {
+      "fileReplacePosition": 2092,
+      "packageFile": "build.gradle",
+    },
+  },
+  Object {
+    "currentValue": "3.1.1",
+    "depName": "org.apache.openjpa:openjpa",
+    "managerData": Object {
+      "fileReplacePosition": 2198,
+      "packageFile": "build.gradle",
+    },
+  },
+]
+`;
diff --git a/lib/manager/gradle-lite/common.ts b/lib/manager/gradle-lite/common.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4c47de178afb72f5042743ebaf7b260523edf743
--- /dev/null
+++ b/lib/manager/gradle-lite/common.ts
@@ -0,0 +1,100 @@
+import { PackageDependency } from '../common';
+
+export interface ManagerData {
+  fileReplacePosition: number;
+  packageFile?: string;
+}
+
+export interface VariableData extends ManagerData {
+  key: string;
+  value: string;
+}
+
+export type PackageVariables = Record<string, VariableData>;
+export type VariableRegistry = Record<string, PackageVariables>;
+
+export enum TokenType {
+  Space = 'space',
+  LineComment = 'lineComment',
+  MultiComment = 'multiComment',
+  Newline = 'newline',
+
+  Semicolon = 'semicolon',
+  Colon = 'colon',
+  Dot = 'dot',
+  Comma = 'comma',
+  Operator = 'operator',
+
+  Assignment = 'assignment',
+
+  Word = 'word',
+
+  LeftParen = 'leftParen',
+  RightParen = 'rightParen',
+
+  LeftBracket = 'leftBracket',
+  RightBracket = 'rightBracket',
+
+  LeftBrace = 'leftBrace',
+  RightBrace = 'rightBrace',
+
+  SingleQuotedStart = 'singleQuotedStart',
+  SingleQuotedFinish = 'singleQuotedFinish',
+
+  DoubleQuotedStart = 'doubleQuotedStart',
+  StringInterpolation = 'interpolation',
+  IgnoredInterpolationStart = 'ignoredInterpolation',
+  Variable = 'variable',
+  DoubleQuotedFinish = 'doubleQuotedFinish',
+
+  TripleSingleQuotedStart = 'tripleQuotedStart',
+  TripleDoubleQuotedStart = 'tripleDoubleQuotedStart',
+  TripleQuotedFinish = 'tripleQuotedFinish',
+
+  Char = 'char',
+  EscapedChar = 'escapedChar',
+  String = 'string',
+
+  UnknownLexeme = 'unknownChar',
+  UnknownFragment = 'unknownFragment',
+}
+
+export interface Token {
+  type: TokenType;
+  value: string;
+  offset: number;
+}
+
+export interface StringInterpolation extends Token {
+  type: TokenType.StringInterpolation;
+  children: Token[]; // Tokens inside double-quoted string that are subject of interpolation
+  isComplete: boolean; // True if token has parsed completely
+  isValid: boolean; // False if string contains something unprocessable
+}
+
+// Matcher on single token
+export interface SyntaxMatcher {
+  matchType: TokenType | TokenType[];
+  matchValue?: string | string[];
+  lookahead?: boolean;
+  tokenMapKey?: string;
+}
+
+export type TokenMap = Record<string, Token>;
+
+export interface SyntaxHandlerInput {
+  packageFile: string;
+  variables: PackageVariables;
+  tokenMap: TokenMap;
+}
+
+export type SyntaxHandlerOutput = {
+  deps?: PackageDependency<ManagerData>[];
+  vars?: PackageVariables;
+  urls?: string[];
+} | null;
+
+export interface SyntaxMatchConfig {
+  matchers: SyntaxMatcher[];
+  handler: (MatcherHandlerInput) => SyntaxHandlerOutput;
+}
diff --git a/lib/manager/gradle-lite/extract.spec.ts b/lib/manager/gradle-lite/extract.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..87d2dd16ec2872efd2d52db686115857ffde6e59
--- /dev/null
+++ b/lib/manager/gradle-lite/extract.spec.ts
@@ -0,0 +1,69 @@
+import { fs } from '../../../test/util';
+import { extractAllPackageFiles } from '.';
+
+jest.mock('../../util/fs');
+
+function mockFs(files: Record<string, string>): void {
+  fs.readLocalFile.mockImplementation(
+    (fileName: string): Promise<string> => {
+      const content = files?.[fileName];
+      return typeof content === 'string'
+        ? Promise.resolve(content)
+        : Promise.reject(`File not found: ${fileName}`);
+    }
+  );
+}
+
+describe('manager/gradle-lite/extract', () => {
+  beforeAll(() => {});
+  afterAll(() => {
+    jest.resetAllMocks();
+  });
+
+  it('returns null', async () => {
+    mockFs({
+      'gradle.properties': '',
+      'build.gradle': '',
+    });
+
+    const res = await extractAllPackageFiles({} as never, [
+      'build.gradle',
+      'gradle.properties',
+    ]);
+
+    expect(res).toBeNull();
+  });
+
+  it('works', async () => {
+    mockFs({
+      'gradle.properties': 'baz=1.2.3',
+      'build.gradle': 'url "https://example.com"; "foo:bar:$baz"',
+      'settings.gradle': null,
+    });
+
+    const res = await extractAllPackageFiles({} as never, [
+      'build.gradle',
+      'gradle.properties',
+      'settings.gradle',
+    ]);
+
+    expect(res).toMatchObject([
+      {
+        packageFile: 'gradle.properties',
+        deps: [
+          {
+            depName: 'foo:bar',
+            currentValue: '1.2.3',
+            registryUrls: ['https://example.com'],
+          },
+        ],
+      },
+      { packageFile: 'build.gradle', deps: [] },
+      {
+        datasource: 'maven',
+        deps: [],
+        packageFile: 'settings.gradle',
+      },
+    ]);
+  });
+});
diff --git a/lib/manager/gradle-lite/extract.ts b/lib/manager/gradle-lite/extract.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cab54c04aa7d806defe9c5210b81518b7f5f5553
--- /dev/null
+++ b/lib/manager/gradle-lite/extract.ts
@@ -0,0 +1,72 @@
+import * as upath from 'upath';
+import * as datasourceMaven from '../../datasource/maven';
+import { logger } from '../../logger';
+import { readLocalFile } from '../../util/fs';
+import { ExtractConfig, PackageDependency, PackageFile } from '../common';
+import { ManagerData, VariableRegistry } from './common';
+import { parseGradle, parseProps } from './parser';
+import {
+  getVars,
+  isGradleFile,
+  isPropsFile,
+  reorderFiles,
+  toAbsolutePath,
+} from './utils';
+
+export async function extractAllPackageFiles(
+  config: ExtractConfig,
+  packageFiles: string[]
+): Promise<PackageFile[] | null> {
+  const extractedDeps: PackageDependency<ManagerData>[] = [];
+  const registry: VariableRegistry = {};
+  const packageFilesByName: Record<string, PackageFile> = {};
+  const registryUrls = [];
+  for (const packageFile of reorderFiles(packageFiles)) {
+    packageFilesByName[packageFile] = {
+      packageFile,
+      datasource: datasourceMaven.id,
+      deps: [],
+    };
+
+    try {
+      const content = await readLocalFile(packageFile, 'utf8');
+      const dir = upath.dirname(toAbsolutePath(packageFile));
+      if (isPropsFile(packageFile)) {
+        const { vars, deps } = parseProps(content, packageFile);
+        registry[dir] = vars;
+        extractedDeps.push(...deps);
+      } else if (isGradleFile(packageFile)) {
+        const vars = getVars(registry, dir);
+        const { deps, urls } = parseGradle(content, vars, packageFile);
+        urls.forEach((url) => {
+          if (!registryUrls.includes(url)) {
+            registryUrls.push(url);
+          }
+        });
+        extractedDeps.push(...deps);
+      }
+    } catch (e) {
+      logger.warn(
+        { config, packageFile },
+        `Failed to process Gradle file: ${packageFile}`
+      );
+    }
+  }
+
+  if (!extractedDeps.length) {
+    return null;
+  }
+
+  extractedDeps.forEach((dep) => {
+    const key = dep.managerData.packageFile;
+    const pkgFile: PackageFile = packageFilesByName[key];
+    const { deps } = pkgFile;
+    deps.push({
+      ...dep,
+      registryUrls: [...(dep.registryUrls || []), ...registryUrls],
+    });
+    packageFilesByName[key] = pkgFile;
+  });
+
+  return Object.values(packageFilesByName);
+}
diff --git a/lib/manager/gradle-lite/index.ts b/lib/manager/gradle-lite/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2696995d6bd88c34f20da90a3db9631d9c42f86f
--- /dev/null
+++ b/lib/manager/gradle-lite/index.ts
@@ -0,0 +1,10 @@
+import * as gradleVersioning from '../../versioning/gradle';
+
+export { extractAllPackageFiles } from './extract';
+export { updateDependency } from './update';
+
+export const defaultConfig = {
+  fileMatch: ['(^|/)gradle.properties$', '\\.gradle(\\.kts)?$'],
+  versioning: gradleVersioning.id,
+  enabled: false,
+};
diff --git a/lib/manager/gradle-lite/parser.spec.ts b/lib/manager/gradle-lite/parser.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9b58cc58b880a0bb983681512185e37320220ef1
--- /dev/null
+++ b/lib/manager/gradle-lite/parser.spec.ts
@@ -0,0 +1,187 @@
+import { readFileSync } from 'fs';
+import path from 'path';
+import { parseGradle, parseProps } from './parser';
+
+function getGradleFile(fileName: string): string {
+  return readFileSync(path.resolve(__dirname, fileName), 'utf8');
+}
+
+describe('manager/gradle-lite/parser', () => {
+  it('handles end of input', () => {
+    expect(parseGradle('version = ').deps).toBeEmpty();
+    expect(parseGradle('id "foo.bar" version').deps).toBeEmpty();
+  });
+  it('parses variables', () => {
+    let deps;
+
+    ({ deps } = parseGradle(
+      '\nversion = "1.2.3"\n"foo:bar:$version"\nversion = "3.2.1"'
+    ));
+    expect(deps).toMatchObject([
+      {
+        depName: 'foo:bar',
+        currentValue: '1.2.3',
+      },
+    ]);
+
+    ({ deps } = parseGradle('version = "1.2.3"\n"foo:bar:$version@@@"'));
+    expect(deps).toBeEmpty();
+  });
+  it('parses registryUrls', () => {
+    let urls;
+
+    ({ urls } = parseGradle('url ""'));
+    expect(urls).toBeEmpty();
+
+    ({ urls } = parseGradle('url "#!@"'));
+    expect(urls).toBeEmpty();
+
+    ({ urls } = parseGradle('url "https://example.com"'));
+    expect(urls).toStrictEqual(['https://example.com']);
+
+    ({ urls } = parseGradle('url("https://example.com")'));
+    expect(urls).toStrictEqual(['https://example.com']);
+  });
+  it('parses long form deps', () => {
+    let deps;
+    ({ deps } = parseGradle(
+      'group: "com.example", name: "my.dependency", version: "1.2.3"'
+    ));
+    expect(deps).toMatchObject([
+      {
+        depName: 'com.example:my.dependency',
+        currentValue: '1.2.3',
+      },
+    ]);
+
+    ({ deps } = parseGradle(
+      'group: "com.example", name: "my.dependency", version: depVersion'
+    ));
+    expect(deps).toBeEmpty();
+
+    ({ deps } = parseGradle(
+      'depVersion = "1.2.3"\ngroup: "com.example", name: "my.dependency", version: depVersion'
+    ));
+    expect(deps).toMatchObject([
+      {
+        depName: 'com.example:my.dependency',
+        currentValue: '1.2.3',
+      },
+    ]);
+
+    ({ deps } = parseGradle('("com.example", "my.dependency", "1.2.3")'));
+    expect(deps).toMatchObject([
+      {
+        depName: 'com.example:my.dependency',
+        currentValue: '1.2.3',
+      },
+    ]);
+
+    ({ deps } = parseGradle(
+      '(group = "com.example", name = "my.dependency", version = "1.2.3")'
+    ));
+    expect(deps).toMatchObject([
+      {
+        depName: 'com.example:my.dependency',
+        currentValue: '1.2.3',
+      },
+    ]);
+  });
+  it('parses plugin', () => {
+    let deps;
+
+    ({ deps } = parseGradle('id "foo.bar" version "1.2.3"'));
+    expect(deps).toMatchObject([
+      {
+        depName: 'foo.bar',
+        lookupName: 'foo.bar:foo.bar.gradle.plugin',
+        currentValue: '1.2.3',
+      },
+    ]);
+
+    ({ deps } = parseGradle('id("foo.bar") version "1.2.3"'));
+    expect(deps).toMatchObject([
+      {
+        depName: 'foo.bar',
+        lookupName: 'foo.bar:foo.bar.gradle.plugin',
+        currentValue: '1.2.3',
+      },
+    ]);
+
+    ({ deps } = parseGradle('kotlin("jvm") version "1.3.71"'));
+    expect(deps).toMatchObject([
+      {
+        depName: 'org.jetbrains.kotlin.jvm',
+        lookupName:
+          'org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin',
+        currentValue: '1.3.71',
+      },
+    ]);
+  });
+  it('parses fixture from "gradle" manager', () => {
+    const content = getGradleFile(
+      `../gradle/__fixtures__/build.gradle.example1`
+    );
+    const { deps } = parseGradle(content, {}, 'build.gradle');
+    deps.forEach((dep) => {
+      expect(
+        content
+          .slice(dep.managerData.fileReplacePosition)
+          .indexOf(dep.currentValue)
+      ).toEqual(0);
+    });
+    expect(deps).toMatchSnapshot();
+  });
+  it('calculates offset', () => {
+    const content = "'foo:bar:1.2.3'";
+    const { deps } = parseGradle(content);
+    const res = deps[0];
+    expect(
+      content.slice(res.managerData.fileReplacePosition).indexOf('1.2.3')
+    ).toEqual(0);
+  });
+  it('gradle.properties', () => {
+    expect(parseProps('foo=bar')).toMatchObject({
+      vars: {
+        foo: {
+          fileReplacePosition: 4,
+          key: 'foo',
+          value: 'bar',
+        },
+      },
+      deps: [],
+    });
+    expect(parseProps(' foo = bar ')).toMatchObject({
+      vars: {
+        foo: { key: 'foo', value: 'bar', fileReplacePosition: 7 },
+      },
+      deps: [],
+    });
+    expect(parseProps('foo.bar=baz')).toMatchObject({
+      vars: {
+        'foo.bar': { key: 'foo.bar', value: 'baz', fileReplacePosition: 8 },
+      },
+      deps: [],
+    });
+    expect(parseProps('foo=foo\nbar=bar')).toMatchObject({
+      vars: {
+        foo: { key: 'foo', value: 'foo', fileReplacePosition: 4 },
+        bar: { key: 'bar', value: 'bar', fileReplacePosition: 12 },
+      },
+      deps: [],
+    });
+    expect(parseProps('x=foo:bar:baz', 'x/gradle.properties')).toMatchObject({
+      vars: {},
+      deps: [
+        {
+          currentValue: 'baz',
+          depName: 'foo:bar',
+          managerData: {
+            fileReplacePosition: 10,
+            packageFile: 'x/gradle.properties',
+          },
+        },
+      ],
+    });
+  });
+});
diff --git a/lib/manager/gradle-lite/parser.ts b/lib/manager/gradle-lite/parser.ts
new file mode 100644
index 0000000000000000000000000000000000000000..18579e66f8fd014b86a4a3d22187760a29eb9b6a
--- /dev/null
+++ b/lib/manager/gradle-lite/parser.ts
@@ -0,0 +1,475 @@
+import * as url from 'url';
+import is from '@sindresorhus/is';
+import { logger } from '../../logger';
+import { regEx } from '../../util/regex';
+import { PackageDependency } from '../common';
+import {
+  ManagerData,
+  PackageVariables,
+  StringInterpolation,
+  SyntaxHandlerInput,
+  SyntaxHandlerOutput,
+  SyntaxMatchConfig,
+  SyntaxMatcher,
+  Token,
+  TokenMap,
+  TokenType,
+  VariableData,
+} from './common';
+import { tokenize } from './tokenizer';
+import {
+  interpolateString,
+  isDependencyString,
+  parseDependencyString,
+} from './utils';
+
+function matchTokens(
+  tokens: Token[],
+  matchers: SyntaxMatcher[]
+): TokenMap | null {
+  let lookaheadCount = 0;
+  const result: TokenMap = {};
+  for (let idx = 0; idx < matchers.length; idx += 1) {
+    const token = tokens[idx];
+    const matcher = matchers[idx];
+
+    if (!token) {
+      if (matcher.lookahead) {
+        break;
+      }
+      return null;
+    }
+
+    const typeMatches = is.string(matcher.matchType)
+      ? matcher.matchType === token.type
+      : matcher.matchType.includes(token.type);
+    if (!typeMatches) {
+      return null;
+    }
+
+    if (is.string(matcher.matchValue) && token.value !== matcher.matchValue) {
+      return null;
+    }
+
+    if (
+      is.array<string>(matcher.matchValue) &&
+      !matcher.matchValue.includes(token.value)
+    ) {
+      return null;
+    }
+
+    lookaheadCount = matcher.lookahead ? lookaheadCount + 1 : 0;
+
+    if (matcher.tokenMapKey) {
+      result[matcher.tokenMapKey] = token;
+    }
+  }
+
+  tokens.splice(0, matchers.length - lookaheadCount);
+  return result;
+}
+
+const endOfInstruction: SyntaxMatcher = {
+  // Ensure we skip assignments of complex expressions (not strings)
+  matchType: [
+    TokenType.Semicolon,
+    TokenType.RightBrace,
+    TokenType.Word,
+    TokenType.String,
+    TokenType.StringInterpolation,
+  ],
+  lookahead: true,
+};
+
+const potentialStringTypes = [TokenType.String, TokenType.Word];
+
+function coercePotentialString(
+  token: Token,
+  variables: PackageVariables
+): string | null {
+  const tokenType = token?.type;
+  if (tokenType === TokenType.String) {
+    return token?.value;
+  }
+  if (
+    tokenType === TokenType.Word &&
+    typeof variables[token?.value] !== 'undefined'
+  ) {
+    return variables[token.value].value;
+  }
+  return null;
+}
+
+function handleAssignment({
+  packageFile,
+  tokenMap,
+}: SyntaxHandlerInput): SyntaxHandlerOutput {
+  const { keyToken, valToken } = tokenMap;
+  const variableData: VariableData = {
+    key: keyToken.value,
+    value: valToken.value,
+    fileReplacePosition: valToken.offset,
+    packageFile,
+  };
+  return { vars: { [variableData.key]: variableData } };
+}
+
+function processDepString({
+  packageFile,
+  tokenMap,
+}: SyntaxHandlerInput): SyntaxHandlerOutput {
+  const { token } = tokenMap;
+  const dep = parseDependencyString(token.value);
+  if (dep) {
+    dep.managerData = {
+      fileReplacePosition: token.offset + dep.depName.length + 1,
+      packageFile,
+    };
+    return { deps: [dep] };
+  }
+  return null;
+}
+
+function processDepInterpolation({
+  tokenMap,
+  variables,
+}: SyntaxHandlerInput): SyntaxHandlerOutput {
+  const token = tokenMap.depInterpolation as StringInterpolation;
+  const interpolationResult = interpolateString(token.children, variables);
+  if (interpolationResult && isDependencyString(interpolationResult)) {
+    const dep = parseDependencyString(interpolationResult);
+    if (dep) {
+      const lastChild = token.children[token.children.length - 1];
+      const lastChildValue = lastChild?.value;
+      const variable = variables[lastChildValue];
+      if (
+        lastChild?.type === TokenType.Variable &&
+        variable &&
+        variable?.value === dep.currentValue
+      ) {
+        dep.managerData = {
+          fileReplacePosition: variable.fileReplacePosition,
+          packageFile: variable.packageFile,
+        };
+        dep.groupName = variable.key;
+        return { deps: [dep] };
+      }
+    }
+  }
+  return null;
+}
+
+function processPlugin({
+  tokenMap,
+  packageFile,
+}: SyntaxHandlerInput): SyntaxHandlerOutput {
+  const { pluginName, pluginVersion, methodName } = tokenMap;
+  const plugin = pluginName.value;
+  const depName =
+    methodName.value === 'kotlin' ? `org.jetbrains.kotlin.${plugin}` : plugin;
+  const lookupName =
+    methodName.value === 'kotlin'
+      ? `org.jetbrains.kotlin.${plugin}:org.jetbrains.kotlin.${plugin}.gradle.plugin`
+      : `${plugin}:${plugin}.gradle.plugin`;
+  const currentValue = pluginVersion.value;
+  const fileReplacePosition = pluginVersion.offset;
+  const dep = {
+    depType: 'plugin',
+    depName,
+    lookupName,
+    registryUrls: ['https://plugins.gradle.org/m2/'],
+    currentValue,
+    commitMessageTopic: `plugin ${depName}`,
+    managerData: {
+      fileReplacePosition,
+      packageFile,
+    },
+  };
+  return { deps: [dep] };
+}
+
+function processRegistryUrl({
+  tokenMap,
+}: SyntaxHandlerInput): SyntaxHandlerOutput {
+  const registryUrl = tokenMap.registryUrl?.value;
+  try {
+    if (registryUrl) {
+      const { host, protocol } = url.parse(registryUrl);
+      if (host && protocol) {
+        return { urls: [registryUrl] };
+      }
+    }
+  } catch (e) {
+    // no-op
+  }
+  return null;
+}
+
+function processLongFormDep({
+  tokenMap,
+  variables,
+  packageFile,
+}: SyntaxHandlerInput): SyntaxHandlerOutput {
+  const groupId = coercePotentialString(tokenMap.groupId, variables);
+  const artifactId = coercePotentialString(tokenMap.artifactId, variables);
+  const version = coercePotentialString(tokenMap.version, variables);
+  const dep = parseDependencyString([groupId, artifactId, version].join(':'));
+  if (dep) {
+    const versionToken: Token = tokenMap.version;
+    if (versionToken.type === TokenType.Word) {
+      const variable = variables[versionToken.value];
+      dep.managerData = {
+        fileReplacePosition: variable.fileReplacePosition,
+        packageFile: variable.packageFile,
+      };
+    } else {
+      dep.managerData = {
+        fileReplacePosition: versionToken.offset,
+        packageFile,
+      };
+    }
+    return { deps: [dep] };
+  }
+  return null;
+}
+
+const matcherConfigs: SyntaxMatchConfig[] = [
+  {
+    // foo = 'bar'
+    matchers: [
+      { matchType: TokenType.Word, tokenMapKey: 'keyToken' },
+      { matchType: TokenType.Assignment },
+      { matchType: TokenType.String, tokenMapKey: 'valToken' },
+      endOfInstruction,
+    ],
+    handler: handleAssignment,
+  },
+  {
+    // 'foo.bar:baz:1.2.3'
+    matchers: [
+      {
+        matchType: TokenType.String,
+        tokenMapKey: 'token',
+      },
+    ],
+    handler: processDepString,
+  },
+  {
+    // "foo.bar:baz:${bazVersion}"
+    matchers: [
+      {
+        matchType: TokenType.StringInterpolation,
+        tokenMapKey: 'depInterpolation',
+      },
+    ],
+    handler: processDepInterpolation,
+  },
+  {
+    // id 'foo.bar' version '1.2.3'
+    matchers: [
+      {
+        matchType: TokenType.Word,
+        matchValue: ['id', 'kotlin'],
+        tokenMapKey: 'methodName',
+      },
+      { matchType: TokenType.String, tokenMapKey: 'pluginName' },
+      { matchType: TokenType.Word, matchValue: 'version' },
+      { matchType: TokenType.String, tokenMapKey: 'pluginVersion' },
+      endOfInstruction,
+    ],
+    handler: processPlugin,
+  },
+  {
+    // id('foo.bar') version '1.2.3'
+    matchers: [
+      {
+        matchType: TokenType.Word,
+        matchValue: ['id', 'kotlin'],
+        tokenMapKey: 'methodName',
+      },
+      { matchType: TokenType.LeftParen },
+      { matchType: TokenType.String, tokenMapKey: 'pluginName' },
+      { matchType: TokenType.RightParen },
+      { matchType: TokenType.Word, matchValue: 'version' },
+      { matchType: TokenType.String, tokenMapKey: 'pluginVersion' },
+      endOfInstruction,
+    ],
+    handler: processPlugin,
+  },
+  {
+    // url 'https://repo.spring.io/snapshot/'
+    matchers: [
+      { matchType: TokenType.Word, matchValue: 'url' },
+      { matchType: TokenType.String, tokenMapKey: 'registryUrl' },
+      endOfInstruction,
+    ],
+    handler: processRegistryUrl,
+  },
+  {
+    // url('https://repo.spring.io/snapshot/')
+    matchers: [
+      { matchType: TokenType.Word, matchValue: 'url' },
+      { matchType: TokenType.LeftParen },
+      { matchType: TokenType.String, tokenMapKey: 'registryUrl' },
+      { matchType: TokenType.RightParen },
+      endOfInstruction,
+    ],
+    handler: processRegistryUrl,
+  },
+  {
+    // group: "com.example", name: "my.dependency", version: "1.2.3"
+    matchers: [
+      { matchType: TokenType.Word, matchValue: 'group' },
+      { matchType: TokenType.Colon },
+      { matchType: potentialStringTypes, tokenMapKey: 'groupId' },
+      { matchType: TokenType.Comma },
+      { matchType: TokenType.Word, matchValue: 'name' },
+      { matchType: TokenType.Colon },
+      { matchType: potentialStringTypes, tokenMapKey: 'artifactId' },
+      { matchType: TokenType.Comma },
+      { matchType: TokenType.Word, matchValue: 'version' },
+      { matchType: TokenType.Colon },
+      { matchType: potentialStringTypes, tokenMapKey: 'version' },
+      endOfInstruction,
+    ],
+    handler: processLongFormDep,
+  },
+  {
+    // ("com.example", "my.dependency", "1.2.3")
+    matchers: [
+      { matchType: TokenType.LeftParen },
+      { matchType: potentialStringTypes, tokenMapKey: 'groupId' },
+      { matchType: TokenType.Comma },
+      { matchType: potentialStringTypes, tokenMapKey: 'artifactId' },
+      { matchType: TokenType.Comma },
+      { matchType: potentialStringTypes, tokenMapKey: 'version' },
+      { matchType: TokenType.RightParen },
+    ],
+    handler: processLongFormDep,
+  },
+  {
+    // (group = "com.example", name = "my.dependency", version = "1.2.3")
+    matchers: [
+      { matchType: TokenType.LeftParen },
+      { matchType: TokenType.Word, matchValue: 'group' },
+      { matchType: TokenType.Assignment },
+      { matchType: potentialStringTypes, tokenMapKey: 'groupId' },
+      { matchType: TokenType.Comma },
+      { matchType: TokenType.Word, matchValue: 'name' },
+      { matchType: TokenType.Assignment },
+      { matchType: potentialStringTypes, tokenMapKey: 'artifactId' },
+      { matchType: TokenType.Comma },
+      { matchType: TokenType.Word, matchValue: 'version' },
+      { matchType: TokenType.Assignment },
+      { matchType: potentialStringTypes, tokenMapKey: 'version' },
+      { matchType: TokenType.RightParen },
+    ],
+    handler: processLongFormDep,
+  },
+];
+
+interface MatchConfig {
+  tokens: Token[];
+  variables: PackageVariables;
+  packageFile: string;
+}
+
+function tryMatch({
+  tokens,
+  variables,
+  packageFile,
+}: MatchConfig): SyntaxHandlerOutput {
+  for (const { matchers, handler } of matcherConfigs) {
+    const tokenMap = matchTokens(tokens, matchers);
+    if (tokenMap) {
+      const result = handler({
+        packageFile,
+        variables,
+        tokenMap,
+      });
+      if (result !== null) {
+        return result;
+      }
+    }
+  }
+  tokens.shift();
+  return null;
+}
+
+export function parseGradle(
+  input: string,
+  initVars: PackageVariables = {},
+  packageFile?: string
+): { deps: PackageDependency<ManagerData>[]; urls: string[] } {
+  const vars = { ...initVars };
+  const deps: PackageDependency<ManagerData>[] = [];
+  const urls = [];
+
+  const tokens = tokenize(input);
+  let prevTokensLength = tokens.length;
+  while (tokens.length) {
+    const matchResult = tryMatch({ tokens, variables: vars, packageFile });
+    if (matchResult?.deps?.length) {
+      deps.push(...matchResult.deps);
+    }
+    if (matchResult?.vars) {
+      Object.assign(vars, matchResult.vars);
+    }
+    if (matchResult?.urls) {
+      urls.push(...matchResult.urls);
+    }
+
+    // istanbul ignore if
+    if (tokens.length >= prevTokensLength) {
+      // Should not happen, but it's better to be prepared
+      logger.warn(
+        { packageFile },
+        `${packageFile} parsing error, results can be incomplete`
+      );
+      break;
+    }
+    prevTokensLength = tokens.length;
+  }
+
+  return { deps, urls };
+}
+
+const propWord = '[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)*';
+const propRegex = regEx(
+  `^(?<leftPart>\\s*(?<key>${propWord})\\s*=\\s*['"]?)(?<value>[^\\s'"]+)['"]?\\s*$`
+);
+
+export function parseProps(
+  input: string,
+  packageFile?: string
+): { vars: PackageVariables; deps: PackageDependency<ManagerData>[] } {
+  let offset = 0;
+  const vars = {};
+  const deps = [];
+  for (const line of input.split('\n')) {
+    const lineMatch = propRegex.exec(line);
+    if (lineMatch) {
+      const { key, value, leftPart } = lineMatch.groups;
+      if (isDependencyString(value)) {
+        const dep = parseDependencyString(value);
+        deps.push({
+          ...dep,
+          managerData: {
+            fileReplacePosition:
+              offset + leftPart.length + dep.depName.length + 1,
+            packageFile,
+          },
+        });
+      } else {
+        vars[key] = {
+          key,
+          value,
+          fileReplacePosition: offset + leftPart.length,
+          packageFile,
+        };
+      }
+    }
+    offset += line.length + 1;
+  }
+  return { vars, deps };
+}
diff --git a/lib/manager/gradle-lite/readme.md b/lib/manager/gradle-lite/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..c9778421d78420f0c8723dc67b8d9280547a926e
--- /dev/null
+++ b/lib/manager/gradle-lite/readme.md
@@ -0,0 +1,6 @@
+`gradle-lite` is an an alternate manager for Gradle, and is written in JavaScript.
+The main benefit of `gradle-lite` is that it skips the slow Gradle commands.
+
+You can use the default `gradle` manager and `gradle-lite` at the same time.
+
+If you like the commits from `gradle-lite`, you can use `gradle-lite` as a complete replacement for the default manager.
diff --git a/lib/manager/gradle-lite/tokenizer.spec.ts b/lib/manager/gradle-lite/tokenizer.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0d53d7dbde9c9b7335462779d021180a1531297a
--- /dev/null
+++ b/lib/manager/gradle-lite/tokenizer.spec.ts
@@ -0,0 +1,186 @@
+import { TokenType } from './common';
+import { extractRawTokens, tokenize } from './tokenizer';
+
+function tokenTypes(input): string[] {
+  return extractRawTokens(input).map((token) => token.type);
+}
+
+describe('manager/gradle-lite/tokenizer', () => {
+  it('extractTokens', () => {
+    const samples = {
+      ' ': [TokenType.Space],
+      '\t': [TokenType.Space],
+      '\r': [TokenType.Space],
+      '\t\r': [TokenType.Space],
+      '\r\t': [TokenType.Space],
+      '// foobar': [TokenType.LineComment],
+      '/* foobar */': [TokenType.MultiComment],
+      '/* foo *//* bar */': [TokenType.MultiComment, TokenType.MultiComment],
+      '/* foo\nbar\nbaz */': [TokenType.MultiComment],
+      '/* foo\r\nbar\r\nbaz */': [TokenType.MultiComment],
+      '\n\n': [TokenType.Newline, TokenType.Newline],
+      ':': [TokenType.Colon],
+      ';': [TokenType.Semicolon],
+      '.': [TokenType.Dot],
+      '==': [TokenType.Operator],
+      '=': [TokenType.Assignment],
+      foo: [TokenType.Word],
+      'foo.bar': [TokenType.Word, TokenType.Dot, TokenType.Word],
+      'foo()': [TokenType.Word, TokenType.LeftParen, TokenType.RightParen],
+      'foo[]': [TokenType.Word, TokenType.LeftBracket, TokenType.RightBracket],
+      '{{}}': [
+        TokenType.LeftBrace,
+        TokenType.LeftBrace,
+        TokenType.RightBrace,
+        TokenType.RightBrace,
+      ],
+      '@': [TokenType.UnknownLexeme],
+      "'\\''": [
+        TokenType.SingleQuotedStart,
+        TokenType.EscapedChar,
+        TokenType.SingleQuotedFinish,
+      ],
+      "'\\\"'": [
+        TokenType.SingleQuotedStart,
+        TokenType.EscapedChar,
+        TokenType.SingleQuotedFinish,
+      ],
+      "'\\'\\\"'": [
+        TokenType.SingleQuotedStart,
+        TokenType.EscapedChar,
+        TokenType.EscapedChar,
+        TokenType.SingleQuotedFinish,
+      ],
+      "'x'": [
+        TokenType.SingleQuotedStart,
+        TokenType.Char,
+        TokenType.SingleQuotedFinish,
+      ],
+      "'\n'": [
+        TokenType.SingleQuotedStart,
+        TokenType.Char,
+        TokenType.SingleQuotedFinish,
+      ],
+      "'$x'": [
+        TokenType.SingleQuotedStart,
+        TokenType.Char,
+        TokenType.Char,
+        TokenType.SingleQuotedFinish,
+      ],
+      "''''''": ['tripleQuotedStart', 'tripleQuotedFinish'],
+      "'''x'''": ['tripleQuotedStart', TokenType.Char, 'tripleQuotedFinish'],
+      "'''\n'''": ['tripleQuotedStart', TokenType.Char, 'tripleQuotedFinish'],
+      "'''\\''''": [
+        'tripleQuotedStart',
+        TokenType.EscapedChar,
+        'tripleQuotedFinish',
+      ],
+      "'''\\\"'''": [
+        'tripleQuotedStart',
+        TokenType.EscapedChar,
+        'tripleQuotedFinish',
+      ],
+      "'''\\'\\\"'''": [
+        'tripleQuotedStart',
+        TokenType.EscapedChar,
+        TokenType.EscapedChar,
+        'tripleQuotedFinish',
+      ],
+      '""': [TokenType.DoubleQuotedStart, TokenType.DoubleQuotedFinish],
+      '"\\""': [
+        TokenType.DoubleQuotedStart,
+        TokenType.EscapedChar,
+        TokenType.DoubleQuotedFinish,
+      ],
+      '"\\\'"': [
+        TokenType.DoubleQuotedStart,
+        TokenType.EscapedChar,
+        TokenType.DoubleQuotedFinish,
+      ],
+      '"\\"\\\'"': [
+        TokenType.DoubleQuotedStart,
+        TokenType.EscapedChar,
+        TokenType.EscapedChar,
+        TokenType.DoubleQuotedFinish,
+      ],
+      '"x"': [
+        TokenType.DoubleQuotedStart,
+        TokenType.Char,
+        TokenType.DoubleQuotedFinish,
+      ],
+      '"\n"': [
+        TokenType.DoubleQuotedStart,
+        TokenType.Char,
+        TokenType.DoubleQuotedFinish,
+      ],
+      // eslint-disable-next-line no-template-curly-in-string
+      '"${x}"': [
+        TokenType.DoubleQuotedStart,
+        TokenType.Variable,
+        TokenType.DoubleQuotedFinish,
+      ],
+      // eslint-disable-next-line no-template-curly-in-string
+      '"${foo}"': [
+        TokenType.DoubleQuotedStart,
+        TokenType.Variable,
+        TokenType.DoubleQuotedFinish,
+      ],
+      // eslint-disable-next-line no-template-curly-in-string
+      '"${x()}"': [
+        TokenType.DoubleQuotedStart,
+        TokenType.IgnoredInterpolationStart,
+        TokenType.UnknownLexeme,
+        TokenType.UnknownLexeme,
+        TokenType.UnknownLexeme,
+        TokenType.RightBrace,
+        TokenType.DoubleQuotedFinish,
+      ],
+      // eslint-disable-next-line no-template-curly-in-string
+      '"${x{}}"': [
+        TokenType.DoubleQuotedStart,
+        TokenType.IgnoredInterpolationStart,
+        TokenType.UnknownLexeme,
+        TokenType.LeftBrace,
+        TokenType.RightBrace,
+        TokenType.RightBrace,
+        TokenType.DoubleQuotedFinish,
+      ],
+    };
+    for (const [str, result] of Object.entries(samples)) {
+      expect(tokenTypes(str)).toStrictEqual(result);
+    }
+  });
+
+  it('tokenize', () => {
+    const samples = {
+      '@': [{ type: TokenType.UnknownFragment }],
+      '@@@': [{ type: TokenType.UnknownFragment }],
+      "'foobar'": [{ type: TokenType.String, value: 'foobar' }],
+      "'\\b'": [{ type: TokenType.String, value: '\b' }],
+      "'''foobar'''": [{ type: TokenType.String, value: 'foobar' }],
+      '"foobar"': [{ type: TokenType.String, value: 'foobar' }],
+      '"$foo"': [
+        {
+          type: TokenType.StringInterpolation,
+          children: [{ type: TokenType.Variable }],
+        },
+      ],
+      // eslint-disable-next-line no-template-curly-in-string
+      '" foo ${ bar } baz "': [
+        {
+          type: TokenType.StringInterpolation,
+          children: [
+            { type: TokenType.String, value: ' foo ' },
+            { type: TokenType.Variable, value: 'bar' },
+            { type: TokenType.String, value: ' baz ' },
+          ],
+        },
+      ],
+      // eslint-disable-next-line no-template-curly-in-string
+      '"${ x + y }"': [{ type: TokenType.StringInterpolation, isValid: false }],
+    };
+    for (const [str, result] of Object.entries(samples)) {
+      expect(tokenize(str)).toMatchObject(result);
+    }
+  });
+});
diff --git a/lib/manager/gradle-lite/tokenizer.ts b/lib/manager/gradle-lite/tokenizer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..904cf29b161111c135981b8df31533d931401409
--- /dev/null
+++ b/lib/manager/gradle-lite/tokenizer.ts
@@ -0,0 +1,233 @@
+import moo from 'moo';
+import { StringInterpolation, Token, TokenType } from './common';
+
+const escapedCharRegex = /\\['"bfnrt\\]/;
+const escapedChars = {
+  [TokenType.EscapedChar]: {
+    match: escapedCharRegex,
+    value: (x: string): string =>
+      ({
+        "\\'": "'",
+        '\\"': '"',
+        '\\b': '\b',
+        '\\f': '\f',
+        '\\n': '\n',
+        '\\r': '\r',
+        '\\t': '\t',
+        '\\\\': '\\',
+      }[x]),
+  },
+};
+
+export const rawLexer = {
+  // Top-level Groovy lexemes
+  main: {
+    [TokenType.LineComment]: { match: /\/\/.*?$/ },
+    [TokenType.MultiComment]: { match: /\/\*[^]*?\*\//, lineBreaks: true },
+    [TokenType.Newline]: { match: /\r?\n/, lineBreaks: true },
+    [TokenType.Space]: { match: /[ \t\r]+/ },
+    [TokenType.Semicolon]: ';',
+    [TokenType.Colon]: ':',
+    [TokenType.Dot]: '.',
+    [TokenType.Comma]: ',',
+    [TokenType.Operator]: /(?:==|\+=?|-=?|\/=?|\*\*?|\.+|:)/,
+    [TokenType.Assignment]: '=',
+    [TokenType.Word]: { match: /[a-zA-Z$_][a-zA-Z0-9$_]+/ },
+    [TokenType.LeftParen]: { match: '(' },
+    [TokenType.RightParen]: { match: ')' },
+    [TokenType.LeftBracket]: { match: '[' },
+    [TokenType.RightBracket]: { match: ']' },
+    [TokenType.LeftBrace]: { match: '{', push: 'main' },
+    [TokenType.RightBrace]: { match: '}', pop: 1 },
+    [TokenType.TripleSingleQuotedStart]: {
+      match: "'''",
+      push: TokenType.TripleSingleQuotedStart,
+    },
+    [TokenType.TripleDoubleQuotedStart]: {
+      match: '"""',
+      push: TokenType.TripleDoubleQuotedStart,
+    },
+    [TokenType.SingleQuotedStart]: {
+      match: "'",
+      push: TokenType.SingleQuotedStart,
+    },
+    [TokenType.DoubleQuotedStart]: {
+      match: '"',
+      push: TokenType.DoubleQuotedStart,
+    },
+    [TokenType.UnknownLexeme]: { match: /./ },
+  },
+
+  // Tokenize triple-quoted string literal characters
+  [TokenType.TripleSingleQuotedStart]: {
+    ...escapedChars,
+    [TokenType.TripleQuotedFinish]: { match: "'''", pop: 1 },
+    [TokenType.Char]: { match: /[^]/, lineBreaks: true },
+  },
+  [TokenType.TripleDoubleQuotedStart]: {
+    ...escapedChars,
+    [TokenType.TripleQuotedFinish]: { match: '"""', pop: 1 },
+    [TokenType.Char]: { match: /[^]/, lineBreaks: true },
+  },
+
+  // Tokenize single-quoted string literal characters
+  [TokenType.SingleQuotedStart]: {
+    ...escapedChars,
+    [TokenType.SingleQuotedFinish]: { match: "'", pop: 1 },
+    [TokenType.Char]: { match: /[^]/, lineBreaks: true },
+  },
+
+  // Tokenize double-quoted string literal chars and interpolations
+  [TokenType.DoubleQuotedStart]: {
+    ...escapedChars,
+    [TokenType.DoubleQuotedFinish]: { match: '"', pop: 1 },
+    variable: {
+      // Supported: ${foo}, $foo, ${ foo.bar.baz }, $foo.bar.baz
+      match: /\${\s*[a-zA-Z_][a-zA-Z0-9_]*(?:\s*\.\s*[a-zA-Z_][a-zA-Z0-9_]*)*\s*}|\$[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*/,
+      value: (x: string): string =>
+        x.replace(/^\${?\s*/, '').replace(/\s*}$/, ''),
+    },
+    [TokenType.IgnoredInterpolationStart]: {
+      match: /\${/,
+      push: TokenType.IgnoredInterpolationStart,
+    },
+    [TokenType.Char]: { match: /[^]/, lineBreaks: true },
+  },
+
+  // Ignore interpolation of complex expressions˙,
+  // but track the balance of braces to find the end of interpolation.
+  [TokenType.IgnoredInterpolationStart]: {
+    [TokenType.LeftBrace]: {
+      match: '{',
+      push: TokenType.IgnoredInterpolationStart,
+    },
+    [TokenType.RightBrace]: { match: '}', pop: 1 },
+    [TokenType.UnknownLexeme]: { match: /[^]/, lineBreaks: true },
+  },
+};
+
+/*
+  Turn UnknownLexeme chars to UnknownFragment strings
+ */
+function processUnknownLexeme(acc: Token[], token: Token): Token[] {
+  if (token.type === TokenType.UnknownLexeme) {
+    const prevToken: Token = acc[acc.length - 1];
+    if (prevToken?.type === TokenType.UnknownFragment) {
+      prevToken.value += token.value;
+    } else {
+      acc.push({ ...token, type: TokenType.UnknownFragment });
+    }
+  } else {
+    acc.push(token);
+  }
+  return acc;
+}
+
+//
+// Turn separated chars of string literal to single String token
+//
+function processChar(acc: Token[], token: Token): Token[] {
+  const tokenType = token.type;
+  const prevToken: Token = acc[acc.length - 1];
+  if ([TokenType.Char, TokenType.EscapedChar].includes(tokenType)) {
+    if (prevToken?.type === TokenType.String) {
+      prevToken.value += token.value;
+    } else {
+      acc.push({ ...token, type: TokenType.String });
+    }
+  } else {
+    acc.push(token);
+  }
+  return acc;
+}
+
+export function isInterpolationToken(
+  token: Token
+): token is StringInterpolation {
+  return token?.type === TokenType.StringInterpolation;
+}
+
+//
+// Turn all tokens between double quote pairs into StringInterpolation token
+//
+function processInterpolation(acc: Token[], token: Token): Token[] {
+  if (token.type === TokenType.DoubleQuotedStart) {
+    // This token will accumulate further strings and variables
+    const interpolationToken: StringInterpolation = {
+      type: TokenType.StringInterpolation,
+      children: [],
+      isValid: true,
+      isComplete: false,
+      offset: token.offset + 1,
+      value: '',
+    };
+    acc.push(interpolationToken);
+    return acc;
+  }
+
+  const prevToken: Token = acc[acc.length - 1];
+  if (isInterpolationToken(prevToken) && !prevToken.isComplete) {
+    const type = token.type;
+    if (type === TokenType.DoubleQuotedFinish) {
+      if (
+        prevToken.isValid &&
+        prevToken.children.every(({ type: t }) => t === TokenType.String)
+      ) {
+        // Nothing to interpolate, replace to String
+        acc[acc.length - 1] = {
+          type: TokenType.String,
+          value: prevToken.children.map(({ value }) => value).join(''),
+          offset: prevToken.offset,
+        };
+        return acc;
+      }
+      prevToken.isComplete = true;
+    } else if (type === TokenType.String || type === TokenType.Variable) {
+      prevToken.children.push(token);
+    } else {
+      prevToken.children.push(token);
+      prevToken.isValid = false;
+    }
+  } else {
+    acc.push(token);
+  }
+  return acc;
+}
+
+const filteredTokens = [
+  TokenType.Space,
+  TokenType.LineComment,
+  TokenType.MultiComment,
+  TokenType.Newline,
+  TokenType.Semicolon,
+  TokenType.SingleQuotedStart,
+  TokenType.SingleQuotedFinish,
+  TokenType.DoubleQuotedFinish,
+  TokenType.TripleSingleQuotedStart,
+  TokenType.TripleDoubleQuotedStart,
+  TokenType.TripleQuotedFinish,
+];
+
+function filterTokens({ type }: Token): boolean {
+  return !filteredTokens.includes(type);
+}
+
+export function extractRawTokens(input: string): Token[] {
+  const lexer = moo.states(rawLexer);
+  lexer.reset(input);
+  return Array.from(lexer).map(
+    ({ type, offset, value }) => ({ type, offset, value } as Token)
+  );
+}
+
+export function processTokens(tokens: Token[]): Token[] {
+  return tokens
+    .reduce(processUnknownLexeme, [])
+    .reduce(processChar, [])
+    .reduce(processInterpolation, [])
+    .filter(filterTokens);
+}
+
+export function tokenize(input: string): Token[] {
+  return processTokens(extractRawTokens(input));
+}
diff --git a/lib/manager/gradle-lite/update.spec.ts b/lib/manager/gradle-lite/update.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..36ce9c2f1ff509e23d6b4b99029288314ef28aac
--- /dev/null
+++ b/lib/manager/gradle-lite/update.spec.ts
@@ -0,0 +1,78 @@
+import { updateDependency } from './update';
+
+describe('manager/gradle-lite/update', () => {
+  it('replaces', () => {
+    expect(
+      updateDependency({
+        fileContent: '___1.2.3___',
+        upgrade: {
+          currentValue: '1.2.3',
+          newValue: '1.2.4',
+          managerData: {
+            fileReplacePosition: 3,
+          },
+        },
+      })
+    ).toEqual('___1.2.4___');
+  });
+
+  it('groups', () => {
+    expect(
+      updateDependency({
+        fileContent: '___1.2.4___',
+        upgrade: {
+          currentValue: '1.2.3',
+          newValue: '1.2.5',
+          groupName: 'group',
+          managerData: {
+            fileReplacePosition: 3,
+          },
+        },
+      })
+    ).toEqual('___1.2.5___');
+  });
+
+  it('returns same content', () => {
+    const fileContent = '___1.2.4___';
+    expect(
+      updateDependency({
+        fileContent,
+        upgrade: {
+          currentValue: '1.2.3',
+          newValue: '1.2.4',
+          managerData: {
+            fileReplacePosition: 3,
+          },
+        },
+      })
+    ).toBe(fileContent);
+  });
+
+  it('returns null', () => {
+    expect(
+      updateDependency({
+        fileContent: '___1.3.0___',
+        upgrade: {
+          currentValue: '1.2.3',
+          newValue: '1.2.4',
+          managerData: {
+            fileReplacePosition: 3,
+          },
+        },
+      })
+    ).toBeNull();
+
+    expect(
+      updateDependency({
+        fileContent: '',
+        upgrade: {
+          currentValue: '1.2.3',
+          newValue: '1.2.4',
+          managerData: {
+            fileReplacePosition: 3,
+          },
+        },
+      })
+    ).toBeNull();
+  });
+});
diff --git a/lib/manager/gradle-lite/update.ts b/lib/manager/gradle-lite/update.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5da7d06e8d678fb05d07aa0d2f641bc3be6cb845
--- /dev/null
+++ b/lib/manager/gradle-lite/update.ts
@@ -0,0 +1,29 @@
+import { logger } from '../../logger';
+import { UpdateDependencyConfig } from '../common';
+import { ManagerData } from './common';
+import { versionLikeSubstring } from './utils';
+
+export function updateDependency({
+  fileContent,
+  upgrade,
+}: UpdateDependencyConfig<ManagerData>): string | null {
+  const { depName, currentValue, newValue, managerData } = upgrade;
+  const offset = managerData.fileReplacePosition;
+  const leftPart = fileContent.slice(0, offset);
+  const rightPart = fileContent.slice(offset);
+  const version = versionLikeSubstring(rightPart);
+  if (version) {
+    const versionClosePosition = version.length;
+    const restPart = rightPart.slice(versionClosePosition);
+    if (version === newValue) {
+      return fileContent;
+    }
+    if (version === currentValue || upgrade.groupName) {
+      return leftPart + newValue + restPart;
+    }
+    logger.debug({ depName, version, currentValue, newValue }, 'Unknown value');
+  } else {
+    logger.debug({ depName, currentValue, newValue }, 'Wrong offset');
+  }
+  return null;
+}
diff --git a/lib/manager/gradle-lite/utils.spec.ts b/lib/manager/gradle-lite/utils.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9f014b1e496a8e5fa063083404c61b1163e3ea61
--- /dev/null
+++ b/lib/manager/gradle-lite/utils.spec.ts
@@ -0,0 +1,183 @@
+import { TokenType } from './common';
+import {
+  getVars,
+  interpolateString,
+  isDependencyString,
+  parseDependencyString,
+  reorderFiles,
+  toAbsolutePath,
+  versionLikeSubstring,
+} from './utils';
+
+describe('manager/gradle-lite/utils', () => {
+  it('versionLikeSubstring', () => {
+    [
+      '1.2.3',
+      'foobar',
+      '[1.0,2.0]',
+      '(,2.0[',
+      '2.1.1.RELEASE',
+      '1.0.+',
+      'latest',
+    ].forEach((input) => {
+      expect(versionLikeSubstring(input)).toEqual(input);
+      expect(versionLikeSubstring(`${input}'`)).toEqual(input);
+      expect(versionLikeSubstring(`${input}"`)).toEqual(input);
+      expect(versionLikeSubstring(`${input}\n`)).toEqual(input);
+      expect(versionLikeSubstring(`${input}  `)).toEqual(input);
+      expect(versionLikeSubstring(`${input}$`)).toEqual(input);
+    });
+    expect(versionLikeSubstring('')).toBeNull();
+    expect(versionLikeSubstring(undefined)).toBeNull();
+    expect(versionLikeSubstring(null)).toBeNull();
+  });
+
+  it('isDependencyString', () => {
+    expect(isDependencyString('foo:bar:1.2.3')).toBe(true);
+    expect(isDependencyString('foo.foo:bar.bar:1.2.3')).toBe(true);
+    expect(isDependencyString('foo:bar:baz:qux')).toBe(false);
+    expect(isDependencyString('foo.bar:baz:1.2.3')).toBe(true);
+    expect(isDependencyString('foo.bar:baz:1.2.+')).toBe(true);
+    expect(isDependencyString('foo.bar:baz:qux:quux')).toBe(false);
+    expect(isDependencyString("foo:bar:1.2.3'")).toBe(false);
+    expect(isDependencyString('foo:bar:1.2.3"')).toBe(false);
+    expect(isDependencyString('-Xep:ParameterName:OFF')).toBe(false);
+  });
+
+  it('parseDependencyString', () => {
+    expect(parseDependencyString('foo:bar:1.2.3')).toMatchObject({
+      depName: 'foo:bar',
+      currentValue: '1.2.3',
+    });
+    expect(parseDependencyString('foo.foo:bar.bar:1.2.3')).toMatchObject({
+      depName: 'foo.foo:bar.bar',
+      currentValue: '1.2.3',
+    });
+    expect(parseDependencyString('foo:bar:baz:qux')).toBeNull();
+    expect(parseDependencyString('foo.bar:baz:1.2.3')).toMatchObject({
+      depName: 'foo.bar:baz',
+      currentValue: '1.2.3',
+    });
+    expect(parseDependencyString('foo:bar:1.2.+')).toMatchObject({
+      depName: 'foo:bar',
+      currentValue: '1.2.+',
+    });
+    expect(parseDependencyString('foo.bar:baz:qux:quux')).toBeNull();
+    expect(parseDependencyString("foo:bar:1.2.3'")).toBeNull();
+    expect(parseDependencyString('foo:bar:1.2.3"')).toBeNull();
+    expect(parseDependencyString('-Xep:ParameterName:OFF')).toBeNull();
+  });
+
+  it('interpolateString', () => {
+    expect(interpolateString([], {})).toBe('');
+    expect(
+      interpolateString(
+        [
+          { type: TokenType.String, value: 'foo' },
+          { type: TokenType.Variable, value: 'bar' },
+          { type: TokenType.String, value: 'baz' },
+        ] as never,
+        {
+          bar: { value: 'BAR' },
+        } as never
+      )
+    ).toBe('fooBARbaz');
+    expect(
+      interpolateString(
+        [{ type: TokenType.Variable, value: 'foo' }] as never,
+        {} as never
+      )
+    ).toBeNull();
+    expect(
+      interpolateString(
+        [{ type: TokenType.UnknownFragment, value: 'foo' }] as never,
+        {} as never
+      )
+    ).toBeNull();
+  });
+
+  it('reorderFiles', () => {
+    expect(reorderFiles(['a.gradle', 'b.gradle', 'a.gradle'])).toStrictEqual([
+      'a.gradle',
+      'a.gradle',
+      'b.gradle',
+    ]);
+
+    expect(
+      reorderFiles([
+        'a/b/c/build.gradle',
+        'a/build.gradle',
+        'a/b/build.gradle',
+        'build.gradle',
+      ])
+    ).toStrictEqual([
+      'build.gradle',
+      'a/build.gradle',
+      'a/b/build.gradle',
+      'a/b/c/build.gradle',
+    ]);
+
+    expect(reorderFiles(['b.gradle', 'c.gradle', 'a.gradle'])).toStrictEqual([
+      'a.gradle',
+      'b.gradle',
+      'c.gradle',
+    ]);
+
+    expect(
+      reorderFiles(['b.gradle', 'c.gradle', 'a.gradle', 'gradle.properties'])
+    ).toStrictEqual(['gradle.properties', 'a.gradle', 'b.gradle', 'c.gradle']);
+
+    expect(
+      reorderFiles([
+        'a/b/c/gradle.properties',
+        'a/b/c/build.gradle',
+        'a/build.gradle',
+        'a/gradle.properties',
+        'a/b/build.gradle',
+        'a/b/gradle.properties',
+        'build.gradle',
+        'gradle.properties',
+        'b.gradle',
+        'c.gradle',
+        'a.gradle',
+      ])
+    ).toStrictEqual([
+      'gradle.properties',
+      'a.gradle',
+      'b.gradle',
+      'build.gradle',
+      'c.gradle',
+      'a/gradle.properties',
+      'a/build.gradle',
+      'a/b/gradle.properties',
+      'a/b/build.gradle',
+      'a/b/c/gradle.properties',
+      'a/b/c/build.gradle',
+    ]);
+  });
+
+  it('getVars', () => {
+    const registry = {
+      [toAbsolutePath('/foo')]: {
+        foo: { key: 'foo', value: 'FOO' } as never,
+        bar: { key: 'bar', value: 'BAR' } as never,
+        baz: { key: 'baz', value: 'BAZ' } as never,
+        qux: { key: 'qux', value: 'QUX' } as never,
+      },
+      [toAbsolutePath('/foo/bar')]: {
+        foo: { key: 'foo', value: 'foo' } as never,
+      },
+      [toAbsolutePath('/foo/bar/baz')]: {
+        bar: { key: 'bar', value: 'bar' } as never,
+        baz: { key: 'baz', value: 'baz' } as never,
+      },
+    };
+    const res = getVars(registry, '/foo/bar/baz/build.gradle');
+    expect(res).toStrictEqual({
+      foo: { key: 'foo', value: 'foo' },
+      bar: { key: 'bar', value: 'bar' },
+      baz: { key: 'baz', value: 'baz' },
+      qux: { key: 'qux', value: 'QUX' },
+    });
+  });
+});
diff --git a/lib/manager/gradle-lite/utils.ts b/lib/manager/gradle-lite/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4afe57e7f2c6410e7aefe5db0731a5c546fb6b47
--- /dev/null
+++ b/lib/manager/gradle-lite/utils.ts
@@ -0,0 +1,138 @@
+import upath from 'upath';
+import { regEx } from '../../util/regex';
+import { PackageDependency } from '../common';
+import {
+  ManagerData,
+  PackageVariables,
+  Token,
+  TokenType,
+  VariableRegistry,
+} from './common';
+
+const artifactRegex = regEx(
+  '^[a-zA-Z][-_a-zA-Z0-9]*(?:.[a-zA-Z][-_a-zA-Z0-9]*)*$'
+);
+
+const versionLikeRegex = regEx('^(?<version>[-.\\[\\](),a-zA-Z0-9+]+)');
+
+// Extracts version-like and range-like strings
+// from the beginning of input
+export function versionLikeSubstring(input: string): string | null {
+  const match = input ? versionLikeRegex.exec(input) : null;
+  return match ? match.groups.version : null;
+}
+
+export function isDependencyString(input: string): boolean {
+  const split = input?.split(':');
+  if (split?.length !== 3) {
+    return false;
+  }
+  const [groupId, artifactId, versionPart] = split;
+  return (
+    groupId &&
+    artifactId &&
+    versionPart &&
+    artifactRegex.test(groupId) &&
+    artifactRegex.test(artifactId) &&
+    versionPart === versionLikeSubstring(versionPart)
+  );
+}
+
+export function parseDependencyString(
+  input: string
+): PackageDependency<ManagerData> | null {
+  if (!isDependencyString(input)) {
+    return null;
+  }
+  const [groupId, artifactId, currentValue] = input?.split(':');
+  return {
+    depName: `${groupId}:${artifactId}`,
+    currentValue,
+  };
+}
+
+export function interpolateString(
+  childTokens: Token[],
+  variables: PackageVariables
+): string | null {
+  const resolvedSubstrings = [];
+  for (const childToken of childTokens) {
+    const type = childToken.type;
+    if (type === TokenType.String) {
+      resolvedSubstrings.push(childToken.value);
+    } else if (type === TokenType.Variable) {
+      const varName = childToken.value;
+      const varData = variables[varName];
+      if (varData) {
+        resolvedSubstrings.push(varData.value);
+      } else {
+        return null;
+      }
+    } else {
+      return null;
+    }
+  }
+  return resolvedSubstrings.join('');
+}
+
+export function isGradleFile(path: string): boolean {
+  const filename = upath.basename(path).toLowerCase();
+  return filename.endsWith('.gradle') || filename.endsWith('.gradle.kts');
+}
+
+export function isPropsFile(path: string): boolean {
+  const filename = upath.basename(path).toLowerCase();
+  return filename === 'gradle.properties';
+}
+
+export function toAbsolutePath(packageFile: string): string {
+  return upath.join(packageFile.replace(/^[/\\]*/, '/'));
+}
+
+export function reorderFiles(packageFiles: string[]): string[] {
+  return packageFiles.sort((x, y) => {
+    const xAbs = toAbsolutePath(x);
+    const yAbs = toAbsolutePath(y);
+
+    const xDir = upath.dirname(xAbs);
+    const yDir = upath.dirname(yAbs);
+
+    if (xDir === yDir) {
+      if (
+        (isGradleFile(xAbs) && isGradleFile(yAbs)) ||
+        (isPropsFile(xAbs) && isPropsFile(yAbs))
+      ) {
+        if (xAbs > yAbs) {
+          return 1;
+        }
+        if (xAbs < yAbs) {
+          return -1;
+        }
+      } else if (isGradleFile(xAbs)) {
+        return 1;
+      } else if (isGradleFile(yAbs)) {
+        return -1;
+      }
+    } else if (xDir.startsWith(yDir)) {
+      return 1;
+    } else if (yDir.startsWith(xDir)) {
+      return -1;
+    }
+
+    return 0;
+  });
+}
+
+export function getVars(
+  registry: VariableRegistry,
+  dir: string,
+  vars: PackageVariables = registry[dir] || {}
+): PackageVariables {
+  const dirAbs = toAbsolutePath(dir);
+  const parentDir = upath.dirname(dirAbs);
+  if (parentDir === dirAbs) {
+    return vars;
+  }
+  const parentVars = registry[parentDir] || {};
+  return getVars(registry, parentDir, { ...parentVars, ...vars });
+}
diff --git a/package.json b/package.json
index cfc9ebd10f53803c021621574958eff462f7903b..760fc9c5c60c62389a031686054851775efce5fa 100644
--- a/package.json
+++ b/package.json
@@ -154,6 +154,7 @@
     "markdown-it": "12.0.2",
     "markdown-table": "2.0.0",
     "minimatch": "3.0.4",
+    "moo": "0.5.1",
     "node-emoji": "1.10.0",
     "p-all": "3.0.0",
     "p-map": "4.0.0",
@@ -211,6 +212,7 @@
     "@types/luxon": "1.25.0",
     "@types/markdown-it": "10.0.3",
     "@types/markdown-table": "2.0.0",
+    "@types/moo": "0.5.3",
     "@types/nock": "10.0.3",
     "@types/node": "12.19.8",
     "@types/node-emoji": "1.8.1",
diff --git a/yarn.lock b/yarn.lock
index 0e0a4b430b2738d26261857c2f154ba58a2707b5..acb795f2097d914da42728c4166f974595c2830b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1750,6 +1750,11 @@
   resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256"
   integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==
 
+"@types/moo@0.5.3":
+  version "0.5.3"
+  resolved "https://registry.yarnpkg.com/@types/moo/-/moo-0.5.3.tgz#d034ae641728c473d367e7b7afd991123b8bdecb"
+  integrity sha512-PJJ/jvb5Gor8DWvXN3e75njfQyYNRz0PaFSZ3br9GfHM9N2FxvuJ/E/ytcQePJOLzHlvgFSsIJIvfUMUxWTbnA==
+
 "@types/nock@10.0.3":
   version "10.0.3"
   resolved "https://registry.yarnpkg.com/@types/nock/-/nock-10.0.3.tgz#dab1d18ffbccfbf2db811dab9584304eeb6e1c4c"
@@ -7328,6 +7333,11 @@ moment@^2.19.3:
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
   integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
 
+moo@0.5.1:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4"
+  integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==
+
 move-concurrently@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"