{"record":{"id":"a7406e7d0e9a6f31","repo":"apache/pulsar","slug":"a-v5-authentication-plugin-v5authentication-get","errorCode":null,"errorMessage":"A v5 authentication plugin (${v5Authentication.getClass().getName}) cannot be serialized with the client configuration. Configure authentication with authPluginClassName + authParams instead of a pre-built plugin instance when the configuration has to cross a boundary.","messagePattern":"A v5 authentication plugin \\((.+?)\\) cannot be serialized with the client configuration\\. Configure authentication with authPluginClassName \\+ authParams instead of a pre-built plugin instance when the configuration has to cross a boundary\\.","errorType":"exception","errorClass":"NotSerializableException","httpStatus":null,"severity":"error","filePath":"pulsar-client/src/main/java/org/apache/pulsar/client/impl/conf/ClientConfigurationData.java","lineNumber":594,"sourceCode":"            return operationTimeoutMs;\n        }\n    }\n\n    /**\n     * Refuse to serialize a configuration whose authentication would not survive the round trip.\n     *\n     * <p>A v5 authentication plugin is not {@link Serializable} and this slot is {@code transient}, so\n     * serializing would drop it silently and the deserialized configuration would authenticate as nobody —\n     * an authentication downgrade discovered as a broker rejection, far from its cause. The string form\n     * ({@code authPluginClassName} + {@code authParams}) does survive, and is what a remote or forked\n     * context should be configured with, so a configuration carrying one is allowed through.\n     *\n     * @param out the object output stream\n     * @throws IOException if the configuration cannot be written\n     */\n    private void writeObject(java.io.ObjectOutputStream out) throws IOException {\n        if (v5Authentication != null && StringUtils.isBlank(authPluginClassName)) {\n            throw new NotSerializableException(\"A v5 authentication plugin (\"\n                    + v5Authentication.getClass().getName() + \") cannot be serialized with the client \"\n                    + \"configuration. Configure authentication with authPluginClassName + authParams instead \"\n                    + \"of a pre-built plugin instance when the configuration has to cross a boundary.\");\n        }\n        out.defaultWriteObject();\n    }\n\n    public ClientConfigurationData clone() {\n        try {\n            return (ClientConfigurationData) super.clone();\n        } catch (CloneNotSupportedException e) {\n            throw new RuntimeException(\"Failed to clone ClientConfigurationData\");\n        }\n    }\n\n    public InetSocketAddress getSocks5ProxyAddress() {\n        if (Objects.nonNull(socks5ProxyAddress)) {\n            return socks5ProxyAddress;","sourceCodeStart":576,"sourceCodeEnd":612,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client/src/main/java/org/apache/pulsar/client/impl/conf/ClientConfigurationData.java#L576-L612","documentation":"ClientConfigurationData is Java-serializable, but a pre-built v5 Authentication plugin instance held in the v5Authentication field cannot safely cross a serialization boundary (it holds live state, threads, secrets). During writeObject, if a plugin instance is set and no authPluginClassName is configured, serialization aborts with NotSerializableException, telling you to express auth as class name + params instead.","triggerScenarios":"Serializing (java.io.ObjectOutputStream) a ClientConfigurationData whose v5Authentication field holds a plugin instance while authPluginClassName is blank — e.g. shipping the config object over the wire, caching it, or deep-copying via serialization.","commonSituations":"Frameworks or proxies that serialize client configs between processes; tests that serialize/deserialize configuration; storing configs in a serialized session state after programmatically setting a v5 plugin instance.","solutions":["Set authentication via authPluginClassName + authParams (the string form) before serializing, instead of a pre-built plugin instance.","If you built the plugin programmatically, extract its class name and parameters and configure those on the copy you serialize.","If you must keep the instance, exclude it from serialization (null it out on a copy) and re-instantiate it after deserialization."],"exampleFix":"// before\nconf.setAuthentication(myPluginInstance); // then serialize conf\n// after\nconf.setAuthPluginClassName(\"org.example.MyAuthPlugin\");\nconf.setAuthParams(\"param1=value1\");","handlingStrategy":"validation","validationCode":"if (conf.getAuthentication() != null /* v5 instance */\n        && (conf.getAuthPluginClassName() == null || conf.getAuthPluginClassName().isBlank())) {\n    throw new IllegalStateException(\"set authPluginClassName + authParams before serializing config\");\n}","typeGuard":null,"tryCatchPattern":"try (java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(out)) {\n    oos.writeObject(conf);\n} catch (java.io.NotSerializableException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"A v5 authentication plugin\")) {\n        // rebuild conf with authPluginClassName + authParams and retry\n    } else throw e;\n}","preventionTips":["Configure authentication by class name + params whenever the config may be serialized, cached, or sent across a process boundary.","Keep pre-built plugin instances only in the live client, never in shared/serializable config objects.","Serialize a copy with the plugin field nulled and re-attach the plugin after deserialization."],"tags":["serialization","not-serializable","authentication","configuration"],"backgroundTag":"not-serializable-auth-plugin","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}