{"id":"1e248eeb5a64157c","repo":"apache/kafka","slug":"failed-to-close-deserializers","errorCode":null,"errorMessage":"Failed to close deserializers","messagePattern":"Failed to close deserializers","errorType":"exception","errorClass":"KafkaException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/consumer/internals/Deserializers.java","lineNumber":87,"sourceCode":"        return keyDeserializerPlugin.get();\n    }\n\n    public Deserializer<V> valueDeserializer() {\n        return valueDeserializerPlugin.get();\n    }\n\n    @Override\n    public void close() {\n        AtomicReference<Throwable> firstException = new AtomicReference<>();\n        Utils.closeQuietly(keyDeserializerPlugin, \"key deserializer\", firstException);\n        Utils.closeQuietly(valueDeserializerPlugin, \"value deserializer\", firstException);\n        Throwable exception = firstException.get();\n\n        if (exception != null) {\n            if (exception instanceof InterruptException) {\n                throw (InterruptException) exception;\n            }\n            throw new KafkaException(\"Failed to close deserializers\", exception);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return \"Deserializers{\" +\n                \"keyDeserializer=\" + keyDeserializerPlugin.get() +\n                \", valueDeserializer=\" + valueDeserializerPlugin.get() +\n                '}';\n    }\n}\n","sourceCodeStart":69,"sourceCodeEnd":99,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/Deserializers.java#L69-L99","documentation":"KafkaException(\"Failed to close deserializers\", cause) thrown from Deserializers.close() when either the key or value Deserializer's close() method raised a non-interrupt exception. The first exception captured by Utils.closeQuietly is propagated; InterruptException is rethrown as-is. It indicates user-supplied (or misconfigured) deserializer cleanup code failed during consumer shutdown.","triggerScenarios":"KafkaConsumer.close() invokes Deserializers.close(), which calls close() on the configured key.deserializer / value.deserializer classes (ConsumerUtils line 87). Any exception other than InterruptException thrown by those Deserializer.close() implementations is wrapped here.","commonSituations":"Custom Deserializer that holds a network connection, file handle, or native resource and fails to close it; Avro/Protobuf/JSON deserializers whose schema registry client is closed elsewhere first; deserializer close() that performs I/O without handling IOException; dependency version mismatch where the deserializer class is missing a no-arg close.","solutions":["Inspect the wrapped cause in the stack trace to identify which deserializer (key vs value) and what resource failed.","Make your custom Deserializer.close() idempotent and swallow or log non-fatal cleanup exceptions.","Ensure schema-registry / external clients used by the deserializer are not closed before the consumer closes.","Upgrade the deserializer dependency to match the kafka-clients version on the classpath."],"exampleFix":"// before\npublic class MyJsonDeserializer implements Deserializer<MyType> {\n    private ObjectMapper mapper = new ObjectMapper();\n    @Override public void close() {\n        mapper.getFactory().close(); // throws IOException -> wrapped as KafkaException\n    }\n}\n\n// after\n@Override public void close() {\n    try { mapper.getFactory().close(); }\n    catch (IOException e) { /* log, do not propagate */ }\n}","handlingStrategy":"try-catch","validationCode":"// You cannot fully pre-validate a deserializer's close() — it may throw for I/O reasons.\n// But you CAN ensure your Deserializer implementation is non-throwing by design:\npublic class SafeJsonDeserializer implements Deserializer<MyType> {\n    @Override public void close() {\n        // Deserializers.close() (line 87) wraps the FIRST exception from key or value\n        // deserializer into KafkaException(\"Failed to close deserializers\", cause).\n        // Keep this method side-effect free and non-throwing.\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    consumer.close(); // -> Deserializers.close() -> may throw KafkaException\n} catch (org.apache.kafka.common.KafkaException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"Failed to close deserializers\")) {\n        // One of your key/value Deserializer.close() implementations threw.\n        // The consumer itself is already closed; this is a resource-cleanup warning.\n        log.warn(\"Deserializer cleanup failed (consumer still closed): {}\", e.getCause());\n        // Do not rethrow during shutdown — you would mask the real close error.\n    } else {\n        throw e;\n    }\n}","preventionTips":["Make every custom Deserializer.close() idempotent and non-throwing; release buffers in try/finally internally.","Always close the consumer in a try-with-resources or try/finally, so a throwing deserializer does not skip other cleanup.","Log, do not rethrow, during shutdown — the KafkaException at line 87 is a cleanup warning, not a data error.","Inspect the cause (e.getCause()) to find which deserializer (key vs value) actually failed."],"tags":["consumer","deserializer","shutdown","resource-leak"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}