SonarSource/sonarqube · error · IOException
Directory ' ' is a symbolic link
Error message
Directory '%s' is a symbolic link
What it means
FileUtils2.deleteDirectory refuses to delete anything that is a symbolic link: before recursing it checks Files.isSymbolicLink and throws an IOException formatted with the directory path. This is a safety guard so a symlink pointing elsewhere (e.g. /) is never followed and recursively deleted.
Solutions
- Delete the symlink itself if intended (Files.delete / rm) rather than expecting recursive deletion
- Point the configuration at the real directory path instead of a symlinked path
- If symlink indirection is required, restructure so the parent directory is real and only contents are linked
- Handle/expect the IOException in code that cleans directories and decide explicitly how to treat symlinks
Example fix
// before rm -rf /opt/sonarqube/temp # temp -> /mnt/bigdisk/temp (symlink) // after rm /opt/sonarqube/temp # remove link itself rm -rf /mnt/bigdisk/temp/ # clean the real target explicitly
Defensive patterns
Strategy: try-catch
Validate before calling
if (Files.isSymbolicLink(directory.toPath()))
throw new IllegalArgumentException("Refusing to delete symlinked dir: " + directory); Try / catch
try { FileUtils2.deleteDirectory(dir); } catch (IOException e) {
if (e.getMessage() != null && e.getMessage().contains("symbolic link")) {
Files.delete(dir.toPath()); // remove the link itself
} else { throw e; }
} Prevention
- Avoid symlinking sonarqube data/temp/logs directories to other volumes; use mounts instead
- Check for symlinks in deployment layout before automated cleanup
- Treat deleteDirectory as refusing links by design; handle that case explicitly
- Resolve real paths (toRealPath) in your own tooling before recursive deletes
When it happens
Trigger: deleteDirectory (or deleteQuietly) is called with a File that resolves to a symlink, e.g. a temp dir path that is a symlink, or a project/web dir replaced by a symlink during deployment.
Common situations: Deployments where /opt/sonarqube/data or temp dirs are symlinked to another volume; users symlinking logs/temp for space management; containers mounting volumes through symlinks.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Can not delete
- Can not move file to
- Can not open file
- Can not unzip file to directory
- Can not write to file
AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09).
Data as JSON: /api/errors/3f884f30fa841769.
Report an issue: GitHub.
Appendix: source
Thrown at server/sonar-process/src/main/java/org/sonar/process/FileUtils2.java:116
}
}
/**
* Deletes a directory recursively. Does not support symbolic link to directories.
*
* @param directory directory to delete
* @throws IOException in case deletion is unsuccessful
*/
public static void deleteDirectory(File directory) throws IOException {
requireNonNull(directory, DIRECTORY_CAN_NOT_BE_NULL);
if (!directory.exists()) {
return;
}
Path path = directory.toPath();
if (Files.isSymbolicLink(path)) {
throw new IOException(format("Directory '%s' is a symbolic link", directory));
}
if (directory.isFile()) {
throw new IOException(format("Directory '%s' is a file", directory));
}
deleteDirectoryImpl(path);
}
/**
* Size of file or directory, in bytes. In case of a directory,
* the size is the sum of the sizes of all files recursively traversed.
*
* This implementation is recommended over commons-io
* {@code FileUtils#sizeOf(File)} which suffers from slow usage of Java IO.
*
* @throws IOException if files can't be traversed or size attribute is not present
* @see BasicFileAttributes#size()
*/
public static long sizeOf(Path path) throws IOException {View on GitHub (pinned to 184c821202)