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
- Open the path printed in the message and confirm it exists and is readable.
- Remove or fix broken symlinks in the source folders (`find . -xtype l`).
- Clean Gradle's input snapshots: `./gradlew --rerun-tasks` or clear the build cache.
- 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
- Keep broken symlinks out of source trees (`find . -xtype l`).
- Run the build as a user with read access to the whole tree.
- Use `--rerun-tasks` if stale snapshots reference deleted files.
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
- Cannot generate license header report for ${path}
- Error parsing xml report ${xmlReportFileAbsolutePath}
- IO problem while reading files with API signatures.
- Failed to load one of the given class files.
- unable to read the file {} attributes
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/c46682135acedf99.
Report an issue: GitHub.