cswork / libraryOpen practice workspace

Articles

Gradle Interview Questions: Build Cache, Configuration Cache, and Task Inputs

Practice Gradle interview questions with tested build-cache outcomes, configuration-cache behavior, task inputs, and a repaired stale-output example.

A useful answer to Gradle interview questions starts with a build log and an artifact. UP-TO-DATE, FROM-CACHE, and “Configuration cache entry reused” describe different reuse decisions. None of them, by itself, proves that your generated file contains the right data.

This guide follows one original configuration-file generator through nine local runs. Official Gradle documentation supplies the contracts; the tables report our Gradle 9.0.0 and Java 21 experiment. The interview prompts are editorial practice, not claims about a company's question frequency. Start with Design CI/CD Build Caching, then use the smaller experiment here to explain cache correctness precisely.

What Does Each Cache Reuse?

Think about the work you are avoiding. A dependency cache avoids downloading or resolving previously obtained dependency material under its applicable rules. A task-output build cache restores outputs from an equivalent execution. The configuration cache preserves configured build state so Gradle can avoid repeating configuration work when its configuration inputs still match.

These mechanisms can coexist. A build can reuse configuration and still execute a task because its source file changed. Conversely, Gradle can configure the build again and then discover that a task's existing output is up to date. Our experiment demonstrates both combinations.

The official configuration-cache guide describes configuration inputs separately from task inputs. Do not flatten those two lists into “anything changed, so the cache was cleared.” Ask which cached object became invalid, and why.

Our commands explicitly enable both caches. This avoids relying on defaults that differ across versions or project settings. A current documentation statement about a future release's defaults should not be applied retroactively to a Gradle 9.0.0 build.

Read UP-TO-DATE and FROM-CACHE Correctly

UP-TO-DATE means Gradle's checks allow it to keep the task's existing workspace outputs. FROM-CACHE means it restored task outputs from a build-cache entry instead of executing the task action. An ordinary task line without either label means something different again; inspect the surrounding log before summarizing the result.

The distinction matters after clean. In this example, the base plugin's clean task removes the build directory. The separate local build-cache directory remains. The next generator invocation can therefore restore the missing file from cache.

Do not say “clean clears Gradle's caches.” A task named clean can also be customized, so inspect its behavior in the project you are debugging. Our claim is deliberately about this fixture's base-plugin clean and explicit cache location.

The build-cache documentation also explains that custom task types need to opt in to output caching. Declaring a task does not automatically make it a useful cache candidate. Its output must be reproducible from the inputs Gradle knows about.

Build a Small Configuration Generator

The practice project contains settings.gradle, build.gradle, and message.txt. Use a Gradle 9.0.0 Wrapper and Java 21. The source file initially contains hello followed by a newline. No application dependencies or remote build cache are required.

Set the project name and isolate the local output cache outside build:

// settings.gradle
rootProject.name = 'cache-lab'
buildCache {
    local { directory = new File(rootDir, '.local-build-cache') }
}

Here is the complete task definition used in the experiment:

plugins { id 'base' }

@CacheableTask
abstract class GenerateConfig extends DefaultTask {
    @InputFile @PathSensitive(PathSensitivity.RELATIVE)
    abstract RegularFileProperty getSourceFile()
    @Input abstract Property<String> getLocaleName()
    @OutputFile abstract RegularFileProperty getDestination()

    @TaskAction void generate() {
        def out = destination.get().asFile
        out.parentFile.mkdirs()
        out.setText(
            "${sourceFile.get().asFile.getText('UTF-8').trim()}|${localeName.get()}\n",
            'UTF-8'
        )
    }
}

tasks.register('generateConfig', GenerateConfig) {
    sourceFile = layout.projectDirectory.file('message.txt')
    localeName = providers.gradleProperty('locale').orElse('en')
    destination = layout.buildDirectory.file('generated/config.txt')
}

The task reads only its declared properties during execution. It writes a deterministic UTF-8 string containing the trimmed source and locale. The example does not read a clock, query a service, or depend on the machine's default text encoding.

The file annotation tells Gradle which source to fingerprint. The locale annotation declares another output-affecting value. The output property identifies the artifact to check and restore. Relative path sensitivity avoids making the input's absolute checkout location part of that file property's identity; it is not a promise that every other aspect of the task is relocatable.

A production generator may require additional inputs: template files, schema versions, compiler options, or tool binaries. The correct list comes from what can change its output, not from the shortest list that produces cache hits.

Predict the Five-Run Build Matrix

Use this command shape for every run, adding the task arguments shown below:

./gradlew --console=plain --build-cache --configuration-cache generateConfig

The recorded runs used the same Gradle 9.0.0 distribution as the generated Wrapper. The table summarizes observed outcomes; full local logs and SHA-256 artifact hashes were retained. It makes no performance claim from elapsed time.

Run and changeGenerator outcomeConfiguration outcomeFile contents
1. Fresh fixture; generateConfigExecutedEntry storedhello|en
2. Repeat identical commandUP-TO-DATEEntry reusedhello|en
3. Request clean generateConfigFROM-CACHEEntry storedhello|en
4. Change message to hello again; request generateConfigExecutedEntry reusedhello again|en
5. Add a comment to settings.gradle; repeatUP-TO-DATEEntry storedhello again|en

Run 3 requests a different task set, so do not attribute its configuration behavior solely to deleting build outputs. Run 4 changes a file consumed by the task action, while leaving configuration reusable. Run 5 changes a configuration script without changing the generator's effective inputs or implementation.

The first three artifact hashes were identical, beginning 7119abb6460d. Runs 4 and 5 shared a different hash beginning 9193f0f33527. These prefixes are supporting observations, not portable magic constants: different line endings or modified fixture text would change them.

An interviewer can now change one condition and ask for a prediction. If the output file is deleted while the matching cache entry remains available, restoration is plausible. If caching is disabled, missing outputs require execution. If the task's declared input changes to a value already cached, execution is not the only possible outcome.

Can a Cache Hit Be Incorrect?

Yes. Now deliberately replace just the locale property's annotation:

@Internal abstract Property<String> getLocaleName()

This is a broken contract because the action still writes locale into the output. @Internal excludes that property from task input/output tracking; it is not a way to make meaningful inputs cheaper.

Run the broken task with -Plocale=en, then change only the command to -Plocale=fr. In our experiment, the second invocation reported UP-TO-DATE and retained hello again|en. Gradle configured the build again, yet the task's declared input comparison missed the locale change.

That combination is especially instructive. Recomputing configuration does not repair an incomplete task-input declaration. The updated property can exist in configured task state while the execution-skipping decision still ignores its output significance.

Restore @Input, run with French locale, and repeat:

Task declaration and requestObserved generator outcomeObserved artifact
Broken, -Plocale=enExecutedhello again|en
Broken, -Plocale=frUP-TO-DATEhello again|en — wrong
Repaired, -Plocale=frExecutedhello again|fr
Repaired, identical repeatUP-TO-DATEhello again|fr

The repaired French artifact's hash began 27f5ef537392. The exercise checks contents, not just task labels. A test that merely expects a cache hit would have congratulated the broken implementation.

Why Not Just Rerun Everything?

Forcing execution can help distinguish stale reuse from a faulty action, but it is a diagnostic step. If rerunning produces French text while an ordinary invocation preserves English, inspect the task's input model before adopting permanent forced execution.

Similarly, deleting all caches can remove useful evidence and make the symptom disappear temporarily. Record the command, version, declared inputs, task outcome, and artifact first. Then change one condition at a time.

The incremental-build guide provides the broader task-input and output model. A useful engineering answer explains the missing relationship: locale affects bytes, therefore locale must participate in the task's tracked inputs. “The cache was weird” leaves that relationship unexplained.

After repairing a declaration, also consider previously produced artifacts and shared cache policy in a real system. This lab uses only an isolated local directory, so it does not establish how an organization's remote cache should be invalidated or trusted.

What Makes a Task Safe to Cache?

Use the generator as a review checklist rather than memorizing annotations. Can identical declared inputs produce different bytes? Can two different meaningful inputs receive the same tracked identity? Does the declared output set capture everything consumers need?

A timestamp embedded in the generated file violates our deterministic example unless deliberately modeled or removed. A tool reading an undeclared template creates the same class of bug as the hidden locale. An absolute path written into output can make reuse across workspaces incorrect even when an input file uses relative path sensitivity.

The official build-cache concepts discuss repeatable and relocatable outputs. Those are separate questions. Repeating a build successfully in one directory is weaker evidence than restoring its output into another checkout and validating the result there.

Configuration-cache compatibility is another contract. The requirements for build logic restrict execution-time access to live Gradle model objects. Our action operates on its properties instead of reaching into Project during execution. Passing that requirement does not automatically establish task-output correctness.

When investigating a miss, start with an information-level log and the smallest task invocation. Compare the source content, property values, implementation changes, output availability, and cache configuration between runs. A miss can be correct: changing a meaningful input should prevent reuse of an incompatible artifact. Chasing a higher hit rate by suppressing inputs can turn an honest rebuild into a silent correctness bug.

Also separate evidence from inference when timing the build. A faster repeat may benefit from a warm daemon or other reused state. Our matrix establishes outcomes and bytes; a performance experiment would need controlled repeated measurements and a representative workload.

Explain the Evidence in an Interview

A concise explanation of the failure is: “The artifact depends on locale, but the task marked locale internal. Changing it did not invalidate the task's tracked state. I restored the input declaration, checked the French output, and confirmed an identical repeat could safely skip execution.”

Then state the limits. We tested one task with local caching on Gradle 9.0.0 and Java 21. We did not benchmark CI, test remote-cache authorization, or prove every plugin in a larger repository compatible. That precision makes the answer easier to trust.

QuestionWhat to explain
Design CI/CD Build CachingCache identity and artifact correctness.
Design a Dependency-Aware CI/CD PipelineTask dependencies versus reusable results.
Build a Jenkins CI for graphics testsReproducible execution evidence in CI.
Define a Git workflow for CIWhich revision and inputs a build represents.
Explain container image flow in CI/CDKeep image layers separate from Gradle task caching.

Bring the build matrix and ask a partner to change one input, output, or flag; predict the artifact before reading the task label.

Sources and Further Reading