{"id":"1319330f497ded58","repo":"apache/kafka","slug":"jmx-metricscontext-can-only-be-updated-before-jmx","errorCode":null,"errorMessage":"JMX MetricsContext can only be updated before JMX metrics are created","messagePattern":"JMX MetricsContext can only be updated before JMX metrics are created","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/common/metrics/JmxReporter.java","lineNumber":327,"sourceCode":"        try {\n            Pattern includePattern = Pattern.compile(include);\n            Pattern excludePattern = Pattern.compile(exclude);\n\n            return s -> includePattern.matcher(s).matches()\n                        && !excludePattern.matcher(s).matches();\n        } catch (PatternSyntaxException e) {\n            throw new ConfigException(\"JMX filter for configuration\" + METRICS_CONFIG_PREFIX\n                                      + \".(include/exclude) is not a valid regular expression\");\n        }\n    }\n\n    @Override\n    public void contextChange(MetricsContext metricsContext) {\n        String namespace = metricsContext.contextLabels().get(MetricsContext.NAMESPACE);\n        Objects.requireNonNull(namespace);\n        synchronized (LOCK) {\n            if (!mbeans.isEmpty()) {\n                throw new IllegalStateException(\"JMX MetricsContext can only be updated before JMX metrics are created\");\n            }\n\n            // prevent prefix from getting reset back to empty for backwards compatibility\n            // with the deprecated JmxReporter(String prefix) constructor, in case contextChange gets called\n            // via one of the Metrics() constructor with a default empty MetricsContext()\n            if (namespace.isEmpty()) {\n                return;\n            }\n\n            prefix = namespace;\n        }\n    }\n}\n","sourceCodeStart":309,"sourceCodeEnd":341,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/common/metrics/JmxReporter.java#L309-L341","documentation":"Thrown by JmxReporter.contextChange(MetricsContext) when the namespace is changed after at least one MBean has already been registered. The JMX namespace/prefix becomes part of every registered MBean's ObjectName, so changing it post-registration would create orphaned or duplicated MBeans. Kafka therefore mandates that the MetricsContext be finalised before any metric is added to the Metrics instance.","triggerScenarios":"Code obtains a Metrics instance, registers one or more sensors/metrics (which causes JmxReporter to add an entry to its mbeans map), then later calls metricsContext.contextChange(...) or Metrics.addReporter(JmxReporter) after the fact. The check `if (!mbeans.isEmpty())` at JmxReporter.java:326 fails and IllegalStateException is thrown.","commonSituations":"Custom code that constructs Metrics with a default MetricsContext and then tries to set the namespace after registering metrics. Embedding the Kafka producer/consumer in a framework that initialises metrics eagerly but assigns a tenant namespace lazily. Mixing the deprecated JmxReporter(String prefix) constructor with later contextChange calls.","solutions":["Set the MetricsContext namespace before any call to Metrics.addMetric / addSensor or before constructing the JmxReporter-backed Metrics instance.","If the namespace is only known late, construct the JmxReporter and Metrics lazily once the namespace is available rather than mutating an existing reporter.","Avoid mixing the deprecated JmxReporter(String) prefix constructor with contextChange; rely on MetricsContext only.","Reorder startup so contextChange is the first call on the reporter, before any metric registration."],"exampleFix":"// before:\nMetrics m = new Metrics();\nm.addSensor(\"s\"); // registers an mbean\nm.contextChange(new MetricsContext().contextLabels(Map.of(MetricsContext.NAMESPACE, \"tenantA\")));\n// after:\nMetrics m = new Metrics();\nm.contextChange(new MetricsContext().contextLabels(Map.of(MetricsContext.NAMESPACE, \"tenantA\")));\nm.addSensor(\"s\");","handlingStrategy":"validation","validationCode":"// Set MetricsContext BEFORE registering any metric/sensor.\nMetrics metrics = new Metrics(config);\nMetricsContext ctx = new KafkaMetricsContext(\"my-namespace\");\n// IMPORTANT: do this first, when no sensors have been added.\nassert metrics.metrics().isEmpty() : \"set context before adding metrics\";\n((JmxReporter) metricsReporter).contextChange(ctx); // mbeans must be empty here\n// only now: metrics.addSensor(...) / addMetric(...)","typeGuard":"// Narrow to the pre-metrics-creation window.\nboolean canUpdateMetricsContext(JmxReporter r) {\n    return r != null; // safe only if no metric has been added since reporter init\n}","tryCatchPattern":"try {\n    jmxReporter.contextChange(ctx);\n} catch (IllegalStateException e) {\n    // mbeans already populated; recreate the Metrics + reporter in the right order.\n}","preventionTips":["Call contextChange (or KafkaMetricsContext setup) at Metrics construction, before any addSensor/addMetric.","Never swap namespaces after producers/consumers/clients have started reporting.","If namespace must change, tear down and rebuild the Metrics instance and its reporters.","Treat MetricsContext as immutable-after-first-metric configuration."],"tags":["jmx","metrics","lifecycle","metrics-context","startup"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}