{"record":{"id":"f4fe3d13bd5a5dc6","repo":"apache/pulsar","slug":"cryptokeyreader-class-name-required","errorCode":null,"errorMessage":"CryptoKeyReader class name required","messagePattern":"CryptoKeyReader class name required","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java","lineNumber":924,"sourceCode":"            if (filename.contains(\"..\")) {\n                throw new IllegalArgumentException(\"Invalid filename: \" + filename);\n            }\n\n            if (!new File(filename).exists()) {\n                throw new IllegalArgumentException(\"The supplied go file does not exist\");\n            }\n        }\n\n        if (functionConfig.getInputSpecs() != null) {\n            functionConfig.getInputSpecs().forEach((topicName, conf) -> {\n                // receiver queue size should be >= 0\n                if (conf.getReceiverQueueSize() != null && conf.getReceiverQueueSize() < 0) {\n                    throw new IllegalArgumentException(\n                        \"Receiver queue size should be >= zero\");\n                }\n\n                if (conf.getCryptoConfig() != null && isBlank(conf.getCryptoConfig().getCryptoKeyReaderClassName())) {\n                    throw new IllegalArgumentException(\n                            \"CryptoKeyReader class name required\");\n                }\n                if (conf.getMessagePayloadProcessorConfig() != null && isBlank(\n                        conf.getMessagePayloadProcessorConfig().getClassName())) {\n                    throw new IllegalArgumentException(\n                            \"MessagePayloadProcessor class name required\");\n                }\n            });\n        }\n\n        if (functionConfig.getProducerConfig() != null\n                && functionConfig.getProducerConfig().getCryptoConfig() != null) {\n            if (isBlank(functionConfig.getProducerConfig().getCryptoConfig().getCryptoKeyReaderClassName())) {\n                throw new IllegalArgumentException(\"CryptoKeyReader class name required\");\n            }\n\n            if (functionConfig.getProducerConfig().getCryptoConfig().getEncryptionKeys() == null\n                    || functionConfig.getProducerConfig().getCryptoConfig().getEncryptionKeys().length == 0) {","sourceCodeStart":906,"sourceCodeEnd":942,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java#L906-L942","documentation":"If an InputSpec for a topic carries a CryptoConfig (input decryption), the cryptoKeyReaderClassName must be a non-blank string naming a class that implements CryptoKeyReader. A blank/null name means Pulsar could not decrypt messages, so doCommonChecks throws IllegalArgumentException('CryptoKeyReader class name required').","triggerScenarios":"Setting InputSpec.setCryptoConfig(new CryptoConfig()) (or with setCryptoKeyReaderClassName(\"\")/whitespace) on any function input topic, then calling createFunction/updateFunction.","commonSituations":"Enabling input decryption but forgetting to point at the key reader implementation; crypto config copied from producer config where the key was set elsewhere; blank string produced by environment-variable substitution that failed; class name left as template placeholder.","solutions":["Set the fully qualified class name of your CryptoKeyReader implementation: cryptoConfig.setCryptoKeyReaderClassName(\"com.example.MyKeyReader\")","Ensure the implementation class is available on the function worker/instance classpath (nar/jar shipping)","If input decryption is not needed, remove the CryptoConfig from the InputSpec entirely","Check that the property/env substitution feeding the class name actually resolves to a non-blank value"],"exampleFix":"// before\nCryptoConfig crypto = new CryptoConfig(); // className blank\ninputSpec.setCryptoConfig(crypto);\n// after\nCryptoConfig crypto = new CryptoConfig();\ncrypto.setCryptoKeyReaderClassName(\"com.example.MyCryptoKeyReader\");\ninputSpec.setCryptoConfig(crypto);","handlingStrategy":"validation","validationCode":"if (spec.getCryptoConfig() != null\n        && (spec.getCryptoConfig().getCryptoKeyReaderClassName() == null\n            || spec.getCryptoConfig().getCryptoKeyReaderClassName().isBlank())) {\n    throw new IllegalStateException(\"cryptoKeyReaderClassName required for input topic \" + topic);\n}","typeGuard":"boolean hasCryptoKeyReader(org.apache.pulsar.functions.proto.Function.CryptoConfig c) {\n    return c == null || (c.getCryptoKeyReaderClassName() != null\n        && !c.getCryptoKeyReaderClassName().isBlank());\n}","tryCatchPattern":"try {\n    admin.functions().createFunction(conf);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"CryptoKeyReader class name\")) {\n        conf.getInputSpecs().values().forEach(s -> {\n            if (s.getCryptoConfig() != null && s.getCryptoConfig().getCryptoKeyReaderClassName().isBlank()) {\n                s.getCryptoConfig().setCryptoKeyReaderClassName(DEFAULT_KEY_READER_CLASS);\n            }\n        });\n        admin.functions().createFunction(conf);\n    } else throw e;\n}","preventionTips":["Always pair CryptoConfig with a concrete CryptoKeyReader class on the function classpath","Resolve env/property placeholders in class names before submission and fail on unresolved values","Only attach CryptoConfig to inputs that are actually encrypted"],"tags":["pulsar-functions","config-validation","crypto"],"backgroundTag":"missing-crypto-key-reader","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"}