{"record":{"id":"2a341f14e8cb65e0","repo":"elastic/elasticsearch","slug":"sniffer-can-only-be-set-once","errorCode":null,"errorMessage":"sniffer can only be set once","messagePattern":"sniffer can only be set once","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"client/sniffer/src/main/java/org/elasticsearch/client/sniff/SniffOnFailureListener.java","lineNumber":52,"sourceCode":"public class SniffOnFailureListener extends RestClient.FailureListener {\n\n    private volatile Sniffer sniffer;\n    private final AtomicBoolean set;\n\n    public SniffOnFailureListener() {\n        this.set = new AtomicBoolean(false);\n    }\n\n    /**\n     * Sets the {@link Sniffer} instance used to perform sniffing\n     * @throws IllegalStateException if the sniffer was already set, as it can only be set once\n     */\n    public void setSniffer(Sniffer sniffer) {\n        Objects.requireNonNull(sniffer, \"sniffer must not be null\");\n        if (set.compareAndSet(false, true)) {\n            this.sniffer = sniffer;\n        } else {\n            throw new IllegalStateException(\"sniffer can only be set once\");\n        }\n    }\n\n    @Override\n    public void onFailure(Node node) {\n        if (sniffer == null) {\n            throw new IllegalStateException(\"sniffer was not set, unable to sniff on failure\");\n        }\n        sniffer.sniffOnFailure();\n    }\n}\n","sourceCodeStart":34,"sourceCodeEnd":64,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/client/sniffer/src/main/java/org/elasticsearch/client/sniff/SniffOnFailureListener.java#L34-L64","documentation":"SniffOnFailureListener.setSniffer may be called at most once (guarded by an AtomicBoolean). A second call raises IllegalStateException because the listener is designed to be bound to exactly one Sniffer for its lifetime.","triggerScenarios":"Calling setSniffer(sniffer1) then setSniffer(sniffer2) on the same listener instance; re-binding during a reconfiguration hot-reload.","commonSituations":"Re-initialisation code that builds a new Sniffer on config change but reuses the same listener; multiple modules each trying to register their sniffer.","solutions":["Create a fresh SniffOnFailureListener for each new Sniffer rather than reusing the old one.","Track the current listener and reset it together with the Sniffer and RestClient on reconfiguration.","If you need to swap sniffers, also swap the listener (it is cheap)."],"exampleFix":"// before\nSniffOnFailureListener listener = new SniffOnFailureListener();\nclient1 = ...; listener.setSniffer(Sniffer.builder(client1).build());\nclient2 = ...; listener.setSniffer(Sniffer.builder(client2).build()); // throws\n// after\nSniffOnFailureListener listener = new SniffOnFailureListener();\nclient2 = ...; listener.setSniffer(Sniffer.builder(client2).build()); // new listener per client","handlingStrategy":"validation","validationCode":"if (listenerAlreadyBound) throw new IllegalStateException(\"create a new SniffOnFailureListener instead of rebinding\");\nlistener.setSniffer(sniffer);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat SniffOnFailureListener as single-use: build a new one whenever you build a new Sniffer.","Reset listener + sniffer + client together on reconfiguration."],"tags":["sniffer","lifecycle","listener","configuration"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}