elastic/elasticsearch · error · GradleException

Cannot read ${f} to check for duplicate license headers

Error message

Cannot read ${f} to check for duplicate license headers

What it means

checkForDuplicateHeaders iterates every source file in the configured source folders and reads each as UTF-8 to count occurrences of the Elasticsearch license header opener. If Files.readString fails on a file (it does not exist as a readable file, encoding error, or permission denial) the IOException is wrapped as this GradleException. Note the file comes from a FileCollection tree, so Gradle already enumerated it — the failure is at read time.

Source

Thrown at build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/precommit/LicenseHeadersTask.java:259

     * different headers is ambiguous about which license governs it, so we fail explicitly.
     * <p>
     * Detection strategy: count occurrences of the block-comment opener pattern
     * {@code "/*\n * Copyright Elasticsearch B.V."} in each file (after normalising line endings to LF).
     * Matching against the full opener rather than just the copyright string avoids false positives from
     * files that legitimately mention the copyright text inside string literals, text blocks, or Javadoc
     * comments (e.g. code-generators that embed a license header in the strings they emit).
     */
    private void checkForDuplicateHeaders(List<Problem> problems) {
        // The exact prefix of every Elasticsearch license block-comment header.
        final String HEADER_OPENER = "/*\n * Copyright Elasticsearch B.V.";
        for (FileCollection dirSet : getSourceFolders().get()) {
            for (File f : dirSet.getAsFileTree().matching(patternFilterable -> patternFilterable.exclude(getExcludes()))) {
                String content;
                try {
                    // Normalise Windows line endings so the pattern always contains a plain '\n'.
                    content = Files.readString(f.toPath(), StandardCharsets.UTF_8).replace("\r\n", "\n");
                } catch (IOException e) {
                    throw new GradleException("Cannot read " + f + " to check for duplicate license headers", e);
                }
                int count = 0;
                int idx = 0;
                while ((idx = content.indexOf(HEADER_OPENER, idx)) != -1) {
                    count++;
                    idx += 1;
                }
                if (count > 1) {
                    String path = f.getAbsolutePath();
                    getLogger().error("Duplicate license header in: " + path);
                    problems.add(
                        problemReporter.create(
                            ProblemId.create(
                                "duplicate-license-header",
                                "Duplicate license header",
                                ElasticsearchBuildProblems.LICENSE_HEADERS
                            ),
                            spec -> spec.contextualLabel("Duplicate license header in " + path)

View on GitHub (pinned to db6a809a66)

Solutions

  1. Open the path printed in the message and confirm it exists and is readable.
  2. Remove or fix broken symlinks in the source folders (`find . -xtype l`).
  3. Clean Gradle's input snapshots: `./gradlew --rerun-tasks` or clear the build cache.
  4. Ensure the build user has read permission on the whole source tree.

Example fix

// before: broken symlink in source tree
ln -s /nonexistent build-conventions/src/main/resources/missing.txt
// after: remove the dangling link
find . -xtype l -delete
Defensive patterns

Strategy: validation

Validate before calling

// Before running licenseHeaders, scan source folders for unreadable files
for (FileCollection fc : sourceFolders.get()) {
    for (File f : fc.getAsFileTree()) {
        if (!f.isFile() || !Files.isReadable(f.toPath())) {
            throw new GradleException("Source file not readable: " + f);
        }
    }
}

Try / catch

try {
    content = Files.readString(f.toPath(), StandardCharsets.UTF_8).replace("\r\n", "\n");
} catch (IOException e) {
    if (Files.isSymbolicLink(f.toPath()) && !Files.exists(f.toPath())) {
        getLogger().warn("Skipping broken symlink {}", f);
        continue;
    }
    throw new GradleException("Cannot read " + f + " to check for duplicate license headers", e);
}

Prevention

When it happens

Trigger: A file present in the source tree at enumeration time but unreadable when Files.readString is called: a broken symlink, a file deleted between Gradle's snapshot and the task body, a permissions change, or a non-UTF-8 byte sequence the reader rejects.

Common situations: A symlink in source folders pointing at a missing target; a stale Gradle cache referencing a file that was since removed; filesystem permissions tightened by a container user; a binary file misclassified as source.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/c46682135acedf99. Report an issue: GitHub.