apache/incubator-seata · error · SeataRuntimeException

ERR_CONFIG

ERR_CONFIG

Error message

Duplicate placeholders exist '{}' in bundle.

What it means

ResourceBundleUtil.parseStringValue resolves nested ${...} placeholders in seata resource bundles. It tracks each placeholder name in a visitedPlaceholders set; if the same placeholder name appears again on the current resolution path (visitedPlaceholders.add returns false), it throws SeataRuntimeException(ERR_CONFIG) declaring a duplicate/circular placeholder.

Source

Thrown at common/src/main/java/org/apache/seata/common/exception/ResourceBundleUtil.java:113

        String value = StringUtils.EMPTY;
        if (remoteBundle != null && remoteBundle.containsKey(key)) {
            value = remoteBundle.getString(key);
        }
        if (StringUtils.isEmpty(value)) {
            value = localBundle.getString(key);
        }
        return value;
    }

    protected String parseStringValue(String strVal, Set<String> visitedPlaceholders) {
        StringBuffer buf = new StringBuffer(strVal);
        int startIndex = strVal.indexOf(DEFAULT_PLACEHOLDER_PREFIX);
        while (startIndex != -1) {
            int endIndex = findPlaceholderEndIndex(buf, startIndex);
            if (endIndex != -1) {
                String placeholder = buf.substring(startIndex + DEFAULT_PLACEHOLDER_PREFIX.length(), endIndex);
                if (!visitedPlaceholders.add(placeholder)) {
                    throw new SeataRuntimeException(
                            ErrorCode.ERR_CONFIG, "Duplicate placeholders exist '" + placeholder + "' in bundle.");
                }
                placeholder = parseStringValue(placeholder, visitedPlaceholders);
                try {
                    String propVal = resolvePlaceholder(placeholder);
                    if (propVal != null) {
                        propVal = parseStringValue(propVal, visitedPlaceholders);
                        buf.replace(startIndex, endIndex + DEFAULT_PLACEHOLDER_SUFFIX.length(), propVal);
                        startIndex = buf.indexOf(DEFAULT_PLACEHOLDER_PREFIX, startIndex + propVal.length());
                    } else {
                        throw new SeataRuntimeException(
                                ErrorCode.ERR_CONFIG, "Could not resolve placeholder '" + placeholder + "'");
                    }
                } catch (Exception ex) {
                    throw new SeataRuntimeException(
                            ErrorCode.ERR_CONFIG, "Could not resolve placeholder '" + placeholder + "'");
                }
                visitedPlaceholders.remove(placeholder);

View on GitHub (pinned to e01f97c6db)

Solutions

  1. Search the loaded .properties bundle for the placeholder named in the message and break the cycle (make it reference a concrete value).
  2. Check for self-reference like key=${key} and replace the right-hand side with a literal value.
  3. Reload/restart the application after fixing the bundle so the cached parsed bundle is rebuilt.

Example fix

# before
max.active=${max.active}
thread.count=${thread.count}
thread.count=${max.active}

# after
max.active=50
thread.count=50
Defensive patterns

Strategy: validation

Validate before calling

// Before shipping bundles, detect circular placeholder references
static void checkNoCircularPlaceholders(Map<String,String> props) {
  for (String key : props.keySet()) {
    Set<String> visited = new HashSet<>>();
    String cur = key;
    while (true) {
      visited.add(cur);
      Matcher m = Pattern.compile("\\$\\{([^}]+)\\}").matcher(props.getOrDefault(cur, ""));
      if (!m.find()) break;
      String next = m.group(1);
      if (!visited.add(next)) throw new IllegalStateException("Circular placeholder: " + key);
      cur = next;
    }
  }
}

Try / catch

try {
  ResourceBundleUtil.resolve(msg, params);
} catch (SeataRuntimeException e) {
  if (e.getCode() == ErrorCode.ERR_CONFIG && e.getMessage().contains("Duplicate placeholders")) {
    // log config file path + placeholder name; fail startup fast with a clear config error
  }
  throw e;
}

Prevention

When it happens

Trigger: A resource bundle value contains a circular reference such as a=${b} and b=${a}, or the same placeholder is re-entered during nested resolution, when the bundle is first loaded and interpolated (e.g. on server or client startup reading err/resource messages).

Common situations: Editing seata .properties resource bundles and introducing a self-referencing or mutually-referencing placeholder chain; copy-paste mistakes like max.active=${max.active}.

Related errors


AI-assisted analysis of apache/incubator-seata@e01f97c6db (2026-08-14). Data as JSON: /api/errors/9e64c1d65c888938. Report an issue: GitHub.