{"record":{"id":"9fcac9d46d4b25a7","repo":"nathanmarz/storm","slug":"topologycontext-registermetric-can-only-be-called-from","errorCode":null,"errorMessage":"TopologyContext.registerMetric can only be called from within overridden IBolt::prepare() or ISpout::open() method.","messagePattern":"TopologyContext\\.registerMetric can only be called from within overridden IBolt::prepare\\(\\) or ISpout::open\\(\\) method\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"storm-core/src/jvm/backtype/storm/task/TopologyContext.java","lineNumber":230,"sourceCode":"    public void addTaskHook(ITaskHook hook) {\n        hook.prepare(_stormConf, this);\n        _hooks.add(hook);\n    }\n    \n    public Collection<ITaskHook> getHooks() {\n        return _hooks;\n    }\n\n    /*\n     * Register a IMetric instance. \n     * Storm will then call getValueAndReset on the metric every timeBucketSizeInSecs\n     * and the returned value is sent to all metrics consumers.\n     * You must call this during IBolt::prepare or ISpout::open.\n     * @return The IMetric argument unchanged.\n     */\n    public <T extends IMetric> T registerMetric(String name, T metric, int timeBucketSizeInSecs) {\n        if((Boolean)_openOrPrepareWasCalled.deref() == true) {\n            throw new RuntimeException(\"TopologyContext.registerMetric can only be called from within overridden \" + \n                                       \"IBolt::prepare() or ISpout::open() method.\");\n        }\n        \n        Map m1 = _registeredMetrics;\n        if(!m1.containsKey(timeBucketSizeInSecs)) {\n            m1.put(timeBucketSizeInSecs, new HashMap());\n        }\n\n        Map m2 = (Map)m1.get(timeBucketSizeInSecs);\n        if(!m2.containsKey(_taskId)) {\n            m2.put(_taskId, new HashMap());\n        }\n\n        Map m3 = (Map)m2.get(_taskId);\n        if(m3.containsKey(name)) {\n            throw new RuntimeException(\"The same metric name `\" + name + \"` was registered twice.\" );\n        } else {\n            m3.put(name, metric);","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/nathanmarz/storm/blob/cdb116e942666973bc4eaa0df098d5bab82739e7/storm-core/src/jvm/backtype/storm/task/TopologyContext.java#L212-L248","documentation":"TopologyContext.registerMetric must be called during IBolt.prepare() or ISpout.open(); Storm tracks via an AtomicBoolean (_openOrPrepareWasCalled) when those lifecycle methods complete. Calling registerMetric afterwards (e.g. in execute/nextTuple) throws this RuntimeException because metrics must be registered before the metrics polling infrastructure starts consuming them.","triggerScenarios":"Calling topologyContext.registerMetric(...) outside prepare/open — most commonly in execute(), nextTuple(), cleanup(), or an executor thread spawned by prepare after prepare returned.","commonSituations":"Developers lazily registering metrics on first tuple processed; registering metrics in a background thread started from prepare (the flag is already true by the time the thread registers); migrating metrics code from prepare into execute during refactors.","solutions":["Move all registerMetric calls into the body of prepare() (bolt) or open() (spout), before it returns.","If metrics depend on async setup, register placeholder metrics in prepare and update their state later instead of registering late.","If background threads need metrics, create and register the IMetric instance in prepare, then hand the instance to the thread.","Use a different metrics mechanism (e.g. own reporter) for anything that must be initialized after startup."],"exampleFix":"// before\npublic void execute(Tuple tuple) {\n    if (counters == null) {\n        counters = context.registerMetric(\"counters\", new CountMetric(), 10); // throws\n    }\n}\n\n// after\npublic void prepare(Map conf, TopologyContext context, OutputCollector collector) {\n    counters = context.registerMetric(\"counters\", new CountMetric(), 10);\n}\npublic void execute(Tuple tuple) { counters.incr(); }","handlingStrategy":"validation","validationCode":"// before calling registerMetric outside lifecycle:\n// ensure you're inside prepare()/open(); if not, defer registration\nboolean inLifecycle = Thread.currentThread().getName().startsWith(\"main\");","typeGuard":null,"tryCatchPattern":"try {\n    context.registerMetric(name, metric, interval);\n} catch (RuntimeException e) {\n    if (e.getMessage().contains(\"registerMetric can only be called\")) {\n        LOG.warn(\"metric {} registered too late; skipping\", name);\n    } else { throw e; }\n}","preventionTips":["Register all metrics at the top of prepare()/open().","Never register metrics in execute()/nextTuple() or background threads.","Pass pre-registered metric instances to worker threads instead of registering there."],"tags":["storm","metrics","lifecycle","topology-context"],"backgroundTag":"invalid-state-transition","analyzedSha":"cdb116e942666973bc4eaa0df098d5bab82739e7","analyzedAt":"2026-09-12T14:30:00.714Z","contentChangedAt":"2026-09-12T14:30:00.714Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}