testcontainers/testcontainers-java · error · IllegalStateException

'/clusters/0/cluster' expected to be an object

Error message

'/clusters/0/cluster' expected to be an object

What it means

When rewriting the server URL inside the bundled kubeconfig YAML, K3sContainer parses it with Jackson and expects the path /clusters/0/cluster to be a JSON/YAML object. If the YAML shape changed (parse produced a non-object node, e.g. missing, null, or a different structure), the method aborts with IllegalStateException because it cannot safely replace the 'server' field.

Solutions

  1. Restore/keep the default kubeconfig template shipped with testcontainers-k3s so clusters[0].cluster is a mapping
  2. If customizing the YAML, ensure it contains clusters: - cluster: {server: ..., certificate-authority-data: ...} with correct indentation
  3. Inspect the parsed structure: log the YAML and confirm /clusters/0/cluster is a mapping before calling
  4. Upgrade testcontainers-k3s to a version whose bundled kubeconfig matches the code path

Example fix

// before (custom kubeconfig with wrong shape)
clusters: []

// after
clusters:
  - name: default
    cluster:
      server: https://127.0.0.1:6443
      certificate-authority-data: <base64>
Defensive patterns

Strategy: validation

Validate before calling

ObjectNode cfg = new ObjectMapper(new YAMLFactory()).readValue(kubeConfigYaml, ObjectNode.class);
if (!cfg.at("/clusters/0/cluster").isObject()) {
  throw new IllegalStateException("kubeconfig template missing clusters[0].cluster mapping");
}

Try / catch

try {
  String yaml = k3s.generateInternalKubeConfigYaml(alias);
} catch (IllegalStateException e) {
  if (e.getMessage().contains("expected to be an object")) {
    // regenerate with default bundled kubeconfig or fix the YAML template
  }
  throw e;
}

Prevention

When it happens

Trigger: kubeConfigWithServerUrl invoked (directly from generateInternalKubeConfigYaml or via containerIsStarted config update) with a kubeConfigYaml template whose 'clusters[0].cluster' entry is missing, null, or not a mapping — e.g. after editing/replacing the default kubeConfigYaml resource or an incompatible YAML layout.

Common situations: Overriding the embedded kubeconfig YAML with a custom file having a different structure; the clusters list is empty; YAML indentation mistakes making 'cluster' a scalar/string; library version whose bundled template changed.

Related errors


AI-assisted analysis of testcontainers/testcontainers-java@8e549514e3 (2026-09-12). Data as JSON: /api/errors/d22b5312110017e3. Report an issue: GitHub.

Appendix: source

Thrown at modules/k3s/src/main/java/org/testcontainers/k3s/K3sContainer.java:97

     */
    public String generateInternalKubeConfigYaml(String networkAlias) {
        if (this.getNetworkAliases().contains(networkAlias)) {
            String serverUrl = "https://" + networkAlias + ":" + KUBE_SECURE_PORT;
            return kubeConfigWithServerUrl(kubeConfigYaml, serverUrl);
        } else {
            throw new IllegalArgumentException(networkAlias + " is not a network alias for k3s container");
        }
    }

    @SneakyThrows
    private String kubeConfigWithServerUrl(String kubeConfigYaml, String serverUrl) {
        ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());

        ObjectNode kubeConfigObjectNode = objectMapper.readValue(kubeConfigYaml, ObjectNode.class);

        JsonNode clusterNode = kubeConfigObjectNode.at("/clusters/0/cluster");
        if (!clusterNode.isObject()) {
            throw new IllegalStateException("'/clusters/0/cluster' expected to be an object");
        }
        ObjectNode clusterConfig = (ObjectNode) clusterNode;
        clusterConfig.replace("server", new TextNode(serverUrl));

        kubeConfigObjectNode.set("current-context", new TextNode("default"));

        return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(kubeConfigObjectNode);
    }
}

View on GitHub (pinned to 8e549514e3)