SonarSource/sonarqube · critical · MessageException

Embedded database is not supported in cluster mode

Error message

Embedded database is not supported in cluster mode

What it means

Cluster mode requires an external database. ensureNotH2 (called from checkForApplicationNode) reads sonar.jdbc.url and throws when it is absent, blank, or points to an embedded H2 database (jdbc:h2:). H2 cannot be shared across cluster nodes, so startup is blocked rather than risking data in a non-shared embedded store.

Solutions

  1. Configure an external database: set sonar.jdbc.url (plus credentials) to PostgreSQL, SQL Server, or Oracle, e.g. jdbc:postgresql://db-host:5432/sonar.
  2. If the value exists but is wrong, replace the jdbc:h2: URL — any jdbc:h2: prefix is rejected.
  3. Never leave sonar.jdbc.url empty in cluster mode; a blank value triggers the same error.

Example fix

// before (conf/sonar.properties)
sonar.jdbc.url=jdbc:h2:tcp://localhost:9092/sonar

// after
sonar.jdbc.url=jdbc:postgresql://db.internal:5432/sonar
sonar.jdbc.username=sonar
sonar.jdbc.password=secret
Defensive patterns

Strategy: validation

Validate before calling

// shell: require an external JDBC URL in cluster mode
url=$(grep -E '^sonar\.jdbc\.url=' conf/sonar.properties | cut -d= -f2-)
case "$url" in
  jdbc:h2:*|"") echo "Cluster mode requires an external database (jdbc:postgresql://...)"; exit 1 ;;
esac

Prevention

When it happens

Trigger: accept() -> checkForApplicationNode -> ensureNotH2 throws when props value of sonar.jdbc.url is null, empty after trim, or starts with 'jdbc:h2:' on an application node with sonar.cluster.enabled=true.

Common situations: Copying a default/evaluation sonar.properties (which uses embedded H2) to a cluster application node; forgetting to configure sonar.jdbc.url entirely; leaving an old H2 URL from a trial install.

Related errors


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

Appendix: source

Thrown at server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java:199

    return trimmedValue;
  }

  private static void ensureNoSearchNodeForbiddenSettings(Props props) {
    List<String> violations = FORBIDDEN_SEARCH_NODE_SETTINGS.stream()
      .filter(setting -> props.value(setting.getKey()) != null)
      .map(Property::getKey)
      .toList();

    if (!violations.isEmpty()) {
      throw new MessageException(format("Properties [%s] are not allowed when running SonarQube in cluster mode.", String.join(", ", violations)));
    }
  }

  private static void ensureNotH2(Props props) {
    String jdbcUrl = props.value(JDBC_URL.getKey());
    String trimmedJdbcUrl = jdbcUrl == null ? null : jdbcUrl.trim();
    if (trimmedJdbcUrl == null || trimmedJdbcUrl.isEmpty() || trimmedJdbcUrl.startsWith("jdbc:h2:")) {
      throw new MessageException("Embedded database is not supported in cluster mode");
    }
  }

  private void ensureNotLoopbackAddresses(Property property, Set<AddressAndPort> hostAndPorts) {
    Set<AddressAndPort> loopbackAddresses = hostAndPorts.stream()
      .filter(t -> network.isLoopback(t.getHost()))
      .collect(toSet());
    if (!loopbackAddresses.isEmpty()) {
      throw new MessageException(format("Property %s must not contain a loopback address: %s", property.getKey(),
        loopbackAddresses.stream().map(AddressAndPort::getHost).sorted().collect(Collectors.joining(", "))));
    }
  }

  private void ensureLocalButNotLoopbackAddress(Property property, AddressAndPort addressAndPort) {
    String host = addressAndPort.getHost();
    if (!network.isLocal(host) || network.isLoopback(host)) {
      throw new MessageException(format("Property %s must be a local non-loopback address: %s", property.getKey(), addressAndPort.getHost()));
    }

View on GitHub (pinned to 184c821202)