In my prod environment, I got this inconsistent NPE when consuming kafka record. It only happened once and only happened after startup. It does not always happen and from multiple pods only a single pod gives this issue at startup.
The error triggered around 3 seconds after spring logged the Started (myapplication) in xx seconds
message and it only happened one time at that single deployment. Then when I deploy again, it happen again.
I don't think this is a configuration problem since it works fine after that single exception. I am not sure if the record re-processed after that, but I assume it is since it is not commited.
The root cause exception:
Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "java.util.Map.get(Object)" is null
at org.springframework.kafka.listener.adapter.DelegatingInvocableHandler.invoke(DelegatingInvocableHandler.java:204)
The thrown exception:
org.springframework.kafka.KafkaException: Seek to current after exception; nested exception is org.springframework.kafka.listener.ListenerExecutionFailedException: Listener method 'public default void com.lightspeed.hospitality.orderpay.app.kafka.consumers.GenericConsumer.onRawEvent(org.apache.kafka.clients.consumer.ConsumerRecord<java.lang.String, byte[]>)' threw exception; nested exception is java.lang.NullPointerException: Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "java.util.Map.get(Object)" is null; nested exception is java.lang.NullPointerException: Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "java.util.Map.get(Object)" is null
at org.springframework.kafka.listener.SeekUtils.seekOrRecover(SeekUtils.java:208)
For my consumer I use:
@KafkaListener
at my class and the method I use:
@KafkaHandler(isDefault = true)
void onRawEvent(ConsumerRecord<String, byte[]> consumerRecord)
Does anyone get an idea of where to investigate? I checked spring-kafka code but going nowhere so far.
The issue caused by:
return new InvocationResult(result, replyTo, this.handlerReturnsMessage.get(handler));
when it execute this.handlerReturnsMessage.get(handler)
it returns null
thus it cannot unbox to the primitive boolean
. But I still have no clue why it happened or if it is a bug.
What I use:
This is one possible scenario which can cause the the NPE you are seeing. You'll need to look at your logging to see whether it is happening like this.
The DelegatingInvocableHandler
class is completely asynchronous, other than using some CocurrentHashMap
s.
Two threads, T1 and T2 call invoke
concurrently. They are processing two different messages, of the same type.
At this point handlerReturnsMessage
and cachedHandlers
are empty.
T1 enters getHandlerForPayload
, this.cachedHandlers.get(payloadClass)
returns null, so T1 enters the if
block.
T1 caches the handler this.cachedHandlers.putIfAbsent(payloadClass, handler)
.
T2 enters getHandlerForPayload
, this.cachedHandlers.get(payloadClass)
returns non-null, so T2 just returns the handler.
T2 reaches return new InvocationResult(result, replyTo, this.handlerReturnsMessage.get(handler))
. The Map handlerReturnsMessage
is still empty, so an NPE results.
T1 executes setupReplyTo(handler)
. Now handlerReturnsMessage
is no longer empty.