redis/jedis · error · JedisValidationException

Endpoint already exists in the provider

Error message

Endpoint ${config.getEndpoint()} already exists in the provider

What it means

Internal addDatabaseInternal() re-checks endpoint uniqueness before registering a database: if the endpoint is already in databaseMap it throws a JedisValidationException. This is the lock-protected internal path used by add(), so it surfaces the same duplicate-endpoint rule that add() checks, plus is the direct guard for constructor-time registrations.

Solutions

  1. Deduplicate endpoints in MultiDbConfig before constructing the provider.
  2. Serialize add() calls or check an application-level endpoint set before calling add().
  3. Catch JedisValidationException for 'already exists' and treat it as idempotent success if appropriate.

Example fix

// before
new MultiDbConnectionProvider(MultiDbConfig.builder(ep1).addDatabase(ep1Dup)...build(), cache);

// after
Set<Endpoint> seen = new HashSet<>();
MultiDbConfig.Builder b = MultiDbConfig.builder(ep1); seen.add(ep1);
if (seen.add(ep1Dup)) b.addDatabase(ep1Dup); // skip duplicates
Defensive patterns

Strategy: validation

Validate before calling

Set<Endpoint> seen = new HashSet<>();
for (DatabaseConfig cfg : configs) {
  if (!seen.add(cfg.getEndpoint())) continue; // skip duplicates
  provider.add(cfg);
}

Try / catch

try {
  provider.add(cfg);
} catch (JedisValidationException e) {
  if (e.getMessage().contains("already exists")) {
    // concurrent duplicate add; ignore
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Adding a DatabaseConfig whose endpoint already exists — via provider.add(config) racing another add, or during provider construction when initial config lists a duplicate endpoint.

Common situations: Concurrent add() calls from multiple threads adding the same endpoint; configuration files listing the same host:port more than once; re-adding an endpoint after a failed removal.

Related errors


AI-assisted analysis of redis/jedis@6dac31d4c2 (2026-09-08). Data as JSON: /api/errors/5002eb611f26979e. Report an issue: GitHub.

Appendix: source

Thrown at src/main/java/redis/clients/jedis/mcf/MultiDbConnectionProvider.java:323

      if (databaseToRemove != null) {
        databaseToRemove.setDisabled(true);
        databaseToRemove.close();
      }
    } finally {
      activeDatabaseChangeLock.unlock();
    }
    if (notificationData != null) {
      onDatabaseSwitch(SwitchReason.FORCED, notificationData.getKey(), notificationData.getValue());
    }
  }

  /**
   * Internal method to add a database configuration. This method is not thread-safe and should be
   * called within appropriate locks.
   */
  private void addDatabaseInternal(MultiDbConfig multiDbConfig, DatabaseConfig config) {
    if (databaseMap.containsKey(config.getEndpoint())) {
      throw new JedisValidationException(
          "Endpoint " + config.getEndpoint() + " already exists in the provider");
    }

    String databaseId = "database:" + config.getEndpoint();

    Retry retry = RetryRegistry.of(retryConfig).retry(databaseId);

    Retry.EventPublisher retryPublisher = retry.getEventPublisher();
    retryPublisher.onRetry(event -> log.warn(String.valueOf(event)));
    retryPublisher.onError(event -> log.error(String.valueOf(event)));

    CircuitBreaker circuitBreaker = CircuitBreakerRegistry.of(circuitBreakerConfig)
        .circuitBreaker(databaseId);

    CircuitBreaker.EventPublisher circuitBreakerEventPublisher = circuitBreaker.getEventPublisher();
    circuitBreakerEventPublisher.onCallNotPermitted(event -> log.error(String.valueOf(event)));
    circuitBreakerEventPublisher.onError(event -> log.error(String.valueOf(event)));
    circuitBreakerEventPublisher.onFailureRateExceeded(event -> log.error(String.valueOf(event)));

View on GitHub (pinned to 6dac31d4c2)