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
- Search the loaded .properties bundle for the placeholder named in the message and break the cycle (make it reference a concrete value).
- Check for self-reference like key=${key} and replace the right-hand side with a literal value.
- 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
- Lint resource bundles in CI for self- or mutually-referencing ${...} keys.
- Never define key=${key} patterns, even as placeholders for future values.
- Keep interpolation chains one or two levels deep.
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
- Apollo configuration initialized failed,please check the val
- config type can not be null
- file not found
- not support config data type type: %s
- not support config data type suffix: %s
AI-assisted analysis of apache/incubator-seata@e01f97c6db (2026-08-14).
Data as JSON: /api/errors/9e64c1d65c888938.
Report an issue: GitHub.