apache/iceberg · error · ConfigException

${msg}

Error message

${msg}

What it means

IcebergSinkConfig.checkState is a helper that converts a failed boolean condition during configuration validation into a ConfigException with the given message. The thrown message '${msg}' is whatever validation text was passed in — e.g. 'Cannot specify both static and dynamic table names' or 'Must specify a route field if using dynamic table names'.

Source

Thrown at kafka-connect/kafka-connect/src/main/java/org/apache/iceberg/connect/IcebergSinkConfig.java:300

    validate();
  }

  private void validate() {
    checkState(!catalogProps().isEmpty(), "Must specify Iceberg catalog properties");
    if (tables() != null) {
      checkState(!dynamicTablesEnabled(), "Cannot specify both static and dynamic table names");
    } else if (dynamicTablesEnabled()) {
      checkState(
          tablesRouteField() != null, "Must specify a route field if using dynamic table names");
    } else {
      throw new ConfigException("Must specify table name(s)");
    }
  }

  private void checkState(boolean condition, String msg) {
    if (!condition) {
      throw new ConfigException(msg);
    }
  }

  public String connectorName() {
    return originalProps.get(NAME_PROP);
  }

  public String taskId() {
    return originalProps.get(TASK_ID);
  }

  public String transactionalSuffix() {
    // this is for internal use and is not part of the config definition...
    return originalProps.get(INTERNAL_TRANSACTIONAL_SUFFIX_PROP);
  }

  public Map<String, String> catalogProps() {
    return catalogProps;

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. If the message says both were specified, remove 'iceberg.tables' or set 'iceberg.tables.dynamic-enabled=false'.
  2. If the message mentions a route field, set 'iceberg.tables.route-field' to the record field that determines the target table.
  3. Read the ConfigException message itself — it names the exact violated rule.

Example fix

// before
"iceberg.tables": "db.t1",
"iceberg.tables.dynamic-enabled": "true"
// after
"iceberg.tables.dynamic-enabled": "true",
"iceberg.tables.route-field": "targetTable"
Defensive patterns

Strategy: validation

Validate before calling

// Reject mutually exclusive / incomplete dynamic routing config up front
boolean dyn = Boolean.parseBoolean(config.getOrDefault("iceberg.tables.dynamic-enabled", "false"));
if (dyn && config.get("iceberg.tables") != null) throw new IllegalArgumentException("remove iceberg.tables when dynamic routing is enabled");
if (dyn && config.get("iceberg.tables.route-field") == null) throw new IllegalArgumentException("iceberg.tables.route-field is required for dynamic routing");

Try / catch

try { new IcebergSinkConfig(props); } catch (org.apache.kafka.common.config.ConfigException e) { log.error("Config rule violated: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Called from validate() when: both iceberg.tables and iceberg.tables.dynamic-enabled=true are set, or dynamic tables are enabled without iceberg.tables.route-field.

Common situations: Developers partially migrating between static and dynamic table routing — enabling dynamic mode while leaving static tables configured, or enabling dynamic mode without specifying the record field used for routing.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/a1d38fefd6bd52a8. Report an issue: GitHub.