{"record":{"id":"a9ac66e01d9a3506","repo":"apache/iceberg","slug":"noop-counter-has-no-value","errorCode":null,"errorMessage":"NOOP counter has no value","messagePattern":"NOOP counter has no value","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/org/apache/iceberg/metrics/DefaultCounter.java","lineNumber":39,"sourceCode":"import java.util.Optional;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.concurrent.atomic.LongAdder;\nimport org.apache.iceberg.metrics.MetricsContext.Unit;\nimport org.apache.iceberg.relocated.com.google.common.base.Preconditions;\n\n/** A default {@link Counter} implementation that uses an {@link AtomicLong} to count events. */\npublic class DefaultCounter implements Counter {\n  public static final Counter NOOP =\n      new DefaultCounter(Unit.UNDEFINED) {\n        @Override\n        public void increment() {}\n\n        @Override\n        public void increment(long amount) {}\n\n        @Override\n        public long value() {\n          throw new UnsupportedOperationException(\"NOOP counter has no value\");\n        }\n\n        @Override\n        public String toString() {\n          return \"NOOP counter\";\n        }\n      };\n\n  private final LongAdder counter;\n  private final MetricsContext.Unit unit;\n  private AsIntCounter asIntCounter = null;\n  private AsLongCounter asLongCounter = null;\n\n  DefaultCounter(MetricsContext.Unit unit) {\n    Preconditions.checkArgument(null != unit, \"Invalid count unit: null\");\n    this.unit = unit;\n    this.counter = new LongAdder();\n  }","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/api/src/main/java/org/apache/iceberg/metrics/DefaultCounter.java#L21-L57","documentation":"DefaultCounter.NOOP is a non-recording counter; increment() discards data, so value() has no meaningful result and throws UnsupportedOperationException. toString is overridden to render 'NOOP counter' so callers who stringify never hit the throw. Calling value() on the NOOP counter is a programming error: metrics were explicitly disabled or routed to a no-op context.","triggerScenarios":"Calling value() on DefaultCounter.NOOP (obtained via LoggingContext/SimpleContext unsupported defaults or directly), typically for logging or assertions on the metric value.","commonSituations":"Code that assumes a real counter was returned from MetricsContext.counter(); tests asserting on metric values while metrics are disabled; sharing a counter across code paths where one path is no-op.","solutions":["Do not read value() from a NOOP counter; guard on the counter instance before reading","Use a real MetricsContext (e.g. DefaultMetricsContext) if the value is needed","Log the counter via toString() (renders 'NOOP counter') instead of value()","Check whether metrics are intentionally disabled via configuration before consuming values"],"exampleFix":"// before\nlong v = counter.value();\n// after\nif (counter != DefaultCounter.NOOP) {\n  long v = counter.value();\n}","handlingStrategy":"type-guard","validationCode":"if (counter == DefaultCounter.NOOP) { skip; }","typeGuard":"boolean isNoopCounter(Counter<?> c) { return c == DefaultCounter.NOOP; }","tryCatchPattern":"try { long v = counter.value(); } catch (UnsupportedOperationException e) { log.info(\"metrics disabled\"); }","preventionTips":["Guard value() reads when metrics may be disabled","Prefer toString for display of counters","Use DefaultMetricsContext when values matter","Keep NOOP usage centralized"],"tags":["java","metrics","unsupported-operation"],"backgroundTag":"unsupported-operation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}