SonarSource/sonarqube · critical · IllegalStateException

Fail to create or clean-up directory

Error message

Fail to create or clean-up directory %s

What it means

TomcatContexts.addStaticDir serves SonarQube's static content from a filesystem directory. It first calls Fs.createOrCleanupDir (forcing directory creation/cleanup via FileUtils.forceMkdir); if that raises IOException, the directory could not be created or cleaned and an IllegalStateException wrapping it is thrown, failing server startup.

Solutions

  1. Check permissions on the directory's parent and ensure the sonar process user can write there; chown/chmod as needed
  2. Delete any plain FILE named like the expected directory so it can be created as a directory
  3. Ensure the volume is writable and has free space (Docker: use a writable volume, not a read-only mount)

Example fix

// before
sudo -u otheruser ./sonar.sh start  # user can't write to install dir
// after
sudo chown -R sonar:sonar /opt/sonarqube && sudo -u sonar ./sonar.sh start
Defensive patterns

Strategy: validation

Validate before calling

File dir = new File(home, "web");
if (dir.exists() && !dir.isDirectory()) throw new IllegalStateException(dir + " exists and is not a directory");
if (!dir.exists() && !dir.getParentFile().canWrite()) throw new IllegalStateException("No write permission on " + dir.getParentFile());

Try / catch

try { server.start(); } catch (IllegalStateException e) { if (e.getMessage().contains("Fail to create or clean-up directory")) { log.error("Check permissions/ownership of the SonarQube install dir for the process user"); } throw e; }

Prevention

When it happens

Trigger: The static-content directory (e.g. $SONARQUBE_HOME/web) cannot be created because of insufficient filesystem permissions, a read-only volume, or because a non-directory FILE already exists at that path (cleanup fails).

Common situations: Running the SonarQube process as a user without write access to the installation directory; installing over an existing 'web' file; read-only Docker layer or mounted volume; disk full.

Understand the failure class

Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.

Related errors


AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09). Data as JSON: /api/errors/d99f18f7e60550ff. Report an issue: GitHub.

Appendix: source

Thrown at server/sonar-webserver/src/main/java/org/sonar/server/app/TomcatContexts.java:94

  }

  static String getContextPath(Props props) {
    String context = props.value(WEB_CONTEXT.getKey(), "");
    if ("/".equals(context)) {
      context = "";
    } else if (!"".equals(context) && context != null && !context.startsWith("/")) {
      throw MessageException.of(format("Value of '%s' must start with a forward slash: '%s'", WEB_CONTEXT.getKey(), context));
    }
    return context;
  }

  @VisibleForTesting
  StandardContext addStaticDir(Tomcat tomcat, String contextPath, File dir) {
    try {
      fs.createOrCleanupDir(dir);
      configureDeployErrorPages(dir, contextPath);
    } catch (IOException e) {
      throw new IllegalStateException(format("Fail to create or clean-up directory %s", dir.getAbsolutePath()), e);
    }

    return addContext(tomcat, contextPath, dir);
  }

  private static StandardContext addContext(Tomcat tomcat, String contextPath, File dir) {
    try {
      StandardContext context = (StandardContext) tomcat.addWebapp(contextPath, dir.getAbsolutePath());
      context.setClearReferencesHttpClientKeepAliveThread(false);
      context.setClearReferencesStopThreads(false);
      context.setClearReferencesStopTimerThreads(false);
      context.setClearReferencesStopTimerThreads(false);
      context.setAntiResourceLocking(false);
      context.setReloadable(false);
      context.setUseHttpOnly(true);
      context.setTldValidation(false);
      context.setXmlValidation(false);
      context.setXmlNamespaceAware(false);

View on GitHub (pinned to 184c821202)