Spontaneous NullPointerExceptions when firing Events
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
Spontaneous NullPointerException (NPE) when firing events in Java occurs when an event listener is removed (unsubscribed) between the null check and the actual invocation. In a multi-threaded environment, one thread may check that the listener list is non-null, then a second thread removes the last listener, and the first thread throws an NPE when it tries to invoke on the now-null or empty list. This is a classic TOCTOU (time-of-check, time-of-use) race condition. The standard fix uses the "snapshot" pattern — copy the listener reference or list to a local variable before invoking.
The Race Condition
Between the if (listener != null) check and the listener.onEvent() call, another thread can set listener to null. The field is read twice (once for the check, once for the call), creating a window for the race condition.
Fix 1: Local Variable Snapshot
The local variable localListener captures the reference at a single point in time. Even if another thread sets listener = null afterward, localListener still holds the original reference. The volatile keyword ensures the read sees the latest value written by other threads.
Fix 2: CopyOnWriteArrayList for Multiple Listeners
CopyOnWriteArrayList creates a new internal array on every write (add/remove). Iterators operate on a snapshot of the array at the time the iterator was created. This is the standard thread-safe pattern for event listener lists.
Fix 3: Synchronized Snapshot
Take a snapshot of the listener list inside a synchronized block, then iterate and invoke outside the lock. This prevents deadlocks where a listener callback tries to add/remove listeners.
C# Equivalent: The Null-Conditional Pattern
In C#, the ?. operator on an event delegate is thread-safe because it reads the delegate reference once and invokes it atomically. This is the idiomatic C# pattern.
Android/Kotlin Pattern
Common Pitfalls
- Reading the listener field twice (check then use): The classic TOCTOU race.
if (listener != null) listener.onEvent(data)reads the field twice. Between the reads, another thread can null the field. Always copy to a local variable first or use?.(C#/Kotlin). - Iterating the listener list without a snapshot: If a listener callback adds or removes listeners from the same list during iteration, Java throws
ConcurrentModificationException. UseCopyOnWriteArrayListor create a snapshot copy before iterating. - Firing events inside a synchronized block: If a listener callback synchronizes on the same lock (e.g., to remove itself), the call deadlocks. Always fire events outside the synchronized block by taking a snapshot inside and iterating outside.
- Not using
volatileon the listener field: Withoutvolatile, the reading thread may see a stale cached value of the listener field due to CPU cache coherency. Mark the fieldvolatileto ensure cross-thread visibility of the latest reference. - Assuming single-threaded environments: Even in applications that appear single-threaded (like Android UI), callbacks from background threads, timers, or lifecycle methods can modify listener references concurrently. Always use thread-safe event firing patterns as a defensive practice.
Summary
- Spontaneous NPEs in events are caused by a race condition between null-checking and invoking the listener
- Copy the listener reference to a local variable before checking and invoking (snapshot pattern)
- Use
CopyOnWriteArrayListfor thread-safe multi-listener event emitters - In C#, use
EventHandler?.Invoke()— the?.operator is thread-safe for delegates - Fire events outside synchronized blocks to prevent deadlocks from listener callbacks
- Mark listener fields as
volatileto ensure cross-thread visibility
Related reading
- spring-boot-starter-test with JUnit 5
- spring-boot-starter-tomcat vs spring-boot-starter-web
- Spring-Boot and Kafka How to handle broker not available?
- Spring-boot application-test.properties
- spring-boot health not showing details withDetail info
- Spring-Data-MongoDB Failed to convert from type after upgrade to 2.0.7 with custom converter
- spring-boot application without a datasource
- Spring-boot automatically import applicationContext.xml?

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.