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

  1. Always return a valid IObservable from the handler (e.g. the error stream mapped to resubscribe signals, or Observable.Empty<U>() to stop retrying).
  2. Guard in the handler: substitute Observable.Empty<U>() when no policy matches.
  3. 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

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


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)