dotnet/reactive · error · NullReferenceException
The handler returned a null IObservable
Error message
The handler returned a null IObservable
What it means
RetryWhen passes a subject of error signals to the user handler and expects a non-null IObservable<U> controlling resubscription after errors. A null return value is converted into NullReferenceException("The handler returned a null IObservable") delivered via OnError, preserving legacy exception typing (CA2201 suppressed).
Solutions
- Always return a valid IObservable from the handler (e.g. the error stream mapped to resubscribe signals, or Observable.Empty<U>() to stop retrying).
- Guard in the handler: substitute Observable.Empty<U>() when no policy matches.
- Handle OnError in the subscription to surface which handler misbehaved.
Example fix
// before source.RetryWhen(errors => errors.SelectMany(e => policies[e.Type])); // missing policy returns null // after source.RetryWhen(errors => errors.SelectMany(e => policies.TryGetValue(e.Type, out var p) ? p : Observable.Empty<Unit>()));
Defensive patterns
Strategy: validation
Validate before calling
// C# - ensure handler never returns null IObservable<Unit> redo = errorSignals.SelectMany(e => GetPolicy(e) ?? Observable.Empty<Unit>()); source.RetryWhen(_ => redo).Subscribe(...);
Type guard
// C# static bool IsValidRedo<U>(IObservable<U> o) => o is not null;
Try / catch
source.RetryWhen(errors => handler(errors)).Subscribe(
onNext,
ex => { if (ex is NullReferenceException && ex.Message.Contains("null IObservable")) LogHandlerBug(); else throw ex; }); Prevention
- Provide a default retry policy so lookups never yield null.
- Return Observable.Empty<U>() from handlers to terminate retrying.
- Unit-test all error-type branches of the handler.
When it happens
Trigger: source.RetryWhen(errorSignals => ...) where the lambda returns null (e.g. a switch expression without a matching case, or a dictionary lookup miss).
Common situations: Retry policy registries keyed by exception type with no default entry; refactored handlers whose return path became null; conditional retry logic that forgot a terminal branch.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- The handler returned a null IObservable
- Value cannot be null. (Parameter 'source')
- ArgumentNullException
- nameof(source)
- nameof(source)
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/a71d61465f60f6ec.
Report an issue: GitHub.
Appendix: source
Thrown at Rx.NET/Source/src/System.Reactive/Linq/Observable/RetryWhen.cs:41
public IDisposable Subscribe(IObserver<T> observer)
{
if (observer == null)
{
throw new ArgumentNullException(nameof(observer));
}
var errorSignals = new Subject<Exception>();
IObservable<U> redo;
try
{
redo = _handler(errorSignals);
if (redo == null)
{
#pragma warning disable CA2201 // (Do not raise reserved exception types.) Backwards compatibility prevents us from complying.
throw new NullReferenceException("The handler returned a null IObservable");
#pragma warning restore CA2201
}
}
catch (Exception ex)
{
observer.OnError(ex);
return Disposable.Empty;
}
var parent = new MainObserver(observer, _source, new RedoSerializedObserver<Exception>(errorSignals));
var d = redo.SubscribeSafe(parent.HandlerConsumer);
parent.HandlerUpstream.Disposable = d;
parent.HandlerNext();
return parent;
}View on GitHub (pinned to 94b5d5ab91)