View all services
Talk to QA Advisor
Browse the Knowledge Hub211 resources
Tools Library · Review and guide

Gradle review: pricing, pros, cons and setup

Gradle is an open source build tool for JVM, Android and other projects, with build scripts written in a Kotlin or Groovy DSL. For test automation it runs JUnit, TestNG and Spock suites through its Test task, skips tests whose inputs have not changed, and produces HTML and JUnit XML reports. Gradle 9.8 shipped on 24 September 2026, with a security patch, 9.8.1, on 7 October.

QAble has evaluated this tool and works alongside it. The factual sections are complete; our view is bounded to what we have actually seen rather than a long-term verdict.

last reviewed
13 October 2026
version covered
Gradle 9.8
licence
Open source

Reviewed by Viral Patel

Co-Founder, QAbleLast reviewed 13 October 2026

At a glance

Gradle at a glance

Official site
Vendor
Gradle Inc. (Develocity)
Category
Build tools
Licence
Open source (Apache 2.0)
Platforms
JVM, Android, Native
Languages
Groovy, Kotlin
Integrates with
JUnit, TestNG, Android Studio, Jenkins
First released
2007
Version reviewed
Gradle 9.8

Verdict

Our verdict on Gradle

Strengths

  • Tests are skipped when code and inputs are unchanged, and results can come from a local or remote build cache
  • maxParallelForks runs test classes across several JVMs, each identifiable through a worker property
  • The official test-retry plugin reruns failed tests and can fail the build on flakes
  • JVM Test Suite plugin declares integration or UI test suites with their own dependencies and tasks
  • One HTML report covers every test task, and JUnit XML can merge reruns into flakyFailure entries
  • The wrapper pins the Gradle version per repository, and toolchains pin the Java version for tests

Weaknesses

  • Up-to-date checks can skip tests against a live environment unless inputs are modelled or --rerun is used
  • Build scripts are code, and complex builds become hard for test engineers to follow
  • Gradle 9 needs Java 17 or later to run the daemon, and removed several long-deprecated APIs
  • Test retries need a separate plugin; retry is not built into the Test task
  • Detailed flaky test analytics across builds are part of Develocity, a commercial product

Our take

Gradle is the natural choice when a test suite lives inside an existing Gradle build, which covers every Android project and many Kotlin and Spring codebases, and when incremental builds and the build cache would save real CI time. Its test task is capable: parallel forks, test filtering, retries through an official plugin and JUnit XML that records flaky reruns. QAble has evaluated it rather than maintained a long-running suite on it, so treat this as a bounded take. Watch three things. Up-to-date checks mean tests may not run when nothing changed, which is right for builds and surprising for suites that test a live environment. Gradle 9 changed test behaviour, so builds that relied on old conventions can silently stop running tests. And build logic is code, so a test project needs someone who owns it.

We have evaluated this tool rather than maintained a long-running suite on it, so this is a bounded view rather than a long-term verdict. The rest of this page is factual.

How it works

How Gradle works

A task graph, not a lifecycle

Gradle builds a directed graph of tasks from the build scripts and runs only what the requested task needs. The java plugin adds tasks such as compileJava, compileTestJava, test and check, wired together through their inputs and outputs, so ./gradlew test compiles exactly what the tests need and nothing else. Configuration happens first, when scripts are evaluated and the graph is built, then execution. That two-phase model is the source of both its speed and its learning curve: logic written at configuration time runs on every build, even when the task it configures is never executed, which is why the documentation pushes lazy configuration through providers and tasks.register.

Incremental tests and the build cache

Every task declares inputs and outputs. If they match the last run, the task is up to date and is skipped; with the build cache turned on, outputs can be restored from a previous build on this machine or a shared remote cache. The test task’s inputs include the compiled test and production classes, the runtime classpath and the task configuration, so tests rerun only when something they depend on changes. For unit tests that is a large saving. For suites that test a deployed environment, a database or a third-party API, the state that matters is outside Gradle’s view, so the task must be told to rerun, with --rerun on the command line or by modelling the environment as an input.

The Test task and forked JVMs

Tests run in JVMs forked from the build, isolated from Gradle’s own process. useJUnitPlatform selects the JUnit Platform, which runs JUnit Jupiter, Vintage, TestNG through its engine, Spock and others; useTestNG and useJUnit select the older runners. maxParallelForks, 1 by default, sets how many test JVMs run at once, and each has a unique org.gradle.test.worker system property for isolating files or ports. forkEvery recycles a JVM after a number of test classes, which contains leaky static state at the cost of speed. failFast stops at the first failure. Results are written as JUnit XML under build/test-results and an HTML report under build/reports/tests.

Daemon, wrapper and toolchains

A long-lived daemon keeps build logic warm between runs, which is much of why repeated local builds are fast. The wrapper, gradlew with a properties file, downloads the Gradle version the repository names, so CI and every developer use the same one. Toolchains separate the Java that runs Gradle from the Java that compiles and runs tests: Gradle 9 needs Java 17 or later for the daemon, but a toolchain can still compile and test on another version, and Gradle 9.8 adds Java 27. Gradle can provision a missing toolchain if a resolver is configured.

Strengths

What it does well

Separate test suites with their own dependencies

The JVM Test Suite plugin declares suites such as integrationTest or uiTest, each with its own source set, dependencies and Test task. UI suites can depend on Selenium or Playwright without polluting unit test classpaths, run after unit tests with shouldRunAfter, and be wired into check only on the branches that need them.

Retries that keep flakiness visible

The org.gradle.test-retry plugin, published by Gradle, retries failed tests within the same task up to maxRetries, stops retrying after maxFailures, and can fail the build when a test only passed after a retry. With the JUnit XML option mergeReruns, a pass after failure is written as flakyFailure inside one test case, the same shape Maven Surefire produces.

Precise test filtering

The --tests option accepts class names, methods and wildcards, and can be repeated, so a single failing case can be rerun in seconds. Inside the build, filters and JUnit Platform tags select subsets per task. The --test-dry-run option reports which tests a filter would select without running them, which is useful when a CI job runs nothing.

Aggregated test reports

The standard HTML report includes results from every Test task in a project, and the Test Report Aggregation plugin combines results across subprojects into one report. For multi-module automation frameworks that is the difference between one page to read after a run and a dozen.

Build Scans for failure diagnosis

Running a build with --scan publishes a Build Scan: a web page with every task, test result, console output and dependency resolution for that build, shareable by link. It is free to publish to Develocity’s public service after accepting its terms, and is often the quickest way to show a colleague why a CI run failed.

Reuse of Maven mirror settings

Gradle 9.8 can reuse Maven’s mirror configuration when org.gradle.mirror.maven.settings=true is set in gradle.properties. Organisations that route both Maven and Gradle through an internal repository mirror configure it once, which removes a frequent cause of CI builds failing to resolve test dependencies.

Pricing

Gradle pricing and licensing

Gradle Build Tool is free and open source under the Apache 2.0 licence, with no paid edition. Its maker, Gradle Inc., now trading as Develocity, sells a separate commercial platform of the same name for build and test observability, remote build caching, test distribution, predictive test selection and flaky test detection. Develocity is priced per committer per year, with a free trial, and is optional: the build tool works fully without it.

  • Licence: Apache 2.0 for Gradle Build Tool and the test-retry plugin.
  • Free: everything in the build tool, including the build cache, test retries through the plugin and Build Scans published to the public service.
  • Develocity: commercial, priced per committer per year on request, with a 30-day trial. Flaky Tests Detection, Test Distribution and Predictive Test Selection are Develocity features, not build tool features.
  • Infrastructure: CI time, plus a remote build cache node or repository mirror if you run one.
  • Hidden cost: build logic is code, and a large build needs someone who understands Gradle to keep it fast and upgradeable across major versions.

Checked against the vendor’s own pages on 13 October 2026. Pricing changes; confirm on the official site before buying.

Getting started

Gradle tutorial: getting started

Setup, step by step

  1. Install a JDK 17 or later for Gradle 9, and Gradle itself, or start from a repository that already has a wrapper.
  2. Generate a project with gradle init, choosing the Kotlin DSL and JUnit Jupiter.
  3. Use ./gradlew from then on, so everyone runs the repository’s Gradle version.
  4. Declare JUnit through its BOM, add junit-platform-launcher as testRuntimeOnly, and call useJUnitPlatform.
  5. Run ./gradlew test, then open the HTML report under build/reports/tests/test.
  6. Run it again without changes and notice the test task is UP-TO-DATE.

Install

gradle init --type java-application --dsl kotlin --test-framework junit-jupiter
./gradlew test
# force tests to run even when nothing changed
./gradlew test --rerun

A first example

// build.gradle.kts
plugins {
    java
    id("org.gradle.test-retry") version "1.6.6"
}

repositories { mavenCentral() }

java {
    // Tests compile and run on Java 21, whatever JDK runs Gradle
    toolchain { languageVersion = JavaLanguageVersion.of(21) }
}

dependencies {
    testImplementation(platform("org.junit:junit-bom:6.1.3"))
    testImplementation("org.junit.jupiter:junit-jupiter")
    // Declare the launcher yourself; do not rely on Gradle supplying it
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.test {
    useJUnitPlatform()
    maxParallelForks = 2 // test JVMs running at once
    retry {
        // Retry only in CI, and fail if a test passed only after a retry
        if (System.getenv("CI") != null) maxRetries.set(2)
        maxFailures.set(20)
        failOnPassedAfterRetry.set(true)
    }
    reports.junitXml.mergeReruns = true // reruns become flakyFailure entries
}

What trips people up first

Tests that do not run. On a second run with no changes, the test task reports UP-TO-DATE and executes nothing, which is correct for unit tests and confusing for a suite that checks a live environment. Use ./gradlew test --rerun, or model the environment URL as a task input so a change triggers a run. The other early trap is the JUnit launcher: Gradle 8 deprecated loading it from the Gradle distribution, with removal planned for 9.0, so declare junit-platform-launcher as testRuntimeOnly. Gradle 9 also fails the test task if test sources exist but no tests are discovered, which usually means the wrong framework is selected.

In practice

Using Gradle in practice

Organising unit, API and UI suites

Use the JVM Test Suite plugin rather than ad hoc Test tasks. Keep unit tests in the built-in test suite, and register suites such as apiTest and uiTest, each with its own dependencies, so Selenium or Playwright never sits on the unit test classpath. Order them with shouldRunAfter, and attach only fast suites to check, running the slow ones explicitly in CI. Put shared conventions, such as JUnit configuration, retries and report settings, in a convention plugin under build-logic so every module applies them identically. In a multi-project build, keep the automation framework code in its own project that the suites depend on.

// build.gradle.kts
testing {
    suites {
        val test = named<JvmTestSuite>("test")
        // Suites other than test use JUnit Jupiter by convention
        register<JvmTestSuite>("uiTest") {
            dependencies {
                implementation(project())
                implementation("org.seleniumhq.selenium:selenium-java:4.51.0")
            }
            targets {
                all {
                    testTask.configure {
                        shouldRunAfter(test)
                        // UI tests target a live environment: always run them
                        outputs.upToDateWhen { false }
                        systemProperty("baseUrl", System.getenv("BASE_URL") ?: "http://localhost:8080")
                    }
                }
            }
        }
    }
}

Running Gradle suites in CI

Run through the wrapper, cache Gradle’s dependency and build caches, and publish JUnit XML whatever the outcome. In GitHub Actions the gradle/actions setup-gradle action configures caching and writes a job summary of each Gradle build, and wrapper-validation checks that the committed gradle-wrapper.jar is genuine. Pass --continue when several test tasks run, so one failing suite does not stop the others from reporting. Point the reporting step at build/test-results, which holds one folder per Test task.

# .github/workflows/tests.yml
name: tests
on: [pull_request]
permissions:
  contents: read
  checks: write
jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-java@v6
        with:
          distribution: temurin
          java-version: '21'
      - uses: gradle/actions/setup-gradle@v6
      - run: ./gradlew test uiTest --continue
      - uses: mikepenz/action-junit-report@v6
        if: ${{ !cancelled() }}
        with:
          report_paths: '**/build/test-results/**/TEST-*.xml'

Parallelism inside and across builds

Three settings do different jobs. maxParallelForks runs test classes in several JVMs within one Test task; give each fork its own resources using the org.gradle.test.worker property. org.gradle.parallel=true in gradle.properties runs tasks from different subprojects at the same time, so module suites overlap. The build cache, org.gradle.caching=true, avoids running unchanged suites at all. Begin with forks set to about half the CI machine’s cores for browser suites, measure, and raise it only if the environment under test keeps up. Across machines, Gradle Build Tool has no built-in sharding; split by subproject or test filter per CI job, or use Develocity Test Distribution.

Upgrading test builds to Gradle 9

Gradle 9.0 shipped on 31 July 2025, and 9.8 is the current minor. Upgrade on 8.14 first and clear every deprecation warning, then switch the wrapper. Expect four changes to matter for test projects: the daemon needs Java 17 or later, though toolchains can still test older targets; a Test task registered without testClassesDirs and classpath now runs no tests, silently, where it used to borrow the test source set; the test task fails when test sources exist but nothing is discovered; and the Conventions API is gone, which breaks older plugins and build scripts. Move to test suites and current plugin versions as part of the upgrade, and apply 9.8.1, which fixes three high-rated vulnerabilities.

Pitfalls

Common pitfalls

A live-environment suite reported UP-TO-DATE

Why it happens: Gradle skips the test task when classes, classpath and configuration are unchanged. It cannot see that the environment under test changed.

The fix: Run with --rerun, mark the task with outputs.upToDateWhen { false } for environment suites, or add the target URL or build number as an input.

A custom Test task that runs nothing after upgrading

Why it happens: Before Gradle 9, a Test task without configuration borrowed the test source set’s classes and classpath. That convention was removed, and such tasks now run zero tests without failing.

The fix: Declare additional suites with the JVM Test Suite plugin, or set testClassesDirs and classpath explicitly on every custom Test task.

No tests discovered after adding JUnit

Why it happens: The task uses a different framework from the tests, or the JUnit Platform launcher is missing from the runtime classpath. Gradle 9 now fails the task when test sources exist but nothing is discovered.

The fix: Call useJUnitPlatform, import the JUnit BOM and declare junit-platform-launcher as testRuntimeOnly.

Gradle 9 refusing to start on an older JDK

Why it happens: The Gradle 9 daemon needs Java 17 or later. CI images or developer machines still on Java 11 fail before any test runs.

The fix: Run Gradle on Java 17 or later and use a toolchain to compile and test on the version the product targets.

Retries hiding flaky tests

Why it happens: With the test-retry plugin’s defaults, a test that passes on retry does not fail the task, so flaky tests accumulate unnoticed.

The fix: Set failOnPassedAfterRetry where flakiness must be fixed, enable mergeReruns so the XML records flakes, and track them in your reporting.

Configuration-time work slowing every build

Why it happens: Code at the top level of a build script runs during configuration on every invocation, even for unrelated tasks, such as reading files or calling services to set up test data.

The fix: Register tasks lazily with tasks.register, use providers, and move setup work into task actions or the tests themselves.

Compared

How Gradle compares

ToolBuild definitionLicenceFlaky test retriesParallel testsSkips unchanged testsTest result output
GradleKotlin or Groovy DSLApache 2.0Official test-retry pluginmaxParallelForks, parallel project buildsYes, up-to-date checks and build cacheJUnit XML plus HTML report
Apache MavenDeclarative XML POMApache 2.0Built in: rerunFailingTestsCountforkCount JVMs, framework threadsNo, not in core MavenJUnit XML per test class
BazelStarlark BUILD filesApache 2.0flaky attribute: up to three attemptsshard_count across test runner processesYes, passing results are cachedXML per test target

Head-to-head comparisons

Gradle vs Maven

FAQ

Frequently asked questions

Is Gradle free?

Yes. Gradle Build Tool is open source under the Apache 2.0 licence and free to use for any project, including the build cache, the wrapper and the official test-retry plugin. Develocity, sold by the same company, is a separate commercial platform for build and test analytics, flaky test detection, test distribution and predictive test selection, priced per committer per year.

Gradle or Maven for test automation?

Follow the codebase first: if the application builds with Gradle, especially an Android app, keep the tests in that build. For a standalone Java test suite, Maven is simpler to read and has reruns built in, while Gradle gives faster repeated builds through up-to-date checks and caching and more flexibility when the build needs logic. Both produce the same JUnit XML for CI.

Why did my Gradle tests not run?

Usually because the test task was UP-TO-DATE: nothing it depends on changed, so Gradle reused the previous result. Run ./gradlew test --rerun to force it. If no tests run even then, check that useJUnitPlatform matches the framework, that junit-platform-launcher is on the test runtime classpath, and that custom Test tasks set testClassesDirs and classpath, which Gradle 9 no longer infers.

How do I retry flaky tests in Gradle?

Apply the org.gradle.test-retry plugin and configure retry on the test task: maxRetries for attempts, maxFailures to stop retrying when many tests fail, and failOnPassedAfterRetry if a pass after failure should still fail the build. Enable mergeReruns on the JUnit XML report so CI tools see flakes as flakyFailure entries. Limit retries to CI by checking an environment variable.

How do I run a single test with Gradle?

Use --tests with a class or method pattern: ./gradlew test --tests LoginTest or ./gradlew test --tests LoginTest.rejectsBadPassword. Wildcards such as *IntegTest work, the option can be repeated, and filters in the build script still apply. Add --rerun if the task would otherwise be up to date, and use --test-dry-run to check what a filter selects.

What changed in Gradle 9.8?

Gradle 9.8.0 was released on 24 September 2026 and 9.8.1 on 7 October, fixing three high-rated vulnerabilities, so use 9.8.1. The release adds Java 27 support for the daemon and toolchains, reuse of Maven mirror settings, clickable problem locations and ordered failures in console output, lazy destination properties for Copy and Sync, and Windows performance work. Upgrade with ./gradlew wrapper --gradle-version=9.8.1.

Is Gradle still worth learning in 2026?

Yes, for anyone testing JVM or Android software. Android builds use Gradle, many Kotlin and Spring projects do too, and test engineers who can read and adjust a build script can add suites, fix classpath problems and speed up CI without waiting for a build engineer. Learn the Kotlin DSL, test suites and the test task options first.

Alternatives

Gradle alternatives worth weighing

Resources

Where to go next

Ready

Picking the wrong tool costs more than picking late.

QAble advises on toolchain decisions and then implements them, with engineers who have run these tools on real delivery. Start with a free QA audit.

Talk to QA Advisor