{"record":{"id":"839d52a7c0428690","repo":"apache/pulsar","slug":"the-log-error-handler-cannot-be-changed-once-the-a","errorCode":null,"errorMessage":"The log error handler cannot be changed once the appender is started","messagePattern":"The log error handler cannot be changed once the appender is started","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/instance/src/main/java/org/apache/pulsar/functions/instance/LogAppender.java","lineNumber":95,"sourceCode":"    }\n\n    @Override\n    public boolean ignoreExceptions() {\n        return false;\n    }\n\n    @Override\n    public ErrorHandler getHandler() {\n        return errorHandler;\n    }\n\n    @Override\n    public void setHandler(ErrorHandler errorHandler) {\n        if (errorHandler == null) {\n            throw new RuntimeException(\"The log error handler cannot be set to null\");\n        }\n        if (isStarted()) {\n            throw new RuntimeException(\"The log error handler cannot be changed once the appender is started\");\n        }\n        this.errorHandler = errorHandler;\n    }\n\n    @Override\n    public State getState() {\n        return state;\n    }\n\n    @Override\n    public void initialize() {\n        this.state = State.INITIALIZED;\n    }\n\n    @Override\n    public void start() {\n        this.state = State.STARTING;\n        try {","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/instance/src/main/java/org/apache/pulsar/functions/instance/LogAppender.java#L77-L113","documentation":"LogAppender is a lifecycle-managed component: once start() has created the log-topic Producer (state == STARTED), swapping the ErrorHandler would leave the producer's internal failure callbacks pointing at the stale handler. setHandler therefore rejects the change with RuntimeException until stop() is called.","triggerScenarios":"Calling setHandler after start() on the same LogAppender instance; runtime code or user harness attempting to hot-swap error handling while the appender is running.","commonSituations":"Reconfiguring logging at runtime in a long-lived function instance; test code reusing an appender across cases without stopping it; framework upgrade path calling setHandler during restart logic while the producer is still open.","solutions":["Call stop() first, then setHandler(), then start() again to apply a new handler","Set the handler immediately after constructing the LogAppender and before any start() call","Restructure code so the ErrorHandler is created once with mutable internals (e.g. delegate to an AtomicReference) if dynamic behavior is needed"],"exampleFix":"// before\nappender.start();\nappender.setHandler(newHandler); // throws\n// after\nappender.setHandler(newHandler);\nappender.start();","handlingStrategy":"validation","validationCode":"if (logAppender.getState() == State.STARTED) {\n    logAppender.stop();\n}\nlogAppender.setHandler(newHandler);\nlogAppender.start();","typeGuard":null,"tryCatchPattern":"try { logAppender.setHandler(newHandler); }\ncatch (RuntimeException e) {\n  if (e.getMessage().contains(\"cannot be changed once the appender is started\")) {\n    log.error(\"stop() the appender before swapping handlers\", e);\n  }\n}","preventionTips":["Set the handler once, immediately after construction and before start()","Check getState() before mutating configuration","If dynamic behavior is needed, use a delegating ErrorHandler with an AtomicReference","Ensure stop() is called in teardown before reconfiguration"],"tags":["java","pulsar-functions","logging","lifecycle","illegal-state"],"backgroundTag":"illegal-lifecycle-state","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}