diff --git a/src/main/java/io/reactivex/rxjava3/core/Observable.java b/src/main/java/io/reactivex/rxjava3/core/Observable.java
index 16e1454af3..0afdd29a93 100644
--- a/src/main/java/io/reactivex/rxjava3/core/Observable.java
+++ b/src/main/java/io/reactivex/rxjava3/core/Observable.java
@@ -15,6 +15,7 @@
 
 import java.util.*;
 import java.util.concurrent.*;
+import java.util.stream.*;
 
 import org.reactivestreams.Publisher;
 
@@ -24,6 +25,7 @@
 import io.reactivex.rxjava3.functions.*;
 import io.reactivex.rxjava3.internal.functions.*;
 import io.reactivex.rxjava3.internal.fuseable.ScalarSupplier;
+import io.reactivex.rxjava3.internal.jdk8.*;
 import io.reactivex.rxjava3.internal.observers.*;
 import io.reactivex.rxjava3.internal.operators.flowable.*;
 import io.reactivex.rxjava3.internal.operators.mixed.*;
@@ -1931,6 +1933,7 @@ public static <T> Observable<T> fromFuture(@NonNull Future<? extends T> future,
      *            resulting ObservableSource
      * @return an Observable that emits each item in the source {@link Iterable} sequence
      * @see <a href="http://reactivex.io/documentation/operators/from.html">ReactiveX operators documentation: From</a>
+     * @see #fromStream(Stream)
      */
     @CheckReturnValue
     @NonNull
@@ -5211,16 +5214,16 @@ public final Iterable<T> blockingIterable() {
      *  <dd>{@code blockingIterable} does not operate by default on a particular {@link Scheduler}.</dd>
      * </dl>
      *
-     * @param bufferSize the number of items to prefetch from the current Observable
+     * @param capacityHint the expected number of items to be buffered
      * @return an {@link Iterable} version of this {@code Observable}
      * @see <a href="http://reactivex.io/documentation/operators/to.html">ReactiveX documentation: To</a>
      */
     @CheckReturnValue
     @SchedulerSupport(SchedulerSupport.NONE)
     @NonNull
-    public final Iterable<T> blockingIterable(int bufferSize) {
-        ObjectHelper.verifyPositive(bufferSize, "bufferSize");
-        return new BlockingObservableIterable<>(this, bufferSize);
+    public final Iterable<T> blockingIterable(int capacityHint) {
+        ObjectHelper.verifyPositive(capacityHint, "bufferSize");
+        return new BlockingObservableIterable<>(this, capacityHint);
     }
 
     /**
@@ -15910,4 +15913,502 @@ public final TestObserver<T> test(boolean dispose) { // NoPMD
         subscribe(to);
         return to;
     }
+
+    // -------------------------------------------------------------------------
+    // JDK 8 Support
+    // -------------------------------------------------------------------------
+
+    /**
+     * Converts the existing value of the provided optional into a {@link #just(Object)}
+     * or an empty optional into an {@link #empty()} {@code Observable} instance.
+     * <p>
+     * <img width="640" height="335" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/fromOptional.o.png" alt="">
+     * <p>
+     * Note that the operator takes an already instantiated optional reference and does not
+     * by any means create this original optional. If the optional is to be created per
+     * consumer upon subscription, use {@link #defer(Supplier)} around {@code fromOptional}:
+     * <pre><code>
+     * Observable.defer(() -&gt; Observable.fromOptional(createOptional()));
+     * </code></pre>
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code fromOptional} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     * @param <T> the element type of the optional value
+     * @param optional the optional value to convert into an {@code Observable}
+     * @return the new Observable instance
+     * @see #just(Object)
+     * @see #empty()
+     * @since 3.0.0
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public static <T> Observable<@NonNull T> fromOptional(@NonNull Optional<T> optional) {
+        Objects.requireNonNull(optional, "optional is null");
+        return optional.map(Observable::just).orElseGet(Observable::empty);
+    }
+
+    /**
+     * Signals the completion value or error of the given (hot) {@link CompletionStage}-based asynchronous calculation.
+     * <p>
+     * <img width="640" height="262" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/fromCompletionStage.o.png" alt="">
+     * <p>
+     * Note that the operator takes an already instantiated, running or terminated {@code CompletionStage}.
+     * If the optional is to be created per consumer upon subscription, use {@link #defer(Supplier)}
+     * around {@code fromCompletionStage}:
+     * <pre><code>
+     * Observable.defer(() -&gt; Observable.fromCompletionStage(createCompletionStage()));
+     * </code></pre>
+     * <p>
+     * If the {@code CompletionStage} completes with {@code null}, a {@link NullPointerException} is signaled.
+     * <p>
+     * Canceling the flow can't cancel the execution of the {@code CompletionStage} because {@code CompletionStage}
+     * itself doesn't support cancellation. Instead, the operator detaches from the {@code CompletionStage}.
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code fromCompletionStage} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     * @param <T> the element type of the CompletionStage
+     * @param stage the CompletionStage to convert to Observable and signal its terminal value or error
+     * @return the new Observable instance
+     * @since 3.0.0
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public static <T> Observable<@NonNull T> fromCompletionStage(@NonNull CompletionStage<T> stage) {
+        Objects.requireNonNull(stage, "stage is null");
+        return RxJavaPlugins.onAssembly(new ObservableFromCompletionStage<>(stage));
+    }
+
+    /**
+     * Converts a {@link Stream} into a finite {@code Observable} and emits its items in the sequence.
+     * <p>
+     * <img width="640" height="407" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/fromStream.o.png" alt="">
+     * <p>
+     * The operator closes the {@code Stream} upon cancellation and when it terminates. Exceptions raised when
+     * closing a {@code Stream} are routed to the global error handler ({@link RxJavaPlugins#onError(Throwable)}.
+     * If a {@code Stream} should not be closed, turn it into an {@link Iterable} and use {@link #fromIterable(Iterable)}:
+     * <pre><code>
+     * Stream&lt;T&gt; stream = ...
+     * Observable.fromIterable(stream::iterator);
+     * </code></pre>
+     * <p>
+     * Note that {@code Stream}s can be consumed only once; any subsequent attempt to consume a {@code Stream}
+     * will result in an {@link IllegalStateException}.
+     * <p>
+     * Primitive streams are not supported and items have to be boxed manually (e.g., via {@link IntStream#boxed()}):
+     * <pre><code>
+     * IntStream intStream = IntStream.rangeClosed(1, 10);
+     * Observable.fromStream(intStream.boxed());
+     * </code></pre>
+     * <p>
+     * {@code Stream} does not support concurrent usage so creating and/or consuming the same instance multiple times
+     * from multiple threads can lead to undefined behavior.
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code fromStream} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     * @param <T> the element type of the source {@code Stream}
+     * @param stream the {@code Stream} of values to emit
+     * @return the new Observable instance
+     * @since 3.0.0
+     * @see #fromIterable(Iterable)
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public static <T> Observable<@NonNull T> fromStream(@NonNull Stream<T> stream) {
+        Objects.requireNonNull(stream, "stream is null");
+        return RxJavaPlugins.onAssembly(new ObservableFromStream<>(stream));
+    }
+
+    /**
+     * Maps each upstream value into an {@link Optional} and emits the contained item if not empty.
+     * <p>
+     * <img width="640" height="306" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mapOptional.o.png" alt="">
+     *
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code mapOptional} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     * @param <R> the non-null output type
+     * @param mapper the function that receives the upstream item and should return a <em>non-empty</em> {@code Optional}
+     * to emit as the output or an <em>empty</em> {@code Optional} to skip to the next upstream value
+     * @return the new Observable instance
+     * @since 3.0.0
+     * @see #map(Function)
+     * @see #filter(Predicate)
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public final <@NonNull R> Observable<R> mapOptional(@NonNull Function<? super T, @NonNull Optional<? extends R>> mapper) {
+        Objects.requireNonNull(mapper, "mapper is null");
+        return RxJavaPlugins.onAssembly(new ObservableMapOptional<>(this, mapper));
+    }
+
+    /**
+     * Collects the finite upstream's values into a container via a Stream {@link Collector} callback set and emits
+     * it as the success result.
+     * <p>
+     * <img width="640" height="358" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/collector.o.png" alt="">
+     *
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code collect} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     * @param <R> the non-null result type
+     * @param <A> the intermediate container type used for the accumulation
+     * @param collector the interface defining the container supplier, accumulator and finisher functions;
+     * see {@link Collectors} for some standard implementations
+     * @return the new Single instance
+     * @since 3.0.0
+     * @see Collectors
+     * @see #collect(Supplier, BiConsumer)
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public final <@NonNull R, A> Single<R> collect(@NonNull Collector<T, A, R> collector) {
+        Objects.requireNonNull(collector, "collector is null");
+        return RxJavaPlugins.onAssembly(new ObservableCollectWithCollectorSingle<>(this, collector));
+    }
+
+    /**
+     * Signals the first upstream item (or the default item if the upstream is empty) via
+     * a {@link CompletionStage}.
+     * <p>
+     * <img width="640" height="313" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/firstStage.o.png" alt="">
+     * <p>
+     * The upstream can be canceled by converting the resulting {@code CompletionStage} into
+     * {@link CompletableFuture} via {@link CompletionStage#toCompletableFuture()} and
+     * calling {@link CompletableFuture#cancel(boolean)} on it.
+     * The upstream will be also cancelled if the resulting {@code CompletionStage} is converted to and
+     * completed manually by {@link CompletableFuture#complete(Object)} or {@link CompletableFuture#completeExceptionally(Throwable)}.
+     * <p>
+     * {@code CompletionStage}s don't have a notion of emptyness and allow {@code null}s, therefore, one can either use
+     * a {@code defaultItem} of {@code null} or turn the flow into a sequence of {@link Optional}s and default to {@link Optional#empty()}:
+     * <pre><code>
+     * CompletionStage&lt;Optional&lt;T&gt;&gt; stage = source.map(Optional::of).firstStage(Optional.empty());
+     * </code></pre>
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code firstStage} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     * @param defaultItem the item to signal if the upstream is empty
+     * @return the new CompletionStage instance
+     * @since 3.0.0
+     * @see #firstOrErrorStage()
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public final CompletionStage<T> firstStage(@Nullable T defaultItem) {
+        return subscribeWith(new ObservableFirstStageObserver<>(true, defaultItem));
+    }
+
+    /**
+     * Signals the only expected upstream item (or the default item if the upstream is empty)
+     * or signals {@link IllegalArgumentException} if the upstream has more than one item
+     * via a {@link CompletionStage}.
+     * <p>
+     * <img width="640" height="227" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/singleStage.o.png" alt="">
+     * <p>
+     * The upstream can be canceled by converting the resulting {@code CompletionStage} into
+     * {@link CompletableFuture} via {@link CompletionStage#toCompletableFuture()} and
+     * calling {@link CompletableFuture#cancel(boolean)} on it.
+     * The upstream will be also cancelled if the resulting {@code CompletionStage} is converted to and
+     * completed manually by {@link CompletableFuture#complete(Object)} or {@link CompletableFuture#completeExceptionally(Throwable)}.
+     * <p>
+     * {@code CompletionStage}s don't have a notion of emptyness and allow {@code null}s, therefore, one can either use
+     * a {@code defaultItem} of {@code null} or turn the flow into a sequence of {@link Optional}s and default to {@link Optional#empty()}:
+     * <pre><code>
+     * CompletionStage&lt;Optional&lt;T&gt;&gt; stage = source.map(Optional::of).singleStage(Optional.empty());
+     * </code></pre>
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code singleStage} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     * @param defaultItem the item to signal if the upstream is empty
+     * @return the new CompletionStage instance
+     * @since 3.0.0
+     * @see #singleOrErrorStage()
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public final CompletionStage<T> singleStage(@Nullable T defaultItem) {
+        return subscribeWith(new ObservableSingleStageObserver<>(true, defaultItem));
+    }
+
+    /**
+     * Signals the last upstream item (or the default item if the upstream is empty) via
+     * a {@link CompletionStage}.
+     * <p>
+     * <img width="640" height="313" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/lastStage.o.png" alt="">
+     * <p>
+     * The upstream can be canceled by converting the resulting {@code CompletionStage} into
+     * {@link CompletableFuture} via {@link CompletionStage#toCompletableFuture()} and
+     * calling {@link CompletableFuture#cancel(boolean)} on it.
+     * The upstream will be also cancelled if the resulting {@code CompletionStage} is converted to and
+     * completed manually by {@link CompletableFuture#complete(Object)} or {@link CompletableFuture#completeExceptionally(Throwable)}.
+     * <p>
+     * {@code CompletionStage}s don't have a notion of emptyness and allow {@code null}s, therefore, one can either use
+     * a {@code defaultItem} of {@code null} or turn the flow into a sequence of {@link Optional}s and default to {@link Optional#empty()}:
+     * <pre><code>
+     * CompletionStage&lt;Optional&lt;T&gt;&gt; stage = source.map(Optional::of).lastStage(Optional.empty());
+     * </code></pre>
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code lastStage} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     * @param defaultItem the item to signal if the upstream is empty
+     * @return the new CompletionStage instance
+     * @since 3.0.0
+     * @see #lastOrErrorStage()
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public final CompletionStage<T> lastStage(@Nullable T defaultItem) {
+        return subscribeWith(new ObservableLastStageObserver<>(true, defaultItem));
+    }
+
+    /**
+     * Signals the first upstream item or a {@link NoSuchElementException} if the upstream is empty via
+     * a {@link CompletionStage}.
+     * <p>
+     * <img width="640" height="341" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/firstOrErrorStage.o.png" alt="">
+     * <p>
+     * The upstream can be canceled by converting the resulting {@code CompletionStage} into
+     * {@link CompletableFuture} via {@link CompletionStage#toCompletableFuture()} and
+     * calling {@link CompletableFuture#cancel(boolean)} on it.
+     * The upstream will be also cancelled if the resulting {@code CompletionStage} is converted to and
+     * completed manually by {@link CompletableFuture#complete(Object)} or {@link CompletableFuture#completeExceptionally(Throwable)}.
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code firstOrErrorStage} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     * @return the new CompletionStage instance
+     * @since 3.0.0
+     * @see #firstStage(Object)
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public final CompletionStage<T> firstOrErrorStage() {
+        return subscribeWith(new ObservableFirstStageObserver<>(false, null));
+    }
+
+    /**
+     * Signals the only expected upstream item, a {@link NoSuchElementException} if the upstream is empty
+     * or signals {@link IllegalArgumentException} if the upstream has more than one item
+     * via a {@link CompletionStage}.
+     * <p>
+     * <img width="640" height="227" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/singleOrErrorStage.o.png" alt="">
+     * <p>
+     * The upstream can be canceled by converting the resulting {@code CompletionStage} into
+     * {@link CompletableFuture} via {@link CompletionStage#toCompletableFuture()} and
+     * calling {@link CompletableFuture#cancel(boolean)} on it.
+     * The upstream will be also cancelled if the resulting {@code CompletionStage} is converted to and
+     * completed manually by {@link CompletableFuture#complete(Object)} or {@link CompletableFuture#completeExceptionally(Throwable)}.
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code singleOrErrorStage} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     * @return the new CompletionStage instance
+     * @since 3.0.0
+     * @see #singleStage(Object)
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public final CompletionStage<T> singleOrErrorStage() {
+        return subscribeWith(new ObservableSingleStageObserver<>(false, null));
+    }
+
+    /**
+     * Signals the last upstream item or a {@link NoSuchElementException} if the upstream is empty via
+     * a {@link CompletionStage}.
+     * <p>
+     * <img width="640" height="343" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/lastOrErrorStage.o.png" alt="">
+     * <p>
+     * The upstream can be canceled by converting the resulting {@code CompletionStage} into
+     * {@link CompletableFuture} via {@link CompletionStage#toCompletableFuture()} and
+     * calling {@link CompletableFuture#cancel(boolean)} on it.
+     * The upstream will be also cancelled if the resulting {@code CompletionStage} is converted to and
+     * completed manually by {@link CompletableFuture#complete(Object)} or {@link CompletableFuture#completeExceptionally(Throwable)}.
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code lastOrErrorStage} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     * @return the new CompletionStage instance
+     * @since 3.0.0
+     * @see #lastStage(Object)
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public final CompletionStage<T> lastOrErrorStage() {
+        return subscribeWith(new ObservableLastStageObserver<>(false, null));
+    }
+
+    /**
+     * Creates a sequential {@link Stream} to consume or process this {@code Observable} in a blocking manner via
+     * the Java {@code Stream} API.
+     * <p>
+     * <img width="640" height="399" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/blockingStream.o.png" alt="">
+     * <p>
+     * Cancellation of the upstream is done via {@link Stream#close()}, therefore, it is strongly recommended the
+     * consumption is performed within a try-with-resources construct:
+     * <pre><code>
+     * Observable&lt;Integer&gt; source = Observable.range(1, 10)
+     *        .subscribeOn(Schedulers.computation());
+     *
+     * try (Stream&lt;Integer&gt; stream = source.blockingStream()) {
+     *     stream.limit(3).forEach(System.out::println);
+     * }
+     * </code></pre>
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code blockingStream} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     *
+     * @return the new Stream instance
+     * @since 3.0.0
+     * @see #blockingStream(int)
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public final Stream<T> blockingStream() {
+        return blockingStream(bufferSize());
+    }
+
+    /**
+     * Creates a sequential {@link Stream} to consume or process this {@code Observable} in a blocking manner via
+     * the Java {@code Stream} API.
+     * <p>
+     * <img width="640" height="399" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/blockingStream.oi.png" alt="">
+     * <p>
+     * Cancellation of the upstream is done via {@link Stream#close()}, therefore, it is strongly recommended the
+     * consumption is performed within a try-with-resources construct:
+     * <pre><code>
+     * Observable&lt;Integer&gt; source = Observable.range(1, 10)
+     *        .subscribeOn(Schedulers.computation());
+     *
+     * try (Stream&lt;Integer&gt; stream = source.blockingStream(4)) {
+     *     stream.limit(3).forEach(System.out::println);
+     * }
+     * </code></pre>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code blockingStream} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     *
+     * @param capacityHint the expected number of items to be buffered
+     * @return the new Stream instance
+     * @since 3.0.0
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public final Stream<T> blockingStream(int capacityHint) {
+        Iterator<T> iterator = blockingIterable(capacityHint).iterator();
+        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false)
+                .onClose(() -> ((Disposable)iterator).dispose());
+    }
+
+    /**
+     * Maps each upstream item into a {@link Stream} and emits the {@code Stream}'s items to the downstream in a sequential fashion.
+     * <p>
+     * <img width="640" height="299" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concatMapStream.o.png" alt="">
+     * <p>
+     * Due to the blocking and sequential nature of Java {@link Stream}s, the streams are mapped and consumed in a sequential fashion
+     * without interleaving (unlike a more general {@link #flatMap(Function)}). Therefore, {@code flatMapStream} and
+     * {@code concatMapStream} are identical operators and are provided as aliases.
+     * <p>
+     * The operator closes the {@code Stream} upon cancellation and when it terminates. Exceptions raised when
+     * closing a {@code Stream} are routed to the global error handler ({@link RxJavaPlugins#onError(Throwable)}.
+     * If a {@code Stream} should not be closed, turn it into an {@link Iterable} and use {@link #concatMapIterable(Function)}:
+     * <pre><code>
+     * source.concatMapIterable(v -&gt; createStream(v)::iterator);
+     * </code></pre>
+     * <p>
+     * Note that {@code Stream}s can be consumed only once; any subsequent attempt to consume a {@code Stream}
+     * will result in an {@link IllegalStateException}.
+     * <p>
+     * Primitive streams are not supported and items have to be boxed manually (e.g., via {@link IntStream#boxed()}):
+     * <pre><code>
+     * source.concatMapStream(v -&gt; IntStream.rangeClosed(v + 1, v + 10).boxed());
+     * </code></pre>
+     * <p>
+     * {@code Stream} does not support concurrent usage so creating and/or consuming the same instance multiple times
+     * from multiple threads can lead to undefined behavior.
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code concatMapStream} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     *
+     * @param <R> the element type of the {@code Stream}s and the result
+     * @param mapper the function that receives an upstream item and should return a {@code Stream} whose elements
+     * will be emitted to the downstream
+     * @return the new Observable instance
+     * @see #concatMap(Function)
+     * @see #concatMapIterable(Function)
+     * @see #flatMapStream(Function)
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public final <@NonNull R> Observable<R> concatMapStream(@NonNull Function<? super T, @NonNull ? extends Stream<? extends R>> mapper) {
+        return flatMapStream(mapper);
+    }
+
+    /**
+     * Maps each upstream item into a {@link Stream} and emits the {@code Stream}'s items to the downstream in a sequential fashion.
+     * <p>
+     * <img width="640" height="299" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMapStream.o.png" alt="">
+     * <p>
+     * Due to the blocking and sequential nature of Java {@link Stream}s, the streams are mapped and consumed in a sequential fashion
+     * without interleaving (unlike a more general {@link #flatMap(Function)}). Therefore, {@code flatMapStream} and
+     * {@code concatMapStream} are identical operators and are provided as aliases.
+     * <p>
+     * The operator closes the {@code Stream} upon cancellation and when it terminates. Exceptions raised when
+     * closing a {@code Stream} are routed to the global error handler ({@link RxJavaPlugins#onError(Throwable)}.
+     * If a {@code Stream} should not be closed, turn it into an {@link Iterable} and use {@link #flatMapIterable(Function)}:
+     * <pre><code>
+     * source.flatMapIterable(v -&gt; createStream(v)::iterator);
+     * </code></pre>
+     * <p>
+     * Note that {@code Stream}s can be consumed only once; any subsequent attempt to consume a {@code Stream}
+     * will result in an {@link IllegalStateException}.
+     * <p>
+     * Primitive streams are not supported and items have to be boxed manually (e.g., via {@link IntStream#boxed()}):
+     * <pre><code>
+     * source.flatMapStream(v -&gt; IntStream.rangeClosed(v + 1, v + 10).boxed());
+     * </code></pre>
+     * <p>
+     * {@code Stream} does not support concurrent usage so creating and/or consuming the same instance multiple times
+     * from multiple threads can lead to undefined behavior.
+     * <dl>
+     *  <dt><b>Scheduler:</b></dt>
+     *  <dd>{@code flatMapStream} does not operate by default on a particular {@link Scheduler}.</dd>
+     * </dl>
+     *
+     * @param <R> the element type of the {@code Stream}s and the result
+     * @param mapper the function that receives an upstream item and should return a {@code Stream} whose elements
+     * will be emitted to the downstream
+     * @return the new Observable instance
+     * @see #flatMap(Function)
+     * @see #flatMapIterable(Function)
+     */
+    @CheckReturnValue
+    @SchedulerSupport(SchedulerSupport.NONE)
+    @NonNull
+    public final <@NonNull R> Observable<R> flatMapStream(@NonNull Function<? super T, @NonNull ? extends Stream<? extends R>> mapper) {
+        Objects.requireNonNull(mapper, "mapper is null");
+        return RxJavaPlugins.onAssembly(new ObservableFlatMapStream<>(this, mapper));
+    }
 }
diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStream.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStream.java
index ecc18fb154..55ec854ddf 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStream.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStream.java
@@ -69,7 +69,7 @@ public static <T> void subscribeStream(Subscriber<? super T> s, Stream<T> stream
         }
 
         if (s instanceof ConditionalSubscriber) {
-            s.onSubscribe(new StreamConditionalSubscription<T>((ConditionalSubscriber<? super T>)s, iterator, stream));
+            s.onSubscribe(new StreamConditionalSubscription<>((ConditionalSubscriber<? super T>)s, iterator, stream));
         } else {
             s.onSubscribe(new StreamSubscription<>(s, iterator, stream));
         }
@@ -147,15 +147,23 @@ public T poll() {
                 once = true;
             } else {
                 if (!iterator.hasNext()) {
+                    clear();
                     return null;
                 }
             }
-            return Objects.requireNonNull(iterator.next(), "Iterator.next() returned a null value");
+            return Objects.requireNonNull(iterator.next(), "The Stream's Iterator.next() returned a null value");
         }
 
         @Override
         public boolean isEmpty() {
-            return iterator == null || !iterator.hasNext();
+            Iterator<T> it = iterator;
+            if (it != null) {
+                if (!once || it.hasNext()) {
+                    return false;
+                }
+                clear();
+            }
+            return true;
         }
 
         @Override
diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollector.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollector.java
new file mode 100644
index 0000000000..34132a01b3
--- /dev/null
+++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollector.java
@@ -0,0 +1,151 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+package io.reactivex.rxjava3.internal.jdk8;
+
+import java.util.Objects;
+import java.util.function.*;
+import java.util.stream.Collector;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.*;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.Exceptions;
+import io.reactivex.rxjava3.internal.disposables.*;
+import io.reactivex.rxjava3.internal.observers.DeferredScalarDisposable;
+import io.reactivex.rxjava3.plugins.RxJavaPlugins;
+
+/**
+ * Collect items into a container defined by a Stream {@link Collector} callback set.
+ *
+ * @param <T> the upstream value type
+ * @param <A> the intermediate accumulator type
+ * @param <R> the result type
+ * @since 3.0.0
+ */
+public final class ObservableCollectWithCollector<T, A, R> extends Observable<R> {
+
+    final Observable<T> source;
+
+    final Collector<T, A, R> collector;
+
+    public ObservableCollectWithCollector(Observable<T> source, Collector<T, A, R> collector) {
+        this.source = source;
+        this.collector = collector;
+    }
+
+    @Override
+    protected void subscribeActual(@NonNull Observer<? super R> observer) {
+        A container;
+        BiConsumer<A, T> accumulator;
+        Function<A, R> finisher;
+
+        try {
+            container = collector.supplier().get();
+            accumulator = collector.accumulator();
+            finisher = collector.finisher();
+        } catch (Throwable ex) {
+            Exceptions.throwIfFatal(ex);
+            EmptyDisposable.error(ex, observer);
+            return;
+        }
+
+        source.subscribe(new CollectorObserver<>(observer, container, accumulator, finisher));
+    }
+
+    static final class CollectorObserver<T, A, R>
+    extends DeferredScalarDisposable<R>
+    implements Observer<T> {
+
+        private static final long serialVersionUID = -229544830565448758L;
+
+        final BiConsumer<A, T> accumulator;
+
+        final Function<A, R> finisher;
+
+        Disposable upstream;
+
+        boolean done;
+
+        A container;
+
+        CollectorObserver(Observer<? super R> downstream, A container, BiConsumer<A, T> accumulator, Function<A, R> finisher) {
+            super(downstream);
+            this.container = container;
+            this.accumulator = accumulator;
+            this.finisher = finisher;
+        }
+
+        @Override
+        public void onSubscribe(@NonNull Disposable d) {
+            if (DisposableHelper.validate(this.upstream, d)) {
+                this.upstream = d;
+
+                downstream.onSubscribe(this);
+            }
+        }
+
+        @Override
+        public void onNext(T t) {
+            if (done) {
+                return;
+            }
+            try {
+                accumulator.accept(container, t);
+            } catch (Throwable ex) {
+                Exceptions.throwIfFatal(ex);
+                upstream.dispose();
+                onError(ex);
+            }
+        }
+
+        @Override
+        public void onError(Throwable t) {
+            if (done) {
+                RxJavaPlugins.onError(t);
+            } else {
+                done = true;
+                upstream = DisposableHelper.DISPOSED;
+                this.container = null;
+                downstream.onError(t);
+            }
+        }
+
+        @Override
+        public void onComplete() {
+            if (done) {
+                return;
+            }
+
+            done = true;
+            upstream = DisposableHelper.DISPOSED;
+            A container = this.container;
+            this.container = null;
+            R result;
+            try {
+                result = Objects.requireNonNull(finisher.apply(container), "The finisher returned a null value");
+            } catch (Throwable ex) {
+                Exceptions.throwIfFatal(ex);
+                downstream.onError(ex);
+                return;
+            }
+
+            complete(result);
+        }
+
+        @Override
+        public void dispose() {
+            super.dispose();
+            upstream.dispose();
+        }
+    }
+}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollectorSingle.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollectorSingle.java
new file mode 100644
index 0000000000..a919b02ab9
--- /dev/null
+++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollectorSingle.java
@@ -0,0 +1,159 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+package io.reactivex.rxjava3.internal.jdk8;
+
+import java.util.Objects;
+import java.util.function.*;
+import java.util.stream.Collector;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.*;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.Exceptions;
+import io.reactivex.rxjava3.internal.disposables.*;
+import io.reactivex.rxjava3.internal.fuseable.FuseToObservable;
+import io.reactivex.rxjava3.plugins.RxJavaPlugins;
+
+/**
+ * Collect items into a container defined by a Stream {@link Collector} callback set.
+ *
+ * @param <T> the upstream value type
+ * @param <A> the intermediate accumulator type
+ * @param <R> the result type
+ * @since 3.0.0
+ */
+public final class ObservableCollectWithCollectorSingle<T, A, R> extends Single<R> implements FuseToObservable<R> {
+
+    final Observable<T> source;
+
+    final Collector<T, A, R> collector;
+
+    public ObservableCollectWithCollectorSingle(Observable<T> source, Collector<T, A, R> collector) {
+        this.source = source;
+        this.collector = collector;
+    }
+
+    @Override
+    public Observable<R> fuseToObservable() {
+        return new ObservableCollectWithCollector<>(source, collector);
+    }
+
+    @Override
+    protected void subscribeActual(@NonNull SingleObserver<? super R> observer) {
+        A container;
+        BiConsumer<A, T> accumulator;
+        Function<A, R> finisher;
+
+        try {
+            container = collector.supplier().get();
+            accumulator = collector.accumulator();
+            finisher = collector.finisher();
+        } catch (Throwable ex) {
+            Exceptions.throwIfFatal(ex);
+            EmptyDisposable.error(ex, observer);
+            return;
+        }
+
+        source.subscribe(new CollectorSingleObserver<>(observer, container, accumulator, finisher));
+    }
+
+    static final class CollectorSingleObserver<T, A, R> implements Observer<T>, Disposable {
+
+        final SingleObserver<? super R> downstream;
+
+        final BiConsumer<A, T> accumulator;
+
+        final Function<A, R> finisher;
+
+        Disposable upstream;
+
+        boolean done;
+
+        A container;
+
+        CollectorSingleObserver(SingleObserver<? super R> downstream, A container, BiConsumer<A, T> accumulator, Function<A, R> finisher) {
+            this.downstream = downstream;
+            this.container = container;
+            this.accumulator = accumulator;
+            this.finisher = finisher;
+        }
+
+        @Override
+        public void onSubscribe(@NonNull Disposable d) {
+            if (DisposableHelper.validate(this.upstream, d)) {
+                this.upstream = d;
+
+                downstream.onSubscribe(this);
+            }
+        }
+
+        @Override
+        public void onNext(T t) {
+            if (done) {
+                return;
+            }
+            try {
+                accumulator.accept(container, t);
+            } catch (Throwable ex) {
+                Exceptions.throwIfFatal(ex);
+                upstream.dispose();
+                onError(ex);
+            }
+        }
+
+        @Override
+        public void onError(Throwable t) {
+            if (done) {
+                RxJavaPlugins.onError(t);
+            } else {
+                done = true;
+                upstream = DisposableHelper.DISPOSED;
+                this.container = null;
+                downstream.onError(t);
+            }
+        }
+
+        @Override
+        public void onComplete() {
+            if (done) {
+                return;
+            }
+
+            done = true;
+            upstream = DisposableHelper.DISPOSED;
+            A container = this.container;
+            this.container = null;
+            R result;
+            try {
+                result = Objects.requireNonNull(finisher.apply(container), "The finisher returned a null value");
+            } catch (Throwable ex) {
+                Exceptions.throwIfFatal(ex);
+                downstream.onError(ex);
+                return;
+            }
+
+            downstream.onSuccess(result);
+        }
+
+        @Override
+        public void dispose() {
+            upstream.dispose();
+            upstream = DisposableHelper.DISPOSED;
+        }
+
+        @Override
+        public boolean isDisposed() {
+            return upstream == DisposableHelper.DISPOSED;
+        }
+    }
+}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFirstStageObserver.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFirstStageObserver.java
new file mode 100644
index 0000000000..cda0073067
--- /dev/null
+++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFirstStageObserver.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+package io.reactivex.rxjava3.internal.jdk8;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Signals the first element of the source via the underlying CompletableFuture,
+ * signals the a default item if the upstream is empty or signals {@link NoSuchElementException}.
+ *
+ * @param <T> the element type
+ * @since 3.0.0
+ */
+public final class ObservableFirstStageObserver<T> extends ObservableStageObserver<T> {
+
+    final boolean hasDefault;
+
+    final T defaultItem;
+
+    public ObservableFirstStageObserver(boolean hasDefault, T defaultItem) {
+        this.hasDefault = hasDefault;
+        this.defaultItem = defaultItem;
+    }
+
+    @Override
+    public void onNext(T t) {
+        complete(t);
+    }
+
+    @Override
+    public void onComplete() {
+        if (!isDone()) {
+            clear();
+            if (hasDefault) {
+                complete(defaultItem);
+            } else {
+                completeExceptionally(new NoSuchElementException());
+            }
+        }
+    }
+}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFlatMapStream.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFlatMapStream.java
new file mode 100644
index 0000000000..9b7b446e28
--- /dev/null
+++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFlatMapStream.java
@@ -0,0 +1,162 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+
+package io.reactivex.rxjava3.internal.jdk8;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Observer;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.Exceptions;
+import io.reactivex.rxjava3.functions.*;
+import io.reactivex.rxjava3.internal.disposables.*;
+import io.reactivex.rxjava3.plugins.RxJavaPlugins;
+
+/**
+ * Maps the upstream values onto {@link Stream}s and emits their items in order to the downstream.
+ *
+ * @param <T> the upstream element type
+ * @param <R> the inner {@code Stream} and result element type
+ * @since 3.0.0
+ */
+public final class ObservableFlatMapStream<T, R> extends Observable<R> {
+
+    final Observable<T> source;
+
+    final Function<? super T, ? extends Stream<? extends R>> mapper;
+
+    public ObservableFlatMapStream(Observable<T> source, Function<? super T, ? extends Stream<? extends R>> mapper) {
+        this.source = source;
+        this.mapper = mapper;
+    }
+
+    @Override
+    protected void subscribeActual(Observer<? super R> observer) {
+        if (source instanceof Supplier) {
+            Stream<? extends R> stream = null;
+            try {
+                @SuppressWarnings("unchecked")
+                T t = ((Supplier<T>)source).get();
+                if (t != null) {
+                    stream = Objects.requireNonNull(mapper.apply(t), "The mapper returned a null Stream");
+                }
+            } catch (Throwable ex) {
+                EmptyDisposable.error(ex, observer);
+                return;
+            }
+
+            if (stream != null) {
+                ObservableFromStream.subscribeStream(observer, stream);
+            } else {
+                EmptyDisposable.complete(observer);
+            }
+        } else {
+            source.subscribe(new FlatMapStreamObserver<>(observer, mapper));
+        }
+    }
+
+    static final class FlatMapStreamObserver<T, R> extends AtomicInteger
+    implements Observer<T>, Disposable {
+
+        private static final long serialVersionUID = -5127032662980523968L;
+
+        final Observer<? super R> downstream;
+
+        final Function<? super T, ? extends Stream<? extends R>> mapper;
+
+        Disposable upstream;
+
+        volatile boolean disposed;
+
+        boolean done;
+
+        FlatMapStreamObserver(Observer<? super R> downstream, Function<? super T, ? extends Stream<? extends R>> mapper) {
+            this.downstream = downstream;
+            this.mapper = mapper;
+        }
+
+        @Override
+        public void onSubscribe(@NonNull Disposable d) {
+            if (DisposableHelper.validate(this.upstream, d)) {
+                this.upstream = d;
+
+                downstream.onSubscribe(this);
+            }
+        }
+
+        @Override
+        public void onNext(@NonNull T t) {
+            if (done) {
+                return;
+            }
+            try {
+                try (Stream<? extends R> stream = Objects.requireNonNull(mapper.apply(t), "The mapper returned a null Stream")) {
+                    Iterator<? extends R> it = stream.iterator();
+                    while (it.hasNext()) {
+                        if (disposed) {
+                            done = true;
+                            break;
+                        }
+                        R value = Objects.requireNonNull(it.next(), "The Stream's Iterator.next retuned a null value");
+                        if (disposed) {
+                            done = true;
+                            break;
+                        }
+                        downstream.onNext(value);
+                        if (disposed) {
+                            done = true;
+                            break;
+                        }
+                    }
+                }
+            } catch (Throwable ex) {
+                Exceptions.throwIfFatal(ex);
+                upstream.dispose();
+                onError(ex);
+            }
+        }
+
+        @Override
+        public void onError(@NonNull Throwable e) {
+            if (done) {
+                RxJavaPlugins.onError(e);
+            } else {
+                done = true;
+                downstream.onError(e);
+            }
+        }
+
+        @Override
+        public void onComplete() {
+            if (!done) {
+                done = true;
+                downstream.onComplete();
+            }
+        }
+
+        @Override
+        public void dispose() {
+            disposed = true;
+            upstream.dispose();
+        }
+
+        @Override
+        public boolean isDisposed() {
+            return disposed;
+        }
+    }
+}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromCompletionStage.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromCompletionStage.java
new file mode 100644
index 0000000000..262da56026
--- /dev/null
+++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromCompletionStage.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+package io.reactivex.rxjava3.internal.jdk8;
+
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+
+import io.reactivex.rxjava3.core.*;
+import io.reactivex.rxjava3.internal.observers.DeferredScalarDisposable;
+
+/**
+ * Wrap a CompletionStage and signal its outcome.
+ * @param <T> the element type
+ * @since 3.0.0
+ */
+public final class ObservableFromCompletionStage<T> extends Observable<T> {
+
+    final CompletionStage<T> stage;
+
+    public ObservableFromCompletionStage(CompletionStage<T> stage) {
+        this.stage = stage;
+    }
+
+    @Override
+    protected void subscribeActual(Observer<? super T> observer) {
+        // We need an indirection because one can't detach from a whenComplete
+        // and cancellation should not hold onto the stage.
+        BiConsumerAtomicReference<T> whenReference = new BiConsumerAtomicReference<>();
+        CompletionStageHandler<T> handler = new CompletionStageHandler<>(observer, whenReference);
+        whenReference.lazySet(handler);
+
+        observer.onSubscribe(handler);
+        stage.whenComplete(whenReference);
+    }
+
+    static final class CompletionStageHandler<T>
+    extends DeferredScalarDisposable<T>
+    implements BiConsumer<T, Throwable> {
+
+        private static final long serialVersionUID = 4665335664328839859L;
+
+        final BiConsumerAtomicReference<T> whenReference;
+
+        CompletionStageHandler(Observer<? super T> downstream, BiConsumerAtomicReference<T> whenReference) {
+            super(downstream);
+            this.whenReference = whenReference;
+        }
+
+        @Override
+        public void accept(T item, Throwable error) {
+            if (error != null) {
+                downstream.onError(error);
+            }
+            else if (item != null) {
+                complete(item);
+            } else {
+                downstream.onError(new NullPointerException("The CompletionStage terminated with null."));
+            }
+        }
+
+        @Override
+        public void dispose() {
+            super.dispose();
+            whenReference.set(null);
+        }
+    }
+
+    static final class BiConsumerAtomicReference<T> extends AtomicReference<BiConsumer<T, Throwable>>
+    implements BiConsumer<T, Throwable> {
+
+        private static final long serialVersionUID = 45838553147237545L;
+
+        @Override
+        public void accept(T t, Throwable u) {
+            BiConsumer<T, Throwable> biConsumer = get();
+            if (biConsumer != null) {
+                biConsumer.accept(t, u);
+            }
+        }
+    }
+}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromStream.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromStream.java
new file mode 100644
index 0000000000..90d7681303
--- /dev/null
+++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromStream.java
@@ -0,0 +1,219 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+package io.reactivex.rxjava3.internal.jdk8;
+
+import java.util.*;
+import java.util.stream.Stream;
+
+import io.reactivex.rxjava3.annotations.*;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Observer;
+import io.reactivex.rxjava3.exceptions.Exceptions;
+import io.reactivex.rxjava3.internal.disposables.EmptyDisposable;
+import io.reactivex.rxjava3.internal.fuseable.QueueDisposable;
+import io.reactivex.rxjava3.plugins.RxJavaPlugins;
+
+/**
+ * Wraps a {@link Stream} and emits its values as an {@link Observable} sequence.
+ * @param <T> the element type of the Stream
+ * @since 3.0.0
+ */
+public final class ObservableFromStream<T> extends Observable<T> {
+
+    final Stream<T> stream;
+
+    public ObservableFromStream(Stream<T> stream) {
+        this.stream = stream;
+    }
+
+    @Override
+    protected void subscribeActual(Observer<? super T> observer) {
+        subscribeStream(observer, stream);
+    }
+
+    /**
+     * Subscribes to the Stream.
+     * @param <T> the element type of the flow
+     * @param observer the observer to drive
+     * @param stream the sequence to consume
+     */
+    public static <T> void subscribeStream(Observer<? super T> observer, Stream<T> stream) {
+        Iterator<T> iterator;
+        try {
+            iterator = stream.iterator();
+
+            if (!iterator.hasNext()) {
+                EmptyDisposable.complete(observer);
+                closeSafely(stream);
+                return;
+            }
+        } catch (Throwable ex) {
+            Exceptions.throwIfFatal(ex);
+            EmptyDisposable.error(ex, observer);
+            closeSafely(stream);
+            return;
+        }
+
+        StreamDisposable<T> disposable = new StreamDisposable<>(observer, iterator, stream);
+        observer.onSubscribe(disposable);
+        disposable.run();
+    }
+
+    static void closeSafely(AutoCloseable c) {
+        try {
+            c.close();
+        } catch (Throwable ex) {
+            Exceptions.throwIfFatal(ex);
+            RxJavaPlugins.onError(ex);
+        }
+    }
+
+    static final class StreamDisposable<T> implements QueueDisposable<T> {
+
+        final Observer<? super T> downstream;
+
+        Iterator<T> iterator;
+
+        AutoCloseable closeable;
+
+        volatile boolean disposed;
+
+        boolean once;
+
+        boolean outputFused;
+
+        StreamDisposable(Observer<? super T> downstream, Iterator<T> iterator, AutoCloseable closeable) {
+            this.downstream = downstream;
+            this.iterator = iterator;
+            this.closeable = closeable;
+        }
+
+        @Override
+        public void dispose() {
+            disposed = true;
+            run();
+        }
+
+        @Override
+        public boolean isDisposed() {
+            return disposed;
+        }
+
+        @Override
+        public int requestFusion(int mode) {
+            if ((mode & SYNC) != 0) {
+                outputFused = true;
+                return SYNC;
+            }
+            return NONE;
+        }
+
+        @Override
+        public boolean offer(@NonNull T value) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean offer(@NonNull T v1, @NonNull T v2) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Nullable
+        @Override
+        public T poll() {
+            if (iterator == null) {
+                return null;
+            }
+            if (!once) {
+                once = true;
+            } else {
+                if (!iterator.hasNext()) {
+                    clear();
+                    return null;
+                }
+            }
+            return Objects.requireNonNull(iterator.next(), "The Stream's Iterator.next() returned a null value");
+        }
+
+        @Override
+        public boolean isEmpty() {
+            Iterator<T> it = iterator;
+            if (it != null) {
+                if (!once || it.hasNext()) {
+                    return false;
+                }
+                clear();
+            }
+            return true;
+        }
+
+        @Override
+        public void clear() {
+            iterator = null;
+            AutoCloseable c = closeable;
+            closeable = null;
+            if (c != null) {
+                closeSafely(c);
+            }
+        }
+
+        public void run() {
+            if (outputFused) {
+                return;
+            }
+            Iterator<T> iterator = this.iterator;
+            Observer<? super T> downstream = this.downstream;
+
+            for (;;) {
+                if (disposed) {
+                    clear();
+                    break;
+                }
+
+                T next;
+                try {
+                    next = Objects.requireNonNull(iterator.next(), "The Stream's Iterator.next returned a null value");
+                } catch (Throwable ex) {
+                    Exceptions.throwIfFatal(ex);
+                    downstream.onError(ex);
+                    disposed = true;
+                    continue;
+                }
+
+                if (disposed) {
+                    continue;
+                }
+
+                downstream.onNext(next);
+
+                if (disposed) {
+                    continue;
+                }
+
+                try {
+                    if (iterator.hasNext()) {
+                        continue;
+                    }
+                } catch (Throwable ex) {
+                    Exceptions.throwIfFatal(ex);
+                    downstream.onError(ex);
+                    disposed = true;
+                    continue;
+                }
+
+                downstream.onComplete();
+                disposed = true;
+            }
+        }
+    }
+}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableLastStageObserver.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableLastStageObserver.java
new file mode 100644
index 0000000000..e2f9dc2225
--- /dev/null
+++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableLastStageObserver.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+package io.reactivex.rxjava3.internal.jdk8;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Signals the last element of the source via the underlying CompletableFuture,
+ * signals the a default item if the upstream is empty or signals {@link NoSuchElementException}.
+ *
+ * @param <T> the element type
+ * @since 3.0.0
+ */
+public final class ObservableLastStageObserver<T> extends ObservableStageObserver<T> {
+
+    final boolean hasDefault;
+
+    final T defaultItem;
+
+    public ObservableLastStageObserver(boolean hasDefault, T defaultItem) {
+        this.hasDefault = hasDefault;
+        this.defaultItem = defaultItem;
+    }
+
+    @Override
+    public void onNext(T t) {
+        value = t;
+    }
+
+    @Override
+    public void onComplete() {
+        if (!isDone()) {
+            T v = value;
+            clear();
+            if (v != null) {
+                complete(v);
+            } else if (hasDefault) {
+                complete(defaultItem);
+            } else {
+                completeExceptionally(new NoSuchElementException());
+            }
+        }
+    }
+}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableMapOptional.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableMapOptional.java
new file mode 100644
index 0000000000..eb134482be
--- /dev/null
+++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableMapOptional.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+package io.reactivex.rxjava3.internal.jdk8;
+
+import java.util.*;
+
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Observer;
+import io.reactivex.rxjava3.functions.Function;
+import io.reactivex.rxjava3.internal.observers.BasicFuseableObserver;
+
+/**
+ * Map the upstream values into an Optional and emit its value if any.
+ * @param <T> the upstream element type
+ * @param <R> the output element type
+ * @since 3.0.0
+ */
+public final class ObservableMapOptional<T, R> extends Observable<R> {
+
+    final Observable<T> source;
+
+    final Function<? super T, Optional<? extends R>> mapper;
+
+    public ObservableMapOptional(Observable<T> source, Function<? super T, Optional<? extends R>> mapper) {
+        this.source = source;
+        this.mapper = mapper;
+    }
+
+    @Override
+    protected void subscribeActual(Observer<? super R> observer) {
+        source.subscribe(new MapOptionalObserver<>(observer, mapper));
+    }
+
+    static final class MapOptionalObserver<T, R> extends BasicFuseableObserver<T, R> {
+
+        final Function<? super T, Optional<? extends R>> mapper;
+
+        MapOptionalObserver(Observer<? super R> downstream, Function<? super T, Optional<? extends R>> mapper) {
+            super(downstream);
+            this.mapper = mapper;
+        }
+
+        @Override
+        public void onNext(T t) {
+            if (done) {
+                return;
+            }
+
+            if (sourceMode != NONE) {
+                downstream.onNext(null);
+                return;
+            }
+
+            Optional<? extends R> result;
+            try {
+                result = Objects.requireNonNull(mapper.apply(t), "The mapper returned a null Optional");
+            } catch (Throwable ex) {
+                fail(ex);
+                return;
+            }
+
+            if (result.isPresent()) {
+                downstream.onNext(result.get());
+            }
+        }
+
+        @Override
+        public int requestFusion(int mode) {
+            return transitiveBoundaryFusion(mode);
+        }
+
+        @Override
+        public R poll() throws Throwable {
+            for (;;) {
+                T item = qd.poll();
+                if (item == null) {
+                    return null;
+                }
+                Optional<? extends R> result = Objects.requireNonNull(mapper.apply(item), "The mapper returned a null Optional");
+                if (result.isPresent()) {
+                    return result.get();
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableSingleStageObserver.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableSingleStageObserver.java
new file mode 100644
index 0000000000..fa8714397a
--- /dev/null
+++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableSingleStageObserver.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+package io.reactivex.rxjava3.internal.jdk8;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Signals the only element of the source via the underlying CompletableFuture,
+ * signals the a default item if the upstream is empty or signals {@link IllegalArgumentException}
+ * if the upstream has more than one item.
+ *
+ * @param <T> the element type
+ * @since 3.0.0
+ */
+public final class ObservableSingleStageObserver<T> extends ObservableStageObserver<T> {
+
+    final boolean hasDefault;
+
+    final T defaultItem;
+
+    public ObservableSingleStageObserver(boolean hasDefault, T defaultItem) {
+        this.hasDefault = hasDefault;
+        this.defaultItem = defaultItem;
+    }
+
+    @Override
+    public void onNext(T t) {
+        if (value != null) {
+            value = null;
+            completeExceptionally(new IllegalArgumentException("Sequence contains more than one element!"));
+        } else {
+            value = t;
+        }
+    }
+
+    @Override
+    public void onComplete() {
+        if (!isDone()) {
+            T v = value;
+            clear();
+            if (v != null) {
+                complete(v);
+            } else if (hasDefault) {
+                complete(defaultItem);
+            } else {
+                completeExceptionally(new NoSuchElementException());
+            }
+        }
+    }
+}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageObserver.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageObserver.java
new file mode 100644
index 0000000000..54acae086f
--- /dev/null
+++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageObserver.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+package io.reactivex.rxjava3.internal.jdk8;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.Observer;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.internal.disposables.DisposableHelper;
+import io.reactivex.rxjava3.plugins.RxJavaPlugins;
+
+/**
+ * Base class that extends CompletableFuture and provides basic infrastructure
+ * to notify watchers upon upstream signals.
+ * @param <T> the element type
+ * @since 3.0.0
+ */
+abstract class ObservableStageObserver<T> extends CompletableFuture<T> implements Observer<T> {
+
+    final AtomicReference<Disposable> upstream = new AtomicReference<>();
+
+    T value;
+
+    @Override
+    public final void onSubscribe(@NonNull Disposable d) {
+        DisposableHelper.setOnce(upstream, d);
+    }
+
+    @Override
+    public final void onError(Throwable t) {
+        clear();
+        if (!completeExceptionally(t)) {
+            RxJavaPlugins.onError(t);
+        }
+    }
+
+    protected final void disposeUpstream() {
+        DisposableHelper.dispose(upstream);
+    }
+
+    protected final void clear() {
+        value = null;
+        upstream.lazySet(DisposableHelper.DISPOSED);
+    }
+
+    @Override
+    public final boolean cancel(boolean mayInterruptIfRunning) {
+        disposeUpstream();
+        return super.cancel(mayInterruptIfRunning);
+    }
+
+    @Override
+    public final boolean complete(T value) {
+        disposeUpstream();
+        return super.complete(value);
+    }
+
+    @Override
+    public final boolean completeExceptionally(Throwable ex) {
+        disposeUpstream();
+        return super.completeExceptionally(ex);
+    }
+}
diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStreamTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStreamTest.java
index 940ac95c4d..7d5f7a0a74 100644
--- a/src/test/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStreamTest.java
+++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStreamTest.java
@@ -184,6 +184,43 @@ public void onComplete() {
         assertTrue(q.isEmpty());
     }
 
+    @Test
+    public void fusedPoll() throws Throwable {
+        AtomicReference<SimpleQueue<?>> queue = new AtomicReference<>();
+        AtomicInteger calls = new AtomicInteger();
+
+        Flowable.fromStream(Stream.of(1).onClose(() -> calls.getAndIncrement()))
+        .subscribe(new FlowableSubscriber<Integer>() {
+            @Override
+            public void onSubscribe(@NonNull Subscription s) {
+                queue.set((SimpleQueue<?>)s);
+                ((QueueSubscription<?>)s).requestFusion(QueueFuseable.ANY);
+            }
+
+            @Override
+            public void onNext(Integer t) {
+            }
+
+            @Override
+            public void onError(Throwable t) {
+            }
+
+            @Override
+            public void onComplete() {
+            }
+        });
+
+        SimpleQueue<?> q = queue.get();
+
+        assertFalse(q.isEmpty());
+
+        assertEquals(1, q.poll());
+
+        assertTrue(q.isEmpty());
+
+        assertEquals(1, calls.get());
+    }
+
     @Test
     public void streamOfNull() {
         Flowable.fromStream(Stream.of((Integer)null))
@@ -512,4 +549,9 @@ public void closeCalledOnItemCrashConditional() {
 
         assertEquals(1, calls.get());
     }
+
+    @Test
+    public void badRequest() {
+        TestHelper.assertBadRequestReported(Flowable.fromStream(Stream.of(1)));
+    }
 }
diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableBlockingStreamTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableBlockingStreamTest.java
new file mode 100644
index 0000000000..b62bedb6f8
--- /dev/null
+++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableBlockingStreamTest.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+
+package io.reactivex.rxjava3.internal.jdk8;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+import java.util.stream.*;
+
+import org.junit.Test;
+
+import io.reactivex.rxjava3.core.*;
+import io.reactivex.rxjava3.exceptions.TestException;
+import io.reactivex.rxjava3.processors.UnicastProcessor;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+
+public class ObservableBlockingStreamTest extends RxJavaTest {
+
+    @Test
+    public void empty() {
+        try (Stream<Integer> stream = Observable.<Integer>empty().blockingStream()) {
+            assertEquals(0, stream.toArray().length);
+        }
+    }
+
+    @Test
+    public void just() {
+        try (Stream<Integer> stream = Observable.just(1).blockingStream()) {
+            assertArrayEquals(new Integer[] { 1 }, stream.toArray(Integer[]::new));
+        }
+    }
+
+    @Test
+    public void range() {
+        try (Stream<Integer> stream = Observable.range(1, 5).blockingStream()) {
+            assertArrayEquals(new Integer[] { 1, 2, 3, 4, 5 }, stream.toArray(Integer[]::new));
+        }
+    }
+
+    @Test
+    public void rangeBackpressured() {
+        try (Stream<Integer> stream = Observable.range(1, 5).blockingStream(1)) {
+            assertArrayEquals(new Integer[] { 1, 2, 3, 4, 5 }, stream.toArray(Integer[]::new));
+        }
+    }
+
+    @Test
+    public void rangeAsyncBackpressured() {
+        try (Stream<Integer> stream = Observable.range(1, 1000).subscribeOn(Schedulers.computation()).blockingStream()) {
+            List<Integer> list = stream.collect(Collectors.toList());
+
+            assertEquals(1000, list.size());
+            for (int i = 1; i <= 1000; i++) {
+                assertEquals(i, list.get(i - 1).intValue());
+            }
+        }
+    }
+
+    @Test
+    public void rangeAsyncBackpressured1() {
+        try (Stream<Integer> stream = Observable.range(1, 1000).subscribeOn(Schedulers.computation()).blockingStream(1)) {
+            List<Integer> list = stream.collect(Collectors.toList());
+
+            assertEquals(1000, list.size());
+            for (int i = 1; i <= 1000; i++) {
+                assertEquals(i, list.get(i - 1).intValue());
+            }
+        }
+    }
+
+    @Test
+    public void error() {
+        try (Stream<Integer> stream = Observable.<Integer>error(new TestException()).blockingStream()) {
+            stream.toArray(Integer[]::new);
+            fail("Should have thrown!");
+        } catch (TestException expected) {
+            // expected
+        }
+    }
+
+    @Test
+    public void close() {
+        UnicastProcessor<Integer> up = UnicastProcessor.create();
+        up.onNext(1);
+        up.onNext(2);
+        up.onNext(3);
+        up.onNext(4);
+        up.onNext(5);
+
+        try (Stream<Integer> stream = up.blockingStream()) {
+            assertArrayEquals(new Integer[] { 1, 2, 3 }, stream.limit(3).toArray(Integer[]::new));
+        }
+
+        assertFalse(up.hasSubscribers());
+    }
+}
diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollectorTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollectorTest.java
new file mode 100644
index 0000000000..923a7dbaa3
--- /dev/null
+++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollectorTest.java
@@ -0,0 +1,444 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+
+package io.reactivex.rxjava3.internal.jdk8;
+
+import static org.junit.Assert.assertFalse;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import org.junit.Test;
+
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Observer;
+import io.reactivex.rxjava3.core.RxJavaTest;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.TestException;
+import io.reactivex.rxjava3.observers.TestObserver;
+import io.reactivex.rxjava3.processors.*;
+import io.reactivex.rxjava3.subjects.PublishSubject;
+import io.reactivex.rxjava3.testsupport.TestHelper;
+
+public class ObservableCollectWithCollectorTest extends RxJavaTest {
+
+    @Test
+    public void basic() {
+        Observable.range(1, 5)
+        .collect(Collectors.toList())
+        .test()
+        .assertResult(Arrays.asList(1, 2, 3, 4, 5));
+    }
+
+    @Test
+    public void empty() {
+        Observable.empty()
+        .collect(Collectors.toList())
+        .test()
+        .assertResult(Collections.emptyList());
+    }
+
+    @Test
+    public void error() {
+        Observable.error(new TestException())
+        .collect(Collectors.toList())
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void collectorSupplierCrash() {
+        Observable.range(1, 5)
+        .collect(new Collector<Integer, Integer, Integer>() {
+
+            @Override
+            public Supplier<Integer> supplier() {
+                throw new TestException();
+            }
+
+            @Override
+            public BiConsumer<Integer, Integer> accumulator() {
+                return (a, b) -> { };
+            }
+
+            @Override
+            public BinaryOperator<Integer> combiner() {
+                return (a, b) -> a + b;
+            }
+
+            @Override
+            public Function<Integer, Integer> finisher() {
+                return a -> a;
+            }
+
+            @Override
+            public Set<Characteristics> characteristics() {
+                return Collections.emptySet();
+            }
+        })
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void collectorAccumulatorCrash() {
+        BehaviorProcessor<Integer> source = BehaviorProcessor.createDefault(1);
+
+        source
+        .collect(new Collector<Integer, Integer, Integer>() {
+
+            @Override
+            public Supplier<Integer> supplier() {
+                return () -> 1;
+            }
+
+            @Override
+            public BiConsumer<Integer, Integer> accumulator() {
+                return (a, b) -> { throw new TestException(); };
+            }
+
+            @Override
+            public BinaryOperator<Integer> combiner() {
+                return (a, b) -> a + b;
+            }
+
+            @Override
+            public Function<Integer, Integer> finisher() {
+                return a -> a;
+            }
+
+            @Override
+            public Set<Characteristics> characteristics() {
+                return Collections.emptySet();
+            }
+        })
+        .test()
+        .assertFailure(TestException.class);
+
+        assertFalse(source.hasSubscribers());
+    }
+
+    @Test
+    public void collectorFinisherCrash() {
+        Observable.range(1, 5)
+        .collect(new Collector<Integer, Integer, Integer>() {
+
+            @Override
+            public Supplier<Integer> supplier() {
+                return () -> 1;
+            }
+
+            @Override
+            public BiConsumer<Integer, Integer> accumulator() {
+                return (a, b) -> {  };
+            }
+
+            @Override
+            public BinaryOperator<Integer> combiner() {
+                return (a, b) -> a + b;
+            }
+
+            @Override
+            public Function<Integer, Integer> finisher() {
+                return a -> { throw new TestException(); };
+            }
+
+            @Override
+            public Set<Characteristics> characteristics() {
+                return Collections.emptySet();
+            }
+        })
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void collectorAccumulatorDropSignals() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Observable<Integer> source = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                    observer.onNext(2);
+                    observer.onError(new IOException());
+                    observer.onComplete();
+                }
+            };
+
+            source
+            .collect(new Collector<Integer, Integer, Integer>() {
+
+                @Override
+                public Supplier<Integer> supplier() {
+                    return () -> 1;
+                }
+
+                @Override
+                public BiConsumer<Integer, Integer> accumulator() {
+                    return (a, b) -> { throw new TestException(); };
+                }
+
+                @Override
+                public BinaryOperator<Integer> combiner() {
+                    return (a, b) -> a + b;
+                }
+
+                @Override
+                public Function<Integer, Integer> finisher() {
+                    return a -> a;
+                }
+
+                @Override
+                public Set<Characteristics> characteristics() {
+                    return Collections.emptySet();
+                }
+            })
+            .test()
+            .assertFailure(TestException.class);
+
+            TestHelper.assertUndeliverable(errors, 0, IOException.class);
+        });
+    }
+
+    @Test
+    public void dispose() {
+        TestHelper.checkDisposed(PublishSubject.create()
+                .collect(Collectors.toList()));
+    }
+
+    @Test
+    public void onSubscribe() {
+        TestHelper.checkDoubleOnSubscribeObservableToSingle(f -> f.collect(Collectors.toList()));
+    }
+
+    @Test
+    public void basicToObservable() {
+        Observable.range(1, 5)
+        .collect(Collectors.toList())
+        .toObservable()
+        .test()
+        .assertResult(Arrays.asList(1, 2, 3, 4, 5));
+    }
+
+    @Test
+    public void emptyToObservable() {
+        Observable.empty()
+        .collect(Collectors.toList())
+        .toObservable()
+        .test()
+        .assertResult(Collections.emptyList());
+    }
+
+    @Test
+    public void errorToObservable() {
+        Observable.error(new TestException())
+        .collect(Collectors.toList())
+        .toObservable()
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void collectorSupplierCrashToObservable() {
+        Observable.range(1, 5)
+        .collect(new Collector<Integer, Integer, Integer>() {
+
+            @Override
+            public Supplier<Integer> supplier() {
+                throw new TestException();
+            }
+
+            @Override
+            public BiConsumer<Integer, Integer> accumulator() {
+                return (a, b) -> { };
+            }
+
+            @Override
+            public BinaryOperator<Integer> combiner() {
+                return (a, b) -> a + b;
+            }
+
+            @Override
+            public Function<Integer, Integer> finisher() {
+                return a -> a;
+            }
+
+            @Override
+            public Set<Characteristics> characteristics() {
+                return Collections.emptySet();
+            }
+        })
+        .toObservable()
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void collectorAccumulatorCrashToObservable() {
+        BehaviorProcessor<Integer> source = BehaviorProcessor.createDefault(1);
+
+        source
+        .collect(new Collector<Integer, Integer, Integer>() {
+
+            @Override
+            public Supplier<Integer> supplier() {
+                return () -> 1;
+            }
+
+            @Override
+            public BiConsumer<Integer, Integer> accumulator() {
+                return (a, b) -> { throw new TestException(); };
+            }
+
+            @Override
+            public BinaryOperator<Integer> combiner() {
+                return (a, b) -> a + b;
+            }
+
+            @Override
+            public Function<Integer, Integer> finisher() {
+                return a -> a;
+            }
+
+            @Override
+            public Set<Characteristics> characteristics() {
+                return Collections.emptySet();
+            }
+        })
+        .toObservable()
+        .test()
+        .assertFailure(TestException.class);
+
+        assertFalse(source.hasSubscribers());
+    }
+
+    @Test
+    public void collectorFinisherCrashToObservable() {
+        Observable.range(1, 5)
+        .collect(new Collector<Integer, Integer, Integer>() {
+
+            @Override
+            public Supplier<Integer> supplier() {
+                return () -> 1;
+            }
+
+            @Override
+            public BiConsumer<Integer, Integer> accumulator() {
+                return (a, b) -> {  };
+            }
+
+            @Override
+            public BinaryOperator<Integer> combiner() {
+                return (a, b) -> a + b;
+            }
+
+            @Override
+            public Function<Integer, Integer> finisher() {
+                return a -> { throw new TestException(); };
+            }
+
+            @Override
+            public Set<Characteristics> characteristics() {
+                return Collections.emptySet();
+            }
+        })
+        .toObservable()
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void collectorAccumulatorDropSignalsToObservable() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Observable<Integer> source = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                    observer.onNext(2);
+                    observer.onError(new IOException());
+                    observer.onComplete();
+                }
+            };
+
+            source
+            .collect(new Collector<Integer, Integer, Integer>() {
+
+                @Override
+                public Supplier<Integer> supplier() {
+                    return () -> 1;
+                }
+
+                @Override
+                public BiConsumer<Integer, Integer> accumulator() {
+                    return (a, b) -> { throw new TestException(); };
+                }
+
+                @Override
+                public BinaryOperator<Integer> combiner() {
+                    return (a, b) -> a + b;
+                }
+
+                @Override
+                public Function<Integer, Integer> finisher() {
+                    return a -> a;
+                }
+
+                @Override
+                public Set<Characteristics> characteristics() {
+                    return Collections.emptySet();
+                }
+            })
+            .toObservable()
+            .test()
+            .assertFailure(TestException.class);
+
+            TestHelper.assertUndeliverable(errors, 0, IOException.class);
+        });
+    }
+
+    @Test
+    public void disposeToObservable() {
+        TestHelper.checkDisposed(PublishProcessor.create()
+                .collect(Collectors.toList()).toObservable());
+    }
+
+    @Test
+    public void onSubscribeToObservable() {
+        TestHelper.checkDoubleOnSubscribeObservable(f -> f.collect(Collectors.toList()).toObservable());
+    }
+
+    @Test
+    public void toObservableTake() {
+        Observable.range(1, 5)
+        .collect(Collectors.toList())
+        .toObservable()
+        .take(1)
+        .test()
+        .assertResult(Arrays.asList(1, 2, 3, 4, 5));
+    }
+
+    @Test
+    public void disposeBeforeEnd() {
+        TestObserver<List<Integer>> to = Observable.range(1, 5).concatWith(Observable.never())
+        .collect(Collectors.toList())
+        .test();
+
+        to.dispose();
+
+        to.assertEmpty();
+    }
+}
diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFlatMapStreamTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFlatMapStreamTest.java
new file mode 100644
index 0000000000..8341aa03dd
--- /dev/null
+++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFlatMapStreamTest.java
@@ -0,0 +1,466 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+
+package io.reactivex.rxjava3.internal.jdk8;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.*;
+
+import org.junit.Test;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.*;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.TestException;
+import io.reactivex.rxjava3.observers.TestObserver;
+import io.reactivex.rxjava3.subjects.*;
+import io.reactivex.rxjava3.testsupport.TestHelper;
+
+public class ObservableFlatMapStreamTest extends RxJavaTest {
+
+    @Test
+    public void empty() {
+        Observable.empty()
+        .flatMapStream(v -> Stream.of(1, 2, 3, 4, 5))
+        .test()
+        .assertResult();
+    }
+
+    @Test
+    public void emptyHidden() {
+        Observable.empty()
+        .hide()
+        .flatMapStream(v -> Stream.of(1, 2, 3, 4, 5))
+        .test()
+        .assertResult();
+    }
+
+    @Test
+    public void just() {
+        Observable.just(1)
+        .flatMapStream(v -> Stream.of(v + 1, v + 2, v + 3, v + 4, v + 5))
+        .test()
+        .assertResult(2, 3, 4, 5, 6);
+    }
+
+    @Test
+    public void justHidden() {
+        Observable.just(1).hide()
+        .flatMapStream(v -> Stream.of(v + 1, v + 2, v + 3, v + 4, v + 5))
+        .test()
+        .assertResult(2, 3, 4, 5, 6);
+    }
+
+    @Test
+    public void error() {
+        Observable.error(new TestException())
+        .flatMapStream(v -> Stream.of(1, 2, 3, 4, 5))
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void supplierFusedError() {
+        Observable.fromCallable(() -> { throw new TestException(); })
+        .flatMapStream(v -> Stream.of(1, 2, 3, 4, 5))
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void errorHidden() {
+        Observable.error(new TestException())
+        .hide()
+        .flatMapStream(v -> Stream.of(1, 2, 3, 4, 5))
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void range() {
+        Observable.range(1, 5)
+        .flatMapStream(v -> IntStream.range(v * 10, v * 10 + 5).boxed())
+        .test()
+        .assertResult(
+                10, 11, 12, 13, 14,
+                20, 21, 22, 23, 24,
+                30, 31, 32, 33, 34,
+                40, 41, 42, 43, 44,
+                50, 51, 52, 53, 54
+        );
+    }
+
+    @Test
+    public void rangeHidden() {
+        Observable.range(1, 5)
+        .hide()
+        .flatMapStream(v -> IntStream.range(v * 10, v * 10 + 5).boxed())
+        .test()
+        .assertResult(
+                10, 11, 12, 13, 14,
+                20, 21, 22, 23, 24,
+                30, 31, 32, 33, 34,
+                40, 41, 42, 43, 44,
+                50, 51, 52, 53, 54
+        );
+    }
+
+    @Test
+    public void rangeToEmpty() {
+        Observable.range(1, 5)
+        .flatMapStream(v -> Stream.of())
+        .test()
+        .assertResult();
+    }
+
+    @Test
+    public void rangeTake() {
+        Observable.range(1, 5)
+        .flatMapStream(v -> IntStream.range(v * 10, v * 10 + 5).boxed())
+        .take(12)
+        .test()
+        .assertResult(
+                10, 11, 12, 13, 14,
+                20, 21, 22, 23, 24,
+                30, 31
+        );
+    }
+
+    @Test
+    public void rangeTakeHidden() {
+        Observable.range(1, 5)
+        .hide()
+        .flatMapStream(v -> IntStream.range(v * 10, v * 10 + 5).boxed())
+        .take(12)
+        .test()
+        .assertResult(
+                10, 11, 12, 13, 14,
+                20, 21, 22, 23, 24,
+                30, 31
+        );
+    }
+
+    @Test
+    public void upstreamCancelled() {
+        PublishSubject<Integer> ps = PublishSubject.create();
+
+        AtomicInteger calls = new AtomicInteger();
+
+        TestObserver<Integer> to = ps
+                .flatMapStream(v -> Stream.of(v + 1, v + 2).onClose(() -> calls.getAndIncrement()))
+                .take(1)
+                .test();
+
+        assertTrue(ps.hasObservers());
+
+        ps.onNext(1);
+
+        to.assertResult(2);
+
+        assertFalse(ps.hasObservers());
+
+        assertEquals(1, calls.get());
+    }
+
+    @Test
+    public void upstreamCancelledCloseCrash() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            PublishSubject<Integer> ps = PublishSubject.create();
+
+            TestObserver<Integer> to = ps
+                    .flatMapStream(v -> Stream.of(v + 1, v + 2).onClose(() -> { throw new TestException(); }))
+                    .take(1)
+                    .test();
+
+            assertTrue(ps.hasObservers());
+
+            ps.onNext(1);
+
+            to.assertResult(2);
+
+            assertFalse(ps.hasObservers());
+
+            TestHelper.assertUndeliverable(errors, 0, TestException.class);
+        });
+    }
+
+    @Test
+    public void crossMap() {
+        Observable.range(1, 1000)
+        .flatMapStream(v -> IntStream.range(v * 1000, v * 1000 + 1000).boxed())
+        .test()
+        .assertValueCount(1_000_000)
+        .assertNoErrors()
+        .assertComplete();
+    }
+
+    @Test
+    public void crossMapHidden() {
+        Observable.range(1, 1000)
+        .hide()
+        .flatMapStream(v -> IntStream.range(v * 1000, v * 1000 + 1000).boxed())
+        .test()
+        .assertValueCount(1_000_000)
+        .assertNoErrors()
+        .assertComplete();
+    }
+
+    @Test
+    public void onSubscribe() {
+        TestHelper.checkDoubleOnSubscribeObservable(f -> f.flatMapStream(v -> Stream.of(1, 2)));
+    }
+
+    @Test
+    public void mapperThrows() {
+        Observable.just(1).hide()
+        .concatMapStream(v -> { throw new TestException(); })
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void mapperNull() {
+        Observable.just(1).hide()
+        .concatMapStream(v -> null)
+        .test()
+        .assertFailure(NullPointerException.class);
+    }
+
+    @Test
+    public void streamNull() {
+        Observable.just(1).hide()
+        .concatMapStream(v -> Stream.of(1, null))
+        .test()
+        .assertFailure(NullPointerException.class, 1);
+    }
+
+    @Test
+    public void hasNextThrows() {
+        Observable.just(1).hide()
+        .concatMapStream(v -> Stream.generate(() -> { throw new TestException(); }))
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void hasNextThrowsLater() {
+        AtomicInteger counter = new AtomicInteger();
+        Observable.just(1).hide()
+        .concatMapStream(v -> Stream.generate(() -> {
+            if (counter.getAndIncrement() == 0) {
+                return 1;
+            }
+            throw new TestException();
+        }))
+        .test()
+        .assertFailure(TestException.class, 1);
+    }
+
+    @Test
+    public void mapperThrowsWhenUpstreamErrors() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            PublishSubject<Integer> ps = PublishSubject.create();
+
+            AtomicInteger counter = new AtomicInteger();
+
+            TestObserver<Integer> to = ps.hide()
+            .concatMapStream(v -> {
+                if (counter.getAndIncrement() == 0) {
+                    return Stream.of(1, 2);
+                }
+                ps.onError(new IOException());
+                throw new TestException();
+            })
+            .test();
+
+            ps.onNext(1);
+            ps.onNext(2);
+
+            to
+            .assertFailure(IOException.class, 1, 2);
+
+            TestHelper.assertUndeliverable(errors, 0, TestException.class);
+        });
+    }
+
+    @Test
+    public void cancelAfterIteratorNext() throws Exception {
+        TestObserver<Integer> to = new TestObserver<>();
+
+        @SuppressWarnings("unchecked")
+        Stream<Integer> stream = mock(Stream.class);
+        when(stream.iterator()).thenReturn(new Iterator<Integer>() {
+
+            @Override
+            public boolean hasNext() {
+                return true;
+            }
+
+            @Override
+            public Integer next() {
+                to.dispose();
+                return 1;
+            }
+        });
+
+        Observable.just(1)
+        .hide()
+        .concatMapStream(v -> stream)
+        .subscribe(to);
+
+        to.assertEmpty();
+    }
+
+    @Test
+    public void cancelAfterIteratorHasNext() throws Exception {
+        TestObserver<Integer> to = new TestObserver<>();
+
+        @SuppressWarnings("unchecked")
+        Stream<Integer> stream = mock(Stream.class);
+        when(stream.iterator()).thenReturn(new Iterator<Integer>() {
+
+            @Override
+            public boolean hasNext() {
+                to.dispose();
+                return true;
+            }
+
+            @Override
+            public Integer next() {
+                return 1;
+            }
+        });
+
+        Observable.just(1)
+        .hide()
+        .concatMapStream(v -> stream)
+        .subscribe(to);
+
+        to.assertEmpty();
+    }
+
+    @Test
+    public void asyncUpstreamFused() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+
+        TestObserver<Integer> to = us.flatMapStream(v -> Stream.of(1, 2))
+        .test();
+
+        assertTrue(us.hasObservers());
+
+        us.onNext(1);
+
+        to.assertValuesOnly(1, 2);
+
+        us.onComplete();
+
+        to.assertResult(1, 2);
+    }
+
+    @Test
+    public void asyncUpstreamFusionBoundary() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+
+        TestObserver<Integer> to = us
+                .map(v -> v + 1)
+                .flatMapStream(v -> Stream.of(1, 2))
+        .test();
+
+        assertTrue(us.hasObservers());
+
+        us.onNext(1);
+
+        to.assertValuesOnly(1, 2);
+
+        us.onComplete();
+
+        to.assertResult(1, 2);
+    }
+
+    @Test
+    public void fusedPollCrash() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+
+        TestObserver<Integer> to = us
+                .map(v -> { throw new TestException(); })
+                .compose(TestHelper.observableStripBoundary())
+                .flatMapStream(v -> Stream.of(1, 2))
+        .test();
+
+        assertTrue(us.hasObservers());
+
+        us.onNext(1);
+
+        assertFalse(us.hasObservers());
+
+        to.assertFailure(TestException.class);
+    }
+
+    @Test
+    public void dispose() {
+        TestHelper.checkDisposed(PublishSubject.create().flatMapStream(v -> Stream.of(1)));
+    }
+
+    @Test
+    public void eventsIgnoredAfterCrash() {
+        AtomicInteger calls = new AtomicInteger();
+
+        new Observable<Integer>() {
+            @Override
+            protected void subscribeActual(@NonNull Observer<? super Integer> observer) {
+                observer.onSubscribe(Disposable.empty());
+                observer.onNext(1);
+                observer.onNext(2);
+                observer.onComplete();
+            }
+        }
+        .flatMapStream(v -> {
+            calls.getAndIncrement();
+            throw new TestException();
+        })
+        .take(1)
+        .test()
+        .assertFailure(TestException.class);
+
+        assertEquals(1, calls.get());
+    }
+
+    @Test
+    public void eventsIgnoredAfterDispose() {
+        AtomicInteger calls = new AtomicInteger();
+
+        new Observable<Integer>() {
+            @Override
+            protected void subscribeActual(@NonNull Observer<? super Integer> observer) {
+                observer.onSubscribe(Disposable.empty());
+                observer.onNext(1);
+                observer.onNext(2);
+                observer.onComplete();
+            }
+        }
+        .flatMapStream(v -> {
+            calls.getAndIncrement();
+            return Stream.of(1);
+        })
+        .take(1)
+        .test()
+        .assertResult(1);
+
+        assertEquals(1, calls.get());
+    }
+}
diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromCompletionStageTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromCompletionStageTest.java
new file mode 100644
index 0000000000..612ab0724b
--- /dev/null
+++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromCompletionStageTest.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+
+package io.reactivex.rxjava3.internal.jdk8;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.Test;
+
+import io.reactivex.rxjava3.core.*;
+import io.reactivex.rxjava3.exceptions.TestException;
+import io.reactivex.rxjava3.observers.TestObserver;
+
+public class ObservableFromCompletionStageTest extends RxJavaTest {
+
+    @Test
+    public void syncSuccess() {
+        Observable.fromCompletionStage(CompletableFuture.completedFuture(1))
+        .test()
+        .assertResult(1);
+    }
+
+    @Test
+    public void syncFailure() {
+        CompletableFuture<Integer> cf = new CompletableFuture<>();
+        cf.completeExceptionally(new TestException());
+
+        Observable.fromCompletionStage(cf)
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void syncNull() {
+        Observable.fromCompletionStage(CompletableFuture.<Integer>completedFuture(null))
+        .test()
+        .assertFailure(NullPointerException.class);
+    }
+
+    @Test
+    public void cancel() {
+        CompletableFuture<Integer> cf = new CompletableFuture<>();
+
+        TestObserver<Integer> to = Observable.fromCompletionStage(cf)
+        .test();
+
+        to.assertEmpty();
+
+        to.dispose();
+
+        cf.complete(1);
+
+        to.assertEmpty();
+    }
+}
diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromOptionalTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromOptionalTest.java
new file mode 100644
index 0000000000..e2e4059e70
--- /dev/null
+++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromOptionalTest.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+
+package io.reactivex.rxjava3.internal.jdk8;
+
+import java.util.Optional;
+
+import org.junit.Test;
+
+import io.reactivex.rxjava3.core.*;
+
+public class ObservableFromOptionalTest extends RxJavaTest {
+
+    @Test
+    public void hasValue() {
+        Observable.fromOptional(Optional.of(1))
+        .test()
+        .assertResult(1);
+    }
+
+    @Test
+    public void empty() {
+        Observable.fromOptional(Optional.empty())
+        .test()
+        .assertResult();
+    }
+
+}
diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromStreamTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromStreamTest.java
new file mode 100644
index 0000000000..6074e54689
--- /dev/null
+++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromStreamTest.java
@@ -0,0 +1,488 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+
+package io.reactivex.rxjava3.internal.jdk8;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Iterator;
+import java.util.concurrent.atomic.*;
+import java.util.stream.*;
+
+import org.junit.Test;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.*;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.TestException;
+import io.reactivex.rxjava3.internal.fuseable.*;
+import io.reactivex.rxjava3.observers.TestObserver;
+import io.reactivex.rxjava3.testsupport.*;
+
+public class ObservableFromStreamTest extends RxJavaTest {
+
+    @Test
+    public void empty() {
+        Observable.fromStream(Stream.<Integer>of())
+        .test()
+        .assertResult();
+    }
+
+    @Test
+    public void just() {
+        Observable.fromStream(Stream.<Integer>of(1))
+        .test()
+        .assertResult(1);
+    }
+
+    @Test
+    public void many() {
+        Observable.fromStream(Stream.<Integer>of(1, 2, 3, 4, 5))
+        .test()
+        .assertResult(1, 2, 3, 4, 5);
+    }
+
+    @Test
+    public void noReuse() {
+        Observable<Integer> source = Observable.fromStream(Stream.<Integer>of(1, 2, 3, 4, 5));
+
+        source
+        .test()
+        .assertResult(1, 2, 3, 4, 5);
+
+        source
+        .test()
+        .assertFailure(IllegalStateException.class);
+    }
+
+    @Test
+    public void take() {
+        Observable.fromStream(IntStream.rangeClosed(1, 10).boxed())
+        .take(5)
+        .test()
+        .assertResult(1, 2, 3, 4, 5);
+    }
+
+    @Test
+    public void emptyConditional() {
+        Observable.fromStream(Stream.<Integer>of())
+        .filter(v -> true)
+        .test()
+        .assertResult();
+    }
+
+    @Test
+    public void justConditional() {
+        Observable.fromStream(Stream.<Integer>of(1))
+        .filter(v -> true)
+        .test()
+        .assertResult(1);
+    }
+
+    @Test
+    public void manyConditional() {
+        Observable.fromStream(Stream.<Integer>of(1, 2, 3, 4, 5))
+        .filter(v -> true)
+        .test()
+        .assertResult(1, 2, 3, 4, 5);
+    }
+
+    @Test
+    public void manyConditionalSkip() {
+        Observable.fromStream(IntStream.rangeClosed(1, 10).boxed())
+        .filter(v -> v % 2 == 0)
+        .test()
+        .assertResult(2, 4, 6, 8, 10);
+    }
+
+    @Test
+    public void takeConditional() {
+        Observable.fromStream(IntStream.rangeClosed(1, 10).boxed())
+        .filter(v -> true)
+        .take(5)
+        .test()
+        .assertResult(1, 2, 3, 4, 5);
+    }
+
+    @Test
+    public void noOfferNoCrashAfterClear() throws Throwable {
+        AtomicReference<SimpleQueue<?>> queue = new AtomicReference<>();
+
+        Observable.fromStream(IntStream.rangeClosed(1, 10).boxed())
+        .subscribe(new Observer<Integer>() {
+            @Override
+            public void onSubscribe(@NonNull Disposable d) {
+                queue.set((SimpleQueue<?>)d);
+                ((QueueDisposable<?>)d).requestFusion(QueueFuseable.ANY);
+            }
+
+            @Override
+            public void onNext(Integer t) {
+            }
+
+            @Override
+            public void onError(Throwable t) {
+            }
+
+            @Override
+            public void onComplete() {
+            }
+        });
+
+        SimpleQueue<?> q = queue.get();
+        TestHelper.assertNoOffer(q);
+
+        assertFalse(q.isEmpty());
+
+        q.clear();
+
+        assertNull(q.poll());
+
+        assertTrue(q.isEmpty());
+
+        q.clear();
+
+        assertNull(q.poll());
+
+        assertTrue(q.isEmpty());
+    }
+
+    @Test
+    public void fusedPoll() throws Throwable {
+        AtomicReference<SimpleQueue<?>> queue = new AtomicReference<>();
+        AtomicInteger calls = new AtomicInteger();
+
+        Observable.fromStream(Stream.of(1).onClose(() -> calls.getAndIncrement()))
+        .subscribe(new Observer<Integer>() {
+            @Override
+            public void onSubscribe(@NonNull Disposable d) {
+                queue.set((SimpleQueue<?>)d);
+                ((QueueDisposable<?>)d).requestFusion(QueueFuseable.ANY);
+            }
+
+            @Override
+            public void onNext(Integer t) {
+            }
+
+            @Override
+            public void onError(Throwable t) {
+            }
+
+            @Override
+            public void onComplete() {
+            }
+        });
+
+        SimpleQueue<?> q = queue.get();
+
+        assertFalse(q.isEmpty());
+
+        assertEquals(1, q.poll());
+
+        assertTrue(q.isEmpty());
+
+        assertEquals(1, calls.get());
+    }
+
+    @Test
+    public void fusedPoll2() throws Throwable {
+        AtomicReference<SimpleQueue<?>> queue = new AtomicReference<>();
+        AtomicInteger calls = new AtomicInteger();
+
+        Observable.fromStream(Stream.of(1, 2).onClose(() -> calls.getAndIncrement()))
+        .subscribe(new Observer<Integer>() {
+            @Override
+            public void onSubscribe(@NonNull Disposable d) {
+                queue.set((SimpleQueue<?>)d);
+                ((QueueDisposable<?>)d).requestFusion(QueueFuseable.ANY);
+            }
+
+            @Override
+            public void onNext(Integer t) {
+            }
+
+            @Override
+            public void onError(Throwable t) {
+            }
+
+            @Override
+            public void onComplete() {
+            }
+        });
+
+        SimpleQueue<?> q = queue.get();
+
+        assertFalse(q.isEmpty());
+
+        assertEquals(1, q.poll());
+
+        assertFalse(q.isEmpty());
+
+        assertEquals(2, q.poll());
+
+        assertTrue(q.isEmpty());
+
+        assertEquals(1, calls.get());
+    }
+
+    @Test
+    public void streamOfNull() {
+        Observable.fromStream(Stream.of((Integer)null))
+        .test()
+        .assertFailure(NullPointerException.class);
+    }
+
+    @Test
+    public void streamOfNullConditional() {
+        Observable.fromStream(Stream.of((Integer)null))
+        .filter(v -> true)
+        .test()
+        .assertFailure(NullPointerException.class);
+    }
+
+    @Test
+    public void syncFusionSupport() {
+        TestObserverEx<Integer> to = new TestObserverEx<>();
+        to.setInitialFusionMode(QueueFuseable.ANY);
+
+        Observable.fromStream(IntStream.rangeClosed(1, 10).boxed())
+        .subscribeWith(to)
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.SYNC)
+        .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+    }
+
+    @Test
+    public void asyncFusionNotSupported() {
+        TestObserverEx<Integer> to = new TestObserverEx<>();
+        to.setInitialFusionMode(QueueFuseable.ASYNC);
+
+        Observable.fromStream(IntStream.rangeClosed(1, 10).boxed())
+        .subscribeWith(to)
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.NONE)
+        .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+    }
+
+    @Test
+    public void runToEndCloseCrash() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5).onClose(() -> { throw new TestException(); });
+
+            Observable.fromStream(stream)
+            .test()
+            .assertResult(1, 2, 3, 4, 5);
+
+            TestHelper.assertUndeliverable(errors, 0, TestException.class);
+        });
+    }
+
+    @Test
+    public void takeCloseCrash() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5).onClose(() -> { throw new TestException(); });
+
+            Observable.fromStream(stream)
+            .take(3)
+            .test()
+            .assertResult(1, 2, 3);
+
+            TestHelper.assertUndeliverable(errors, 0, TestException.class);
+        });
+    }
+
+    @Test
+    public void hasNextCrash() {
+        AtomicInteger v = new AtomicInteger();
+        Observable.fromStream(Stream.<Integer>generate(() -> {
+            int value = v.getAndIncrement();
+            if (value == 1) {
+                throw new TestException();
+            }
+            return value;
+        }))
+        .test()
+        .assertFailure(TestException.class, 0);
+    }
+
+    @Test
+    public void hasNextCrashConditional() {
+        AtomicInteger counter = new AtomicInteger();
+        Observable.fromStream(Stream.<Integer>generate(() -> {
+            int value = counter.getAndIncrement();
+            if (value == 1) {
+                throw new TestException();
+            }
+            return value;
+        }))
+        .filter(v -> true)
+        .test()
+        .assertFailure(TestException.class, 0);
+    }
+
+    @Test
+    public void closeCalledOnEmpty() {
+        AtomicInteger calls = new AtomicInteger();
+
+        Observable.fromStream(Stream.of().onClose(() -> calls.getAndIncrement()))
+        .test()
+        .assertResult();
+
+        assertEquals(1, calls.get());
+    }
+
+    @Test
+    public void closeCalledAfterItems() {
+        AtomicInteger calls = new AtomicInteger();
+
+        Observable.fromStream(Stream.of(1, 2, 3, 4, 5).onClose(() -> calls.getAndIncrement()))
+        .test()
+        .assertResult(1, 2, 3, 4, 5);
+
+        assertEquals(1, calls.get());
+    }
+
+    @Test
+    public void closeCalledOnCancel() {
+        AtomicInteger calls = new AtomicInteger();
+
+        Observable.fromStream(Stream.of(1, 2, 3, 4, 5).onClose(() -> calls.getAndIncrement()))
+        .take(3)
+        .test()
+        .assertResult(1, 2, 3);
+
+        assertEquals(1, calls.get());
+    }
+
+    @Test
+    public void closeCalledOnItemCrash() {
+        AtomicInteger calls = new AtomicInteger();
+        AtomicInteger counter = new AtomicInteger();
+        Observable.fromStream(Stream.<Integer>generate(() -> {
+            int value = counter.getAndIncrement();
+            if (value == 1) {
+                throw new TestException();
+            }
+            return value;
+        }).onClose(() -> calls.getAndIncrement()))
+        .test()
+        .assertFailure(TestException.class, 0);
+
+        assertEquals(1, calls.get());
+    }
+
+    @Test
+    public void closeCalledAfterItemsConditional() {
+        AtomicInteger calls = new AtomicInteger();
+
+        Observable.fromStream(Stream.of(1, 2, 3, 4, 5).onClose(() -> calls.getAndIncrement()))
+        .filter(v -> true)
+        .test()
+        .assertResult(1, 2, 3, 4, 5);
+
+        assertEquals(1, calls.get());
+    }
+
+    @Test
+    public void closeCalledOnCancelConditional() {
+        AtomicInteger calls = new AtomicInteger();
+
+        Observable.fromStream(Stream.of(1, 2, 3, 4, 5).onClose(() -> calls.getAndIncrement()))
+        .filter(v -> true)
+        .take(3)
+        .test()
+        .assertResult(1, 2, 3);
+
+        assertEquals(1, calls.get());
+    }
+
+    @Test
+    public void closeCalledOnItemCrashConditional() {
+        AtomicInteger calls = new AtomicInteger();
+        AtomicInteger counter = new AtomicInteger();
+        Observable.fromStream(Stream.<Integer>generate(() -> {
+            int value = counter.getAndIncrement();
+            if (value == 1) {
+                throw new TestException();
+            }
+            return value;
+        }).onClose(() -> calls.getAndIncrement()))
+        .filter(v -> true)
+        .test()
+        .assertFailure(TestException.class, 0);
+
+        assertEquals(1, calls.get());
+    }
+
+    @Test
+    public void dispose() {
+        TestHelper.checkDisposed(Observable.fromStream(Stream.of(1)));
+    }
+
+    @Test
+    public void cancelAfterIteratorNext() throws Exception {
+        TestObserver<Integer> to = new TestObserver<>();
+
+        @SuppressWarnings("unchecked")
+        Stream<Integer> stream = mock(Stream.class);
+        when(stream.iterator()).thenReturn(new Iterator<Integer>() {
+
+            @Override
+            public boolean hasNext() {
+                return true;
+            }
+
+            @Override
+            public Integer next() {
+                to.dispose();
+                return 1;
+            }
+        });
+
+        Observable.fromStream(stream)
+        .subscribe(to);
+
+        to.assertEmpty();
+    }
+
+    @Test
+    public void cancelAfterIteratorHasNext() throws Exception {
+        TestObserver<Integer> to = new TestObserver<>();
+
+        @SuppressWarnings("unchecked")
+        Stream<Integer> stream = mock(Stream.class);
+        when(stream.iterator()).thenReturn(new Iterator<Integer>() {
+
+            int calls;
+
+            @Override
+            public boolean hasNext() {
+                if (++calls == 1) {
+                    to.dispose();
+                }
+                return true;
+            }
+
+            @Override
+            public Integer next() {
+                return 1;
+            }
+        });
+
+        Observable.fromStream(stream)
+        .subscribe(to);
+
+        to.assertEmpty();
+    }
+}
diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableMapOptionalTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableMapOptionalTest.java
new file mode 100644
index 0000000000..ba38417b12
--- /dev/null
+++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableMapOptionalTest.java
@@ -0,0 +1,394 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+
+package io.reactivex.rxjava3.internal.jdk8;
+
+import static org.junit.Assert.assertFalse;
+
+import java.util.Optional;
+
+import org.junit.Test;
+
+import io.reactivex.rxjava3.core.*;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.TestException;
+import io.reactivex.rxjava3.functions.Function;
+import io.reactivex.rxjava3.internal.fuseable.QueueFuseable;
+import io.reactivex.rxjava3.subjects.*;
+import io.reactivex.rxjava3.testsupport.TestHelper;
+
+public class ObservableMapOptionalTest extends RxJavaTest {
+
+    static final Function<? super Integer, Optional<? extends Integer>> MODULO = v -> v % 2 == 0 ? Optional.of(v) : Optional.<Integer>empty();
+
+    @Test
+    public void allPresent() {
+        Observable.range(1, 5)
+        .mapOptional(Optional::of)
+        .test()
+        .assertResult(1, 2, 3, 4, 5);
+    }
+
+    @Test
+    public void allEmpty() {
+        Observable.range(1, 5)
+        .mapOptional(v -> Optional.<Integer>empty())
+        .test()
+        .assertResult();
+    }
+
+    @Test
+    public void mixed() {
+        Observable.range(1, 10)
+        .mapOptional(MODULO)
+        .test()
+        .assertResult(2, 4, 6, 8, 10);
+    }
+
+    @Test
+    public void mapperChash() {
+        BehaviorSubject<Integer> source = BehaviorSubject.createDefault(1);
+
+        source
+        .mapOptional(v -> { throw new TestException(); })
+        .test()
+        .assertFailure(TestException.class);
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void mapperNull() {
+        BehaviorSubject<Integer> source = BehaviorSubject.createDefault(1);
+
+        source
+        .mapOptional(v -> null)
+        .test()
+        .assertFailure(NullPointerException.class);
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void crashDropsOnNexts() {
+        Observable<Integer> source = new Observable<Integer>() {
+            @Override
+            protected void subscribeActual(Observer<? super Integer> observer) {
+                observer.onSubscribe(Disposable.empty());
+                observer.onNext(1);
+                observer.onNext(2);
+            }
+        };
+
+        source
+        .mapOptional(v -> { throw new TestException(); })
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void syncFusedAll() {
+        Observable.range(1, 5)
+        .mapOptional(Optional::of)
+        .to(TestHelper.testConsumer(false, QueueFuseable.SYNC))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.SYNC)
+        .assertResult(1, 2, 3, 4, 5);
+    }
+
+    @Test
+    public void asyncFusedAll() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
+
+        us
+        .mapOptional(Optional::of)
+        .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.ASYNC)
+        .assertResult(1, 2, 3, 4, 5);
+    }
+
+    @Test
+    public void boundaryFusedAll() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
+
+        us
+        .mapOptional(Optional::of)
+        .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC | QueueFuseable.BOUNDARY))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.NONE)
+        .assertResult(1, 2, 3, 4, 5);
+    }
+
+    @Test
+    public void syncFusedNone() {
+        Observable.range(1, 5)
+        .mapOptional(v -> Optional.empty())
+        .to(TestHelper.testConsumer(false, QueueFuseable.SYNC))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.SYNC)
+        .assertResult();
+    }
+
+    @Test
+    public void asyncFusedNone() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
+
+        us
+        .mapOptional(v -> Optional.empty())
+        .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.ASYNC)
+        .assertResult();
+    }
+
+    @Test
+    public void boundaryFusedNone() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
+
+        us
+        .mapOptional(v -> Optional.empty())
+        .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC | QueueFuseable.BOUNDARY))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.NONE)
+        .assertResult();
+    }
+
+    @Test
+    public void syncFusedMixed() {
+        Observable.range(1, 10)
+        .mapOptional(MODULO)
+        .to(TestHelper.testConsumer(false, QueueFuseable.SYNC))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.SYNC)
+        .assertResult(2, 4, 6, 8, 10);
+    }
+
+    @Test
+    public void asyncFusedMixed() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+
+        us
+        .mapOptional(MODULO)
+        .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.ASYNC)
+        .assertResult(2, 4, 6, 8, 10);
+    }
+
+    @Test
+    public void boundaryFusedMixed() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+
+        us
+        .mapOptional(MODULO)
+        .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC | QueueFuseable.BOUNDARY))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.NONE)
+        .assertResult(2, 4, 6, 8, 10);
+    }
+
+    @Test
+    public void allPresentConditional() {
+        Observable.range(1, 5)
+        .mapOptional(Optional::of)
+        .filter(v -> true)
+        .test()
+        .assertResult(1, 2, 3, 4, 5);
+    }
+
+    @Test
+    public void allEmptyConditional() {
+        Observable.range(1, 5)
+        .mapOptional(v -> Optional.<Integer>empty())
+        .filter(v -> true)
+        .test()
+        .assertResult();
+    }
+
+    @Test
+    public void mixedConditional() {
+        Observable.range(1, 10)
+        .mapOptional(MODULO)
+        .filter(v -> true)
+        .test()
+        .assertResult(2, 4, 6, 8, 10);
+    }
+
+    @Test
+    public void mapperChashConditional() {
+        BehaviorSubject<Integer> source = BehaviorSubject.createDefault(1);
+
+        source
+        .mapOptional(v -> { throw new TestException(); })
+        .filter(v -> true)
+        .test()
+        .assertFailure(TestException.class);
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void mapperNullConditional() {
+        BehaviorSubject<Integer> source = BehaviorSubject.createDefault(1);
+
+        source
+        .mapOptional(v -> null)
+        .filter(v -> true)
+        .test()
+        .assertFailure(NullPointerException.class);
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void crashDropsOnNextsConditional() {
+        Observable<Integer> source = new Observable<Integer>() {
+            @Override
+            protected void subscribeActual(Observer<? super Integer> observer) {
+                observer.onSubscribe(Disposable.empty());
+                observer.onNext(1);
+                observer.onNext(2);
+            }
+        };
+
+        source
+        .mapOptional(v -> { throw new TestException(); })
+        .filter(v -> true)
+        .test()
+        .assertFailure(TestException.class);
+    }
+
+    @Test
+    public void syncFusedAllConditional() {
+        Observable.range(1, 5)
+        .mapOptional(Optional::of)
+        .filter(v -> true)
+        .to(TestHelper.testConsumer(false, QueueFuseable.SYNC))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.SYNC)
+        .assertResult(1, 2, 3, 4, 5);
+    }
+
+    @Test
+    public void asyncFusedAllConditional() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
+
+        us
+        .mapOptional(Optional::of)
+        .filter(v -> true)
+        .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.ASYNC)
+        .assertResult(1, 2, 3, 4, 5);
+    }
+
+    @Test
+    public void boundaryFusedAllConditiona() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
+
+        us
+        .mapOptional(Optional::of)
+        .filter(v -> true)
+        .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC | QueueFuseable.BOUNDARY))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.NONE)
+        .assertResult(1, 2, 3, 4, 5);
+    }
+
+    @Test
+    public void syncFusedNoneConditional() {
+        Observable.range(1, 5)
+        .mapOptional(v -> Optional.empty())
+        .filter(v -> true)
+        .to(TestHelper.testConsumer(false, QueueFuseable.SYNC))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.SYNC)
+        .assertResult();
+    }
+
+    @Test
+    public void asyncFusedNoneConditional() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
+
+        us
+        .mapOptional(v -> Optional.empty())
+        .filter(v -> true)
+        .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.ASYNC)
+        .assertResult();
+    }
+
+    @Test
+    public void boundaryFusedNoneConditional() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
+
+        us
+        .mapOptional(v -> Optional.empty())
+        .filter(v -> true)
+        .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC | QueueFuseable.BOUNDARY))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.NONE)
+        .assertResult();
+    }
+
+    @Test
+    public void syncFusedMixedConditional() {
+        Observable.range(1, 10)
+        .mapOptional(MODULO)
+        .filter(v -> true)
+        .to(TestHelper.testConsumer(false, QueueFuseable.SYNC))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.SYNC)
+        .assertResult(2, 4, 6, 8, 10);
+    }
+
+    @Test
+    public void asyncFusedMixedConditional() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+
+        us
+        .mapOptional(MODULO)
+        .filter(v -> true)
+        .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.ASYNC)
+        .assertResult(2, 4, 6, 8, 10);
+    }
+
+    @Test
+    public void boundaryFusedMixedConditional() {
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+
+        us
+        .mapOptional(MODULO)
+        .filter(v -> true)
+        .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC | QueueFuseable.BOUNDARY))
+        .assertFuseable()
+        .assertFusionMode(QueueFuseable.NONE)
+        .assertResult(2, 4, 6, 8, 10);
+    }
+}
diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageSubscriberOrDefaultTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageSubscriberOrDefaultTest.java
new file mode 100644
index 0000000000..9b02622c9b
--- /dev/null
+++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageSubscriberOrDefaultTest.java
@@ -0,0 +1,475 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+
+package io.reactivex.rxjava3.internal.jdk8;
+
+import static org.junit.Assert.*;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.Test;
+
+import io.reactivex.rxjava3.core.*;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.*;
+import io.reactivex.rxjava3.subjects.*;
+import io.reactivex.rxjava3.testsupport.TestHelper;
+
+public class ObservableStageSubscriberOrDefaultTest extends RxJavaTest {
+
+    @Test
+    public void firstJust() throws Exception {
+        Integer v = Observable.just(1)
+                .firstStage(null)
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)1, v);
+    }
+
+    @Test
+    public void firstEmpty() throws Exception {
+        Integer v = Observable.<Integer>empty()
+                .firstStage(2)
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)2, v);
+    }
+
+    @Test
+    public void firstCancels() throws Exception {
+        BehaviorSubject<Integer> source = BehaviorSubject.createDefault(1);
+
+        Integer v = source
+                .firstStage(null)
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)1, v);
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void firstCompletableFutureCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .firstStage(null)
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.cancel(true);
+
+        assertTrue(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void firstCompletableManualCompleteCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .firstStage(null)
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.complete(1);
+
+        assertTrue(cf.isDone());
+        assertFalse(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+
+        assertEquals((Integer)1, cf.get());
+    }
+
+    @Test
+    public void firstCompletableManualCompleteExceptionallyCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .firstStage(null)
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.completeExceptionally(new TestException());
+
+        assertTrue(cf.isDone());
+        assertTrue(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+
+        TestHelper.assertError(cf, TestException.class);
+    }
+
+    @Test
+    public void firstError() throws Exception {
+        CompletableFuture<Integer> cf = Observable.<Integer>error(new TestException())
+                .firstStage(null)
+                .toCompletableFuture();
+
+        assertTrue(cf.isDone());
+        assertTrue(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        TestHelper.assertError(cf, TestException.class);
+    }
+
+    @Test
+    public void firstSourceIgnoresCancel() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Integer v = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                    observer.onError(new TestException());
+                    observer.onComplete();
+                }
+            }
+            .firstStage(null)
+            .toCompletableFuture()
+            .get();
+
+            assertEquals((Integer)1, v);
+
+            TestHelper.assertUndeliverable(errors, 0, TestException.class);
+        });
+    }
+
+    @Test
+    public void firstDoubleOnSubscribe() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Integer v = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                }
+            }
+            .firstStage(null)
+            .toCompletableFuture()
+            .get();
+
+            assertEquals((Integer)1, v);
+
+            TestHelper.assertError(errors, 0, ProtocolViolationException.class);
+        });
+    }
+
+    @Test
+    public void singleJust() throws Exception {
+        Integer v = Observable.just(1)
+                .singleStage(null)
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)1, v);
+    }
+
+    @Test
+    public void singleEmpty() throws Exception {
+        Integer v = Observable.<Integer>empty()
+                .singleStage(2)
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)2, v);
+    }
+
+    @Test
+    public void singleTooManyCancels() throws Exception {
+        ReplaySubject<Integer> source = ReplaySubject.create();
+        source.onNext(1);
+        source.onNext(2);
+
+        TestHelper.assertError(source
+                .singleStage(null)
+                .toCompletableFuture(), IllegalArgumentException.class);
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void singleCompletableFutureCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .singleStage(null)
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.cancel(true);
+
+        assertTrue(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void singleCompletableManualCompleteCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .singleStage(null)
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.complete(1);
+
+        assertTrue(cf.isDone());
+        assertFalse(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+
+        assertEquals((Integer)1, cf.get());
+    }
+
+    @Test
+    public void singleCompletableManualCompleteExceptionallyCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .singleStage(null)
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.completeExceptionally(new TestException());
+
+        assertTrue(cf.isDone());
+        assertTrue(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+
+        TestHelper.assertError(cf, TestException.class);
+    }
+
+    @Test
+    public void singleError() throws Exception {
+        CompletableFuture<Integer> cf = Observable.<Integer>error(new TestException())
+                .singleStage(null)
+                .toCompletableFuture();
+
+        assertTrue(cf.isDone());
+        assertTrue(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        TestHelper.assertError(cf, TestException.class);
+    }
+
+    @Test
+    public void singleSourceIgnoresCancel() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Integer v = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                    observer.onComplete();
+                    observer.onError(new TestException());
+                    observer.onComplete();
+                }
+            }
+            .singleStage(null)
+            .toCompletableFuture()
+            .get();
+
+            assertEquals((Integer)1, v);
+
+            TestHelper.assertUndeliverable(errors, 0, TestException.class);
+        });
+    }
+
+    @Test
+    public void singleDoubleOnSubscribe() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Integer v = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                    observer.onComplete();
+                }
+            }
+            .singleStage(null)
+            .toCompletableFuture()
+            .get();
+
+            assertEquals((Integer)1, v);
+
+            TestHelper.assertError(errors, 0, ProtocolViolationException.class);
+        });
+    }
+
+    @Test
+    public void lastJust() throws Exception {
+        Integer v = Observable.just(1)
+                .lastStage(null)
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)1, v);
+    }
+
+    @Test
+    public void lastRange() throws Exception {
+        Integer v = Observable.range(1, 5)
+                .lastStage(null)
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)5, v);
+    }
+
+    @Test
+    public void lastEmpty() throws Exception {
+        Integer v = Observable.<Integer>empty()
+                .lastStage(2)
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)2, v);
+    }
+
+    @Test
+    public void lastCompletableFutureCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .lastStage(null)
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.cancel(true);
+
+        assertTrue(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void lastCompletableManualCompleteCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .lastStage(null)
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.complete(1);
+
+        assertTrue(cf.isDone());
+        assertFalse(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+
+        assertEquals((Integer)1, cf.get());
+    }
+
+    @Test
+    public void lastCompletableManualCompleteExceptionallyCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .lastStage(null)
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.completeExceptionally(new TestException());
+
+        assertTrue(cf.isDone());
+        assertTrue(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+
+        TestHelper.assertError(cf, TestException.class);
+    }
+
+    @Test
+    public void lastError() throws Exception {
+        CompletableFuture<Integer> cf = Observable.<Integer>error(new TestException())
+                .lastStage(null)
+                .toCompletableFuture();
+
+        assertTrue(cf.isDone());
+        assertTrue(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        TestHelper.assertError(cf, TestException.class);
+    }
+
+    @Test
+    public void lastSourceIgnoresCancel() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Integer v = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                    observer.onComplete();
+                    observer.onError(new TestException());
+                    observer.onComplete();
+                }
+            }
+            .lastStage(null)
+            .toCompletableFuture()
+            .get();
+
+            assertEquals((Integer)1, v);
+
+            TestHelper.assertUndeliverable(errors, 0, TestException.class);
+        });
+    }
+
+    @Test
+    public void lastDoubleOnSubscribe() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Integer v = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                    observer.onComplete();
+                }
+            }
+            .lastStage(null)
+            .toCompletableFuture()
+            .get();
+
+            assertEquals((Integer)1, v);
+
+            TestHelper.assertError(errors, 0, ProtocolViolationException.class);
+        });
+    }
+}
diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageSubscriberOrErrorTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageSubscriberOrErrorTest.java
new file mode 100644
index 0000000000..d8e6fbe918
--- /dev/null
+++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageSubscriberOrErrorTest.java
@@ -0,0 +1,469 @@
+/**
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+
+package io.reactivex.rxjava3.internal.jdk8;
+
+import static org.junit.Assert.*;
+
+import java.util.NoSuchElementException;
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.Test;
+
+import io.reactivex.rxjava3.core.*;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.*;
+import io.reactivex.rxjava3.subjects.*;
+import io.reactivex.rxjava3.testsupport.TestHelper;
+
+public class ObservableStageSubscriberOrErrorTest extends RxJavaTest {
+
+    @Test
+    public void firstJust() throws Exception {
+        Integer v = Observable.just(1)
+                .firstOrErrorStage()
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)1, v);
+    }
+
+    @Test
+    public void firstEmpty() throws Exception {
+        TestHelper.assertError(
+                Observable.<Integer>empty()
+                .firstOrErrorStage()
+                .toCompletableFuture(), NoSuchElementException.class);
+    }
+
+    @Test
+    public void firstCancels() throws Exception {
+        BehaviorSubject<Integer> source = BehaviorSubject.createDefault(1);
+
+        Integer v = source
+                .firstOrErrorStage()
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)1, v);
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void firstCompletableFutureCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .firstOrErrorStage()
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.cancel(true);
+
+        assertTrue(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void firstCompletableManualCompleteCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .firstOrErrorStage()
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.complete(1);
+
+        assertTrue(cf.isDone());
+        assertFalse(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+
+        assertEquals((Integer)1, cf.get());
+    }
+
+    @Test
+    public void firstCompletableManualCompleteExceptionallyCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .firstOrErrorStage()
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.completeExceptionally(new TestException());
+
+        assertTrue(cf.isDone());
+        assertTrue(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+
+        TestHelper.assertError(cf, TestException.class);
+    }
+
+    @Test
+    public void firstError() throws Exception {
+        CompletableFuture<Integer> cf = Observable.<Integer>error(new TestException())
+                .firstOrErrorStage()
+                .toCompletableFuture();
+
+        assertTrue(cf.isDone());
+        assertTrue(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        TestHelper.assertError(cf, TestException.class);
+    }
+
+    @Test
+    public void firstSourceIgnoresCancel() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Integer v = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                    observer.onError(new TestException());
+                    observer.onComplete();
+                }
+            }
+            .firstOrErrorStage()
+            .toCompletableFuture()
+            .get();
+
+            assertEquals((Integer)1, v);
+
+            TestHelper.assertUndeliverable(errors, 0, TestException.class);
+        });
+    }
+
+    @Test
+    public void firstDoubleOnSubscribe() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Integer v = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                }
+            }
+            .firstOrErrorStage()
+            .toCompletableFuture()
+            .get();
+
+            assertEquals((Integer)1, v);
+
+            TestHelper.assertError(errors, 0, ProtocolViolationException.class);
+        });
+    }
+
+    @Test
+    public void singleJust() throws Exception {
+        Integer v = Observable.just(1)
+                .singleOrErrorStage()
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)1, v);
+    }
+
+    @Test
+    public void singleEmpty() throws Exception {
+        TestHelper.assertError(
+                Observable.<Integer>empty()
+                .singleOrErrorStage()
+                .toCompletableFuture(), NoSuchElementException.class);
+    }
+
+    @Test
+    public void singleTooManyCancels() throws Exception {
+        ReplaySubject<Integer> source = ReplaySubject.create();
+        source.onNext(1);
+        source.onNext(2);
+
+        TestHelper.assertError(source
+            .singleOrErrorStage()
+            .toCompletableFuture(), IllegalArgumentException.class);
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void singleCompletableFutureCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .singleOrErrorStage()
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.cancel(true);
+
+        assertTrue(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void singleCompletableManualCompleteCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .singleOrErrorStage()
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.complete(1);
+
+        assertTrue(cf.isDone());
+        assertFalse(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+
+        assertEquals((Integer)1, cf.get());
+    }
+
+    @Test
+    public void singleCompletableManualCompleteExceptionallyCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .singleOrErrorStage()
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.completeExceptionally(new TestException());
+
+        assertTrue(cf.isDone());
+        assertTrue(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+
+        TestHelper.assertError(cf, TestException.class);
+    }
+
+    @Test
+    public void singleError() throws Exception {
+        CompletableFuture<Integer> cf = Observable.<Integer>error(new TestException())
+                .singleOrErrorStage()
+                .toCompletableFuture();
+
+        assertTrue(cf.isDone());
+        assertTrue(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        TestHelper.assertError(cf, TestException.class);
+    }
+
+    @Test
+    public void singleSourceIgnoresCancel() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Integer v = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                    observer.onComplete();
+                    observer.onError(new TestException());
+                    observer.onComplete();
+                }
+            }
+            .singleOrErrorStage()
+            .toCompletableFuture()
+            .get();
+
+            assertEquals((Integer)1, v);
+
+            TestHelper.assertUndeliverable(errors, 0, TestException.class);
+        });
+    }
+
+    @Test
+    public void singleDoubleOnSubscribe() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Integer v = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                    observer.onComplete();
+                }
+            }
+            .singleOrErrorStage()
+            .toCompletableFuture()
+            .get();
+
+            assertEquals((Integer)1, v);
+
+            TestHelper.assertError(errors, 0, ProtocolViolationException.class);
+        });
+    }
+
+    @Test
+    public void lastJust() throws Exception {
+        Integer v = Observable.just(1)
+                .lastOrErrorStage()
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)1, v);
+    }
+
+    @Test
+    public void lastRange() throws Exception {
+        Integer v = Observable.range(1, 5)
+                .lastOrErrorStage()
+                .toCompletableFuture()
+                .get();
+
+        assertEquals((Integer)5, v);
+    }
+
+    @Test
+    public void lastEmpty() throws Exception {
+        TestHelper.assertError(Observable.<Integer>empty()
+                .lastOrErrorStage()
+                .toCompletableFuture(), NoSuchElementException.class);
+    }
+
+    @Test
+    public void lastCompletableFutureCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .lastOrErrorStage()
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.cancel(true);
+
+        assertTrue(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+    }
+
+    @Test
+    public void lastCompletableManualCompleteCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .lastOrErrorStage()
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.complete(1);
+
+        assertTrue(cf.isDone());
+        assertFalse(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+
+        assertEquals((Integer)1, cf.get());
+    }
+
+    @Test
+    public void lastCompletableManualCompleteExceptionallyCancels() throws Exception {
+        PublishSubject<Integer> source = PublishSubject.create();
+
+        CompletableFuture<Integer> cf = source
+                .lastOrErrorStage()
+                .toCompletableFuture();
+
+        assertTrue(source.hasObservers());
+
+        cf.completeExceptionally(new TestException());
+
+        assertTrue(cf.isDone());
+        assertTrue(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        assertFalse(source.hasObservers());
+
+        TestHelper.assertError(cf, TestException.class);
+    }
+
+    @Test
+    public void lastError() throws Exception {
+        CompletableFuture<Integer> cf = Observable.<Integer>error(new TestException())
+                .lastOrErrorStage()
+                .toCompletableFuture();
+
+        assertTrue(cf.isDone());
+        assertTrue(cf.isCompletedExceptionally());
+        assertFalse(cf.isCancelled());
+
+        TestHelper.assertError(cf, TestException.class);
+    }
+
+    @Test
+    public void lastSourceIgnoresCancel() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Integer v = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                    observer.onComplete();
+                    observer.onError(new TestException());
+                    observer.onComplete();
+                }
+            }
+            .lastOrErrorStage()
+            .toCompletableFuture()
+            .get();
+
+            assertEquals((Integer)1, v);
+
+            TestHelper.assertUndeliverable(errors, 0, TestException.class);
+        });
+    }
+
+    @Test
+    public void lastDoubleOnSubscribe() throws Throwable {
+        TestHelper.withErrorTracking(errors -> {
+            Integer v = new Observable<Integer>() {
+                @Override
+                protected void subscribeActual(Observer<? super Integer> observer) {
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onSubscribe(Disposable.empty());
+                    observer.onNext(1);
+                    observer.onComplete();
+                }
+            }
+            .lastOrErrorStage()
+            .toCompletableFuture()
+            .get();
+
+            assertEquals((Integer)1, v);
+
+            TestHelper.assertError(errors, 0, ProtocolViolationException.class);
+        });
+    }
+}
diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapEagerTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapEagerTest.java
index e7625da6f9..0adfd655b0 100644
--- a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapEagerTest.java
+++ b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapEagerTest.java
@@ -882,14 +882,14 @@ public Integer call() throws Exception {
 
     @Test
     public void innerErrorAfterPoll() {
-        final UnicastProcessor<Integer> us = UnicastProcessor.create();
-        us.onNext(1);
+        final UnicastProcessor<Integer> up = UnicastProcessor.create();
+        up.onNext(1);
 
         TestSubscriber<Integer> ts = new TestSubscriber<Integer>() {
             @Override
             public void onNext(Integer t) {
                 super.onNext(t);
-                us.onError(new TestException());
+                up.onError(new TestException());
             }
         };
 
@@ -897,7 +897,7 @@ public void onNext(Integer t) {
         .concatMapEager(new Function<Integer, Flowable<Integer>>() {
             @Override
             public Flowable<Integer> apply(Integer v) throws Exception {
-                return us;
+                return up;
             }
         }, 1, 128)
         .subscribe(ts);
@@ -973,12 +973,12 @@ public Integer apply(Integer v) throws Exception {
 
     @Test
     public void fuseAndTake() {
-        UnicastProcessor<Integer> us = UnicastProcessor.create();
+        UnicastProcessor<Integer> up = UnicastProcessor.create();
 
-        us.onNext(1);
-        us.onComplete();
+        up.onNext(1);
+        up.onComplete();
 
-        us.concatMapEager(new Function<Integer, Flowable<Integer>>() {
+        up.concatMapEager(new Function<Integer, Flowable<Integer>>() {
             @Override
             public Flowable<Integer> apply(Integer v) throws Exception {
                 return Flowable.just(1);
diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDistinctTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDistinctTest.java
index 70682d9e03..87d5cda476 100644
--- a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDistinctTest.java
+++ b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDistinctTest.java
@@ -128,13 +128,13 @@ public void fusedSync() {
     public void fusedAsync() {
         TestSubscriberEx<Integer> ts = new TestSubscriberEx<Integer>().setInitialFusionMode(QueueFuseable.ANY);
 
-        UnicastProcessor<Integer> us = UnicastProcessor.create();
+        UnicastProcessor<Integer> up = UnicastProcessor.create();
 
-        us
+        up
         .distinct()
         .subscribe(ts);
 
-        TestHelper.emit(us, 1, 1, 2, 1, 3, 2, 4, 5, 4);
+        TestHelper.emit(up, 1, 1, 2, 1, 3, 2, 4, 5, 4);
 
         ts.assertFusionMode(QueueFuseable.ASYNC)
         .assertResult(1, 2, 3, 4, 5);
diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFilterTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFilterTest.java
index 86ad5aa874..586a7b56c2 100644
--- a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFilterTest.java
+++ b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFilterTest.java
@@ -557,9 +557,9 @@ public boolean test(Integer v) throws Exception {
     public void fusedAsync() {
         TestSubscriberEx<Integer> ts = new TestSubscriberEx<Integer>().setInitialFusionMode(QueueFuseable.ANY);
 
-        UnicastProcessor<Integer> us = UnicastProcessor.create();
+        UnicastProcessor<Integer> up = UnicastProcessor.create();
 
-        us
+        up
         .filter(new Predicate<Integer>() {
             @Override
             public boolean test(Integer v) throws Exception {
@@ -568,7 +568,7 @@ public boolean test(Integer v) throws Exception {
         })
         .subscribe(ts);
 
-        TestHelper.emit(us, 1, 2, 3, 4, 5);
+        TestHelper.emit(up, 1, 2, 3, 4, 5);
 
         ts.assertFusionMode(QueueFuseable.ASYNC)
         .assertResult(2, 4);
diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableMapTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableMapTest.java
index c12aafa21d..1c0e6c58a3 100644
--- a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableMapTest.java
+++ b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableMapTest.java
@@ -583,13 +583,13 @@ public void fusedSync() {
     public void fusedAsync() {
         TestSubscriberEx<Integer> ts = new TestSubscriberEx<Integer>().setInitialFusionMode(QueueFuseable.ANY);
 
-        UnicastProcessor<Integer> us = UnicastProcessor.create();
+        UnicastProcessor<Integer> up = UnicastProcessor.create();
 
-        us
+        up
         .map(Functions.<Integer>identity())
         .subscribe(ts);
 
-        TestHelper.emit(us, 1, 2, 3, 4, 5);
+        TestHelper.emit(up, 1, 2, 3, 4, 5);
 
         ts.assertFusionMode(QueueFuseable.ASYNC)
         .assertResult(1, 2, 3, 4, 5);
diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOnTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOnTest.java
index 73efdfc426..7fbf19fa60 100644
--- a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOnTest.java
+++ b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOnTest.java
@@ -1193,11 +1193,11 @@ public void inputSyncFused() {
 
     @Test
     public void inputAsyncFused() {
-        UnicastProcessor<Integer> us = UnicastProcessor.create();
+        UnicastProcessor<Integer> up = UnicastProcessor.create();
 
-        TestSubscriber<Integer> ts = us.observeOn(Schedulers.single()).test();
+        TestSubscriber<Integer> ts = up.observeOn(Schedulers.single()).test();
 
-        TestHelper.emit(us, 1, 2, 3, 4, 5);
+        TestHelper.emit(up, 1, 2, 3, 4, 5);
 
         ts
         .awaitDone(5, TimeUnit.SECONDS)
@@ -1206,11 +1206,11 @@ public void inputAsyncFused() {
 
     @Test
     public void inputAsyncFusedError() {
-        UnicastProcessor<Integer> us = UnicastProcessor.create();
+        UnicastProcessor<Integer> up = UnicastProcessor.create();
 
-        TestSubscriber<Integer> ts = us.observeOn(Schedulers.single()).test();
+        TestSubscriber<Integer> ts = up.observeOn(Schedulers.single()).test();
 
-        us.onError(new TestException());
+        up.onError(new TestException());
 
         ts
         .awaitDone(5, TimeUnit.SECONDS)
@@ -1219,11 +1219,11 @@ public void inputAsyncFusedError() {
 
     @Test
     public void inputAsyncFusedErrorDelayed() {
-        UnicastProcessor<Integer> us = UnicastProcessor.create();
+        UnicastProcessor<Integer> up = UnicastProcessor.create();
 
-        TestSubscriber<Integer> ts = us.observeOn(Schedulers.single(), true).test();
+        TestSubscriber<Integer> ts = up.observeOn(Schedulers.single(), true).test();
 
-        us.onError(new TestException());
+        up.onError(new TestException());
 
         ts
         .awaitDone(5, TimeUnit.SECONDS)
@@ -1260,12 +1260,12 @@ public void outputFusedReject() {
     public void inputOutputAsyncFusedError() {
         TestSubscriberEx<Integer> ts = new TestSubscriberEx<Integer>().setInitialFusionMode(QueueFuseable.ANY);
 
-        UnicastProcessor<Integer> us = UnicastProcessor.create();
+        UnicastProcessor<Integer> up = UnicastProcessor.create();
 
-        us.observeOn(Schedulers.single())
+        up.observeOn(Schedulers.single())
         .subscribe(ts);
 
-        us.onError(new TestException());
+        up.onError(new TestException());
 
         ts
         .awaitDone(5, TimeUnit.SECONDS)
@@ -1280,12 +1280,12 @@ public void inputOutputAsyncFusedError() {
     public void inputOutputAsyncFusedErrorDelayed() {
         TestSubscriberEx<Integer> ts = new TestSubscriberEx<Integer>().setInitialFusionMode(QueueFuseable.ANY);
 
-        UnicastProcessor<Integer> us = UnicastProcessor.create();
+        UnicastProcessor<Integer> up = UnicastProcessor.create();
 
-        us.observeOn(Schedulers.single(), true)
+        up.observeOn(Schedulers.single(), true)
         .subscribe(ts);
 
-        us.onError(new TestException());
+        up.onError(new TestException());
 
         ts
         .awaitDone(5, TimeUnit.SECONDS)
@@ -1298,11 +1298,11 @@ public void inputOutputAsyncFusedErrorDelayed() {
 
     @Test
     public void outputFusedCancelReentrant() throws Exception {
-        final UnicastProcessor<Integer> us = UnicastProcessor.create();
+        final UnicastProcessor<Integer> up = UnicastProcessor.create();
 
         final CountDownLatch cdl = new CountDownLatch(1);
 
-        us.observeOn(Schedulers.single())
+        up.observeOn(Schedulers.single())
         .subscribe(new FlowableSubscriber<Integer>() {
             Subscription upstream;
             int count;
@@ -1315,7 +1315,7 @@ public void onSubscribe(Subscription s) {
             @Override
             public void onNext(Integer value) {
                 if (++count == 1) {
-                    us.onNext(2);
+                    up.onNext(2);
                     upstream.cancel();
                     cdl.countDown();
                 }
@@ -1332,7 +1332,7 @@ public void onComplete() {
             }
         });
 
-        us.onNext(1);
+        up.onNext(1);
 
         cdl.await();
     }
diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDistinctUntilChangedTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDistinctUntilChangedTest.java
index da1d67e819..190506aed2 100644
--- a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDistinctUntilChangedTest.java
+++ b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDistinctUntilChangedTest.java
@@ -169,9 +169,9 @@ public boolean test(Integer a, Integer b) throws Exception {
     public void fusedAsync() {
         TestObserverEx<Integer> to = new TestObserverEx<>(QueueFuseable.ANY);
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
+        UnicastSubject<Integer> us = UnicastSubject.create();
 
-        up
+        us
         .distinctUntilChanged(new BiPredicate<Integer, Integer>() {
             @Override
             public boolean test(Integer a, Integer b) throws Exception {
@@ -180,7 +180,7 @@ public boolean test(Integer a, Integer b) throws Exception {
         })
         .subscribe(to);
 
-        TestHelper.emit(up, 1, 2, 2, 3, 3, 4, 5);
+        TestHelper.emit(us, 1, 2, 2, 3, 3, 4, 5);
 
         to.assertFuseable()
         .assertFusionMode(QueueFuseable.ASYNC)
diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoAfterNextTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoAfterNextTest.java
index 5b6cd18987..411989a0f1 100644
--- a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoAfterNextTest.java
+++ b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoAfterNextTest.java
@@ -131,11 +131,11 @@ public void asyncFusedRejected() {
     public void asyncFused() {
         TestObserverEx<Integer> to0 = new TestObserverEx<>(QueueFuseable.ASYNC);
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
+        UnicastSubject<Integer> us = UnicastSubject.create();
 
-        TestHelper.emit(up, 1, 2, 3, 4, 5);
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
 
-        up
+        us
         .doAfterNext(afterNext)
         .subscribe(to0);
 
@@ -228,11 +228,11 @@ public void asyncFusedRejectedConditional() {
     public void asyncFusedConditional() {
         TestObserverEx<Integer> to0 = new TestObserverEx<>(QueueFuseable.ASYNC);
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
+        UnicastSubject<Integer> us = UnicastSubject.create();
 
-        TestHelper.emit(up, 1, 2, 3, 4, 5);
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
 
-        up
+        us
         .doAfterNext(afterNext)
         .filter(Functions.alwaysTrue())
         .subscribe(to0);
diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoFinallyTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoFinallyTest.java
index a0eeccb405..0cc21b8f13 100644
--- a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoFinallyTest.java
+++ b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoFinallyTest.java
@@ -129,10 +129,10 @@ public void syncFusedBoundary() {
     public void asyncFused() {
         TestObserverEx<Integer> to = new TestObserverEx<>(QueueFuseable.ASYNC);
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
-        TestHelper.emit(up, 1, 2, 3, 4, 5);
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
 
-        up
+        us
         .doFinally(this)
         .subscribe(to);
 
@@ -146,10 +146,10 @@ public void asyncFused() {
     public void asyncFusedBoundary() {
         TestObserverEx<Integer> to = new TestObserverEx<>(QueueFuseable.ASYNC | QueueFuseable.BOUNDARY);
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
-        TestHelper.emit(up, 1, 2, 3, 4, 5);
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
 
-        up
+        us
         .doFinally(this)
         .subscribe(to);
 
@@ -267,10 +267,10 @@ public void syncFusedBoundaryConditional() {
     public void asyncFusedConditional() {
         TestObserverEx<Integer> to = new TestObserverEx<>(QueueFuseable.ASYNC);
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
-        TestHelper.emit(up, 1, 2, 3, 4, 5);
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
 
-        up
+        us
         .doFinally(this)
         .filter(Functions.alwaysTrue())
         .subscribe(to);
@@ -285,10 +285,10 @@ public void asyncFusedConditional() {
     public void asyncFusedBoundaryConditional() {
         TestObserverEx<Integer> to = new TestObserverEx<>(QueueFuseable.ASYNC | QueueFuseable.BOUNDARY);
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
-        TestHelper.emit(up, 1, 2, 3, 4, 5);
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
 
-        up
+        us
         .doFinally(this)
         .filter(Functions.alwaysTrue())
         .subscribe(to);
diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoOnEachTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoOnEachTest.java
index 0482d23906..6b8627b02b 100644
--- a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoOnEachTest.java
+++ b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoOnEachTest.java
@@ -596,9 +596,9 @@ public void fusedAsync() {
 
         final int[] call = { 0, 0 };
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
+        UnicastSubject<Integer> us = UnicastSubject.create();
 
-        up
+        us
         .doOnNext(new Consumer<Integer>() {
             @Override
             public void accept(Integer v) throws Exception {
@@ -613,7 +613,7 @@ public void run() throws Exception {
         })
         .subscribe(to);
 
-        TestHelper.emit(up, 1, 2, 3, 4, 5);
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
 
         to.assertFuseable()
         .assertFusionMode(QueueFuseable.ASYNC)
@@ -630,9 +630,9 @@ public void fusedAsyncConditional() {
 
         final int[] call = { 0, 0 };
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
+        UnicastSubject<Integer> us = UnicastSubject.create();
 
-        up
+        us
         .doOnNext(new Consumer<Integer>() {
             @Override
             public void accept(Integer v) throws Exception {
@@ -648,7 +648,7 @@ public void run() throws Exception {
         .filter(Functions.alwaysTrue())
         .subscribe(to);
 
-        TestHelper.emit(up, 1, 2, 3, 4, 5);
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
 
         to.assertFuseable()
         .assertFusionMode(QueueFuseable.ASYNC)
@@ -665,9 +665,9 @@ public void fusedAsyncConditional2() {
 
         final int[] call = { 0, 0 };
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
+        UnicastSubject<Integer> us = UnicastSubject.create();
 
-        up.hide()
+        us.hide()
         .doOnNext(new Consumer<Integer>() {
             @Override
             public void accept(Integer v) throws Exception {
@@ -683,7 +683,7 @@ public void run() throws Exception {
         .filter(Functions.alwaysTrue())
         .subscribe(to);
 
-        TestHelper.emit(up, 1, 2, 3, 4, 5);
+        TestHelper.emit(us, 1, 2, 3, 4, 5);
 
         to.assertFuseable()
         .assertFusionMode(QueueFuseable.NONE)
diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableObserveOnTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableObserveOnTest.java
index 579b713313..2b8c2e0274 100644
--- a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableObserveOnTest.java
+++ b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableObserveOnTest.java
@@ -775,12 +775,12 @@ public void workerNotDisposedPrematurelySyncInNormalOut() {
     public void workerNotDisposedPrematurelyAsyncInNormalOut() {
         DisposeTrackingScheduler s = new DisposeTrackingScheduler();
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
-        up.onNext(1);
-        up.onComplete();
+        UnicastSubject<Integer> us = UnicastSubject.create();
+        us.onNext(1);
+        us.onComplete();
 
         Observable.concat(
-                up.observeOn(s),
+                us.observeOn(s),
                 Observable.just(2)
         )
         .test()
diff --git a/src/test/java/io/reactivex/rxjava3/processors/UnicastProcessorTest.java b/src/test/java/io/reactivex/rxjava3/processors/UnicastProcessorTest.java
index 53b7062028..002d6a17d3 100644
--- a/src/test/java/io/reactivex/rxjava3/processors/UnicastProcessorTest.java
+++ b/src/test/java/io/reactivex/rxjava3/processors/UnicastProcessorTest.java
@@ -131,14 +131,14 @@ public void run() {
     public void onTerminateCalledWhenOnError() {
         final AtomicBoolean didRunOnTerminate = new AtomicBoolean();
 
-        UnicastProcessor<Integer> us = UnicastProcessor.create(Observable.bufferSize(), new Runnable() {
+        UnicastProcessor<Integer> up = UnicastProcessor.create(Observable.bufferSize(), new Runnable() {
             @Override public void run() {
                 didRunOnTerminate.set(true);
             }
         });
 
         assertFalse(didRunOnTerminate.get());
-        us.onError(new RuntimeException("some error"));
+        up.onError(new RuntimeException("some error"));
         assertTrue(didRunOnTerminate.get());
     }
 
@@ -146,14 +146,14 @@ public void onTerminateCalledWhenOnError() {
     public void onTerminateCalledWhenOnComplete() {
         final AtomicBoolean didRunOnTerminate = new AtomicBoolean();
 
-        UnicastProcessor<Integer> us = UnicastProcessor.create(Observable.bufferSize(), new Runnable() {
+        UnicastProcessor<Integer> up = UnicastProcessor.create(Observable.bufferSize(), new Runnable() {
             @Override public void run() {
                 didRunOnTerminate.set(true);
             }
         });
 
         assertFalse(didRunOnTerminate.get());
-        us.onComplete();
+        up.onComplete();
         assertTrue(didRunOnTerminate.get());
     }
 
@@ -161,13 +161,13 @@ public void onTerminateCalledWhenOnComplete() {
     public void onTerminateCalledWhenCanceled() {
         final AtomicBoolean didRunOnTerminate = new AtomicBoolean();
 
-        UnicastProcessor<Integer> us = UnicastProcessor.create(Observable.bufferSize(), new Runnable() {
+        UnicastProcessor<Integer> up = UnicastProcessor.create(Observable.bufferSize(), new Runnable() {
             @Override public void run() {
                 didRunOnTerminate.set(true);
             }
         });
 
-        final Disposable subscribe = us.subscribe();
+        final Disposable subscribe = up.subscribe();
 
         assertFalse(didRunOnTerminate.get());
         subscribe.dispose();
@@ -327,7 +327,7 @@ public void run() {
     @Test
     public void subscribeRace() {
         for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) {
-            final UnicastProcessor<Integer> us = UnicastProcessor.create();
+            final UnicastProcessor<Integer> up = UnicastProcessor.create();
 
             final TestSubscriberEx<Integer> ts1 = new TestSubscriberEx<>();
             final TestSubscriberEx<Integer> ts2 = new TestSubscriberEx<>();
@@ -335,14 +335,14 @@ public void subscribeRace() {
             Runnable r1 = new Runnable() {
                 @Override
                 public void run() {
-                    us.subscribe(ts1);
+                    up.subscribe(ts1);
                 }
             };
 
             Runnable r2 = new Runnable() {
                 @Override
                 public void run() {
-                    us.subscribe(ts2);
+                    up.subscribe(ts2);
                 }
             };
 
@@ -361,67 +361,67 @@ public void run() {
 
     @Test
     public void hasObservers() {
-        UnicastProcessor<Integer> us = UnicastProcessor.create();
+        UnicastProcessor<Integer> up = UnicastProcessor.create();
 
-        assertFalse(us.hasSubscribers());
+        assertFalse(up.hasSubscribers());
 
-        TestSubscriber<Integer> ts = us.test();
+        TestSubscriber<Integer> ts = up.test();
 
-        assertTrue(us.hasSubscribers());
+        assertTrue(up.hasSubscribers());
 
         ts.cancel();
 
-        assertFalse(us.hasSubscribers());
+        assertFalse(up.hasSubscribers());
     }
 
     @Test
     public void drainFusedFailFast() {
-        UnicastProcessor<Integer> us = UnicastProcessor.create(false);
+        UnicastProcessor<Integer> up = UnicastProcessor.create(false);
 
-        TestSubscriberEx<Integer> ts = us.to(TestHelper.<Integer>testSubscriber(1, QueueFuseable.ANY, false));
+        TestSubscriberEx<Integer> ts = up.to(TestHelper.<Integer>testSubscriber(1, QueueFuseable.ANY, false));
 
-        us.done = true;
-        us.drainFused(ts);
+        up.done = true;
+        up.drainFused(ts);
 
         ts.assertResult();
     }
 
     @Test
     public void drainFusedFailFastEmpty() {
-        UnicastProcessor<Integer> us = UnicastProcessor.create(false);
+        UnicastProcessor<Integer> up = UnicastProcessor.create(false);
 
-        TestSubscriberEx<Integer> ts = us.to(TestHelper.<Integer>testSubscriber(1, QueueFuseable.ANY, false));
+        TestSubscriberEx<Integer> ts = up.to(TestHelper.<Integer>testSubscriber(1, QueueFuseable.ANY, false));
 
-        us.drainFused(ts);
+        up.drainFused(ts);
 
         ts.assertEmpty();
     }
 
     @Test
     public void checkTerminatedFailFastEmpty() {
-        UnicastProcessor<Integer> us = UnicastProcessor.create(false);
+        UnicastProcessor<Integer> up = UnicastProcessor.create(false);
 
-        TestSubscriberEx<Integer> ts = us.to(TestHelper.<Integer>testSubscriber(1, QueueFuseable.ANY, false));
+        TestSubscriberEx<Integer> ts = up.to(TestHelper.<Integer>testSubscriber(1, QueueFuseable.ANY, false));
 
-        us.checkTerminated(true, true, false, ts, us.queue);
+        up.checkTerminated(true, true, false, ts, up.queue);
 
         ts.assertEmpty();
     }
 
     @Test
     public void alreadyCancelled() {
-        UnicastProcessor<Integer> us = UnicastProcessor.create(false);
+        UnicastProcessor<Integer> up = UnicastProcessor.create(false);
 
-        us.test().cancel();
+        up.test().cancel();
 
         BooleanSubscription bs = new BooleanSubscription();
-        us.onSubscribe(bs);
+        up.onSubscribe(bs);
 
         assertTrue(bs.isCancelled());
 
         List<Throwable> errors = TestHelper.trackPluginErrors();
         try {
-            us.onError(new TestException());
+            up.onError(new TestException());
 
             TestHelper.assertUndeliverable(errors, 0, TestException.class);
         } finally {
@@ -431,9 +431,9 @@ public void alreadyCancelled() {
 
     @Test
     public void unicastSubscriptionBadRequest() {
-        UnicastProcessor<Integer> us = UnicastProcessor.create(false);
+        UnicastProcessor<Integer> up = UnicastProcessor.create(false);
 
-        UnicastProcessor<Integer>.UnicastQueueSubscription usc = (UnicastProcessor<Integer>.UnicastQueueSubscription)us.wip;
+        UnicastProcessor<Integer>.UnicastQueueSubscription usc = (UnicastProcessor<Integer>.UnicastQueueSubscription)up.wip;
 
         List<Throwable> errors = TestHelper.trackPluginErrors();
         try {
@@ -449,17 +449,17 @@ public void fusedNoConcurrentCleanDueToCancel() {
         for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) {
             List<Throwable> errors = TestHelper.trackPluginErrors();
             try {
-                final UnicastProcessor<Integer> us = UnicastProcessor.create();
+                final UnicastProcessor<Integer> up = UnicastProcessor.create();
 
-                TestObserver<Integer> to = us
+                TestObserver<Integer> to = up
                 .observeOn(Schedulers.io())
                 .map(Functions.<Integer>identity())
                 .observeOn(Schedulers.single())
                 .firstOrError()
                 .test();
 
-                for (int i = 0; us.hasSubscribers(); i++) {
-                    us.onNext(i);
+                for (int i = 0; up.hasSubscribers(); i++) {
+                    up.onNext(i);
                 }
 
                 to
diff --git a/src/test/java/io/reactivex/rxjava3/subjects/UnicastSubjectTest.java b/src/test/java/io/reactivex/rxjava3/subjects/UnicastSubjectTest.java
index 17e4cb0a72..ac000e4228 100644
--- a/src/test/java/io/reactivex/rxjava3/subjects/UnicastSubjectTest.java
+++ b/src/test/java/io/reactivex/rxjava3/subjects/UnicastSubjectTest.java
@@ -228,14 +228,14 @@ public void zeroCapacityHint() {
     public void completeCancelRace() {
         for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) {
             final int[] calls = { 0 };
-            final UnicastSubject<Object> up = UnicastSubject.create(100, new Runnable() {
+            final UnicastSubject<Object> us = UnicastSubject.create(100, new Runnable() {
                 @Override
                 public void run() {
                     calls[0]++;
                 }
             });
 
-            final TestObserver<Object> to = up.test();
+            final TestObserver<Object> to = us.test();
 
             Runnable r1 = new Runnable() {
                 @Override
@@ -247,7 +247,7 @@ public void run() {
             Runnable r2 = new Runnable() {
                 @Override
                 public void run() {
-                    up.onComplete();
+                    us.onComplete();
                 }
             };
 
diff --git a/src/test/java/io/reactivex/rxjava3/testsupport/TestObserverExTest.java b/src/test/java/io/reactivex/rxjava3/testsupport/TestObserverExTest.java
index 9ee8a1cfc5..0d6edd6c3d 100644
--- a/src/test/java/io/reactivex/rxjava3/testsupport/TestObserverExTest.java
+++ b/src/test/java/io/reactivex/rxjava3/testsupport/TestObserverExTest.java
@@ -1094,16 +1094,16 @@ public void asyncQueueThrows() {
         TestObserverEx<Object> to = new TestObserverEx<>();
         to.setInitialFusionMode(QueueFuseable.ANY);
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
+        UnicastSubject<Integer> us = UnicastSubject.create();
 
-        up
+        us
         .map(new Function<Integer, Object>() {
             @Override
             public Object apply(Integer v) throws Exception { throw new TestException(); }
         })
         .subscribe(to);
 
-        up.onNext(1);
+        us.onNext(1);
 
         to.assertSubscribed()
         .assertFuseable()
@@ -1132,13 +1132,13 @@ public void asyncFusion() {
         TestObserverEx<Object> to = new TestObserverEx<>();
         to.setInitialFusionMode(QueueFuseable.ANY);
 
-        UnicastSubject<Integer> up = UnicastSubject.create();
+        UnicastSubject<Integer> us = UnicastSubject.create();
 
-        up
+        us
         .subscribe(to);
 
-        up.onNext(1);
-        up.onComplete();
+        us.onNext(1);
+        us.onComplete();
 
         to.assertSubscribed()
         .assertFuseable()
diff --git a/src/test/java/io/reactivex/rxjava3/validators/CheckLocalVariablesInTests.java b/src/test/java/io/reactivex/rxjava3/validators/CheckLocalVariablesInTests.java
index 40a976a165..df6f0a378a 100644
--- a/src/test/java/io/reactivex/rxjava3/validators/CheckLocalVariablesInTests.java
+++ b/src/test/java/io/reactivex/rxjava3/validators/CheckLocalVariablesInTests.java
@@ -94,7 +94,15 @@ static void findPattern(String pattern, boolean checkMain) throws Exception {
                                                 .append(fname)
                                                 .append("#L").append(lineNum)
                                                 .append("    ").append(line)
-                                                .append("\n");
+                                                .append("\n")
+                                                .append(" at ")
+                                                .append(fname.replace(".java", ""))
+                                                .append(".method(")
+                                                .append(fname)
+                                                .append(":")
+                                                .append(lineNum)
+                                                .append(")\n");
+
                                                 total++;
                                             }
                                         }
@@ -111,9 +119,7 @@ static void findPattern(String pattern, boolean checkMain) throws Exception {
             }
         }
         if (total != 0) {
-            fail.append("Found ")
-            .append(total)
-            .append(" instances");
+            fail.insert(0, "Found " + total + " instances");
             System.out.println(fail);
             throw new AssertionError(fail.toString());
         }
@@ -149,6 +155,16 @@ public void publishProcessorAsPs() throws Exception {
         findPattern("PublishProcessor<.*>\\s+ps");
     }
 
+    @Test
+    public void unicastSubjectAsUp() throws Exception {
+        findPattern("UnicastSubject<.*>\\s+up");
+    }
+
+    @Test
+    public void unicastProcessorAsUs() throws Exception {
+        findPattern("UnicastProcessor<.*>\\s+us");
+    }
+
     @Test
     public void behaviorProcessorAsBs() throws Exception {
         findPattern("BehaviorProcessor<.*>\\s+bs");
diff --git a/src/test/java/io/reactivex/rxjava3/validators/JavadocWording.java b/src/test/java/io/reactivex/rxjava3/validators/JavadocWording.java
index e8219df148..4a4baaf8ae 100644
--- a/src/test/java/io/reactivex/rxjava3/validators/JavadocWording.java
+++ b/src/test/java/io/reactivex/rxjava3/validators/JavadocWording.java
@@ -57,8 +57,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception {
                                 && !m.signature.contains("Flowable")
                                 && !m.signature.contains("Observable")
                                 && !m.signature.contains("ObservableSource")) {
-                            e.append("java.lang.RuntimeException: Maybe doc mentions onNext but no Flowable/Observable in signature\r\n at io.reactivex.")
-                            .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Maybe doc mentions onNext but no Flowable/Observable in signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -74,8 +74,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception {
                                 && !m.signature.contains("Flowable")
                                 && !m.signature.contains("TestSubscriber")
                         ) {
-                            e.append("java.lang.RuntimeException: Maybe doc mentions Subscriber but not using Flowable\r\n at io.reactivex.")
-                            .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Maybe doc mentions Subscriber but not using Flowable\r\n at io.reactivex.rxjava3.core.")
+                            .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -90,8 +90,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception {
                         if (!m.signature.contains("Publisher")
                                 && !m.signature.contains("Flowable")
                         ) {
-                            e.append("java.lang.RuntimeException: Maybe doc mentions Subscription but not using Flowable\r\n at io.reactivex.")
-                            .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Maybe doc mentions Subscription but not using Flowable\r\n at io.reactivex.rxjava3.core.")
+                            .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -108,8 +108,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception {
                                 && !m.signature.contains("TestObserver")) {
 
                             if (idx < 5 || !m.javadoc.substring(idx - 5, idx + 8).equals("MaybeObserver")) {
-                                e.append("java.lang.RuntimeException: Maybe doc mentions Observer but not using Observable\r\n at io.reactivex.")
-                                .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Maybe doc mentions Observer but not using Observable\r\n at io.reactivex.rxjava3.core.")
+                                .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
 
@@ -124,8 +124,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception {
                     if (idx >= 0) {
                         if (!m.signature.contains("Publisher")) {
                             if (idx == 0 || !m.javadoc.substring(idx - 1, idx + 9).equals("(Publisher")) {
-                                e.append("java.lang.RuntimeException: Maybe doc mentions Publisher but not in the signature\r\n at io.reactivex.")
-                                .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Maybe doc mentions Publisher but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                                .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
 
@@ -139,8 +139,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception {
                     int idx = m.javadoc.indexOf("Flowable", jdx);
                     if (idx >= 0) {
                         if (!m.signature.contains("Flowable")) {
-                            e.append("java.lang.RuntimeException: Maybe doc mentions Flowable but not in the signature\r\n at io.reactivex.")
-                            .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Maybe doc mentions Flowable but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
                         jdx = idx + 6;
                     } else {
@@ -154,7 +154,7 @@ public void maybeDocRefersToMaybeTypes() throws Exception {
                         int j = m.javadoc.indexOf("#toSingle", jdx);
                         int k = m.javadoc.indexOf("{@code Single", jdx);
                         if (!m.signature.contains("Single") && (j + 3 != idx && k + 7 != idx)) {
-                            e.append("java.lang.RuntimeException: Maybe doc mentions Single but not in the signature\r\n at io.reactivex.")
+                            e.append("java.lang.RuntimeException: Maybe doc mentions Single but not in the signature\r\n at io.reactivex.rxjava3.core.")
                             .append("Maybe(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
                         jdx = idx + 6;
@@ -167,8 +167,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception {
                     int idx = m.javadoc.indexOf("SingleSource", jdx);
                     if (idx >= 0) {
                         if (!m.signature.contains("SingleSource")) {
-                            e.append("java.lang.RuntimeException: Maybe doc mentions SingleSource but not in the signature\r\n at io.reactivex.")
-                            .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Maybe doc mentions SingleSource but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
                         jdx = idx + 6;
                     } else {
@@ -180,8 +180,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception {
                     int idx = m.javadoc.indexOf("Observable", jdx);
                     if (idx >= 0) {
                         if (!m.signature.contains("Observable")) {
-                            e.append("java.lang.RuntimeException: Maybe doc mentions Observable but not in the signature\r\n at io.reactivex.")
-                            .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Maybe doc mentions Observable but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
                         jdx = idx + 6;
                     } else {
@@ -193,8 +193,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception {
                     int idx = m.javadoc.indexOf("ObservableSource", jdx);
                     if (idx >= 0) {
                         if (!m.signature.contains("ObservableSource")) {
-                            e.append("java.lang.RuntimeException: Maybe doc mentions ObservableSource but not in the signature\r\n at io.reactivex.")
-                            .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Maybe doc mentions ObservableSource but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
                         jdx = idx + 6;
                     } else {
@@ -233,8 +233,8 @@ public void flowableDocRefersToFlowableTypes() throws Exception {
                                 && !m.signature.contains("MaybeSource")
                                 && !m.signature.contains("Single")
                                 && !m.signature.contains("SingleSource")) {
-                            e.append("java.lang.RuntimeException: Flowable doc mentions onSuccess\r\n at io.reactivex.")
-                            .append("Flowable (Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Flowable doc mentions onSuccess\r\n at io.reactivex.rxjava3.core.")
+                            .append("Flowable.method(Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -248,8 +248,8 @@ public void flowableDocRefersToFlowableTypes() throws Exception {
                     if (idx >= 0) {
                         if (!m.signature.contains("ObservableSource")
                                 && !m.signature.contains("Observable")) {
-                            e.append("java.lang.RuntimeException: Flowable doc mentions Observer but not using Flowable\r\n at io.reactivex.")
-                            .append("Flowable (Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Flowable doc mentions Observer but not using Flowable\r\n at io.reactivex.rxjava3.core.")
+                            .append("Flowable.method(Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -273,8 +273,8 @@ public void flowableDocRefersToFlowableTypes() throws Exception {
                         ) {
                             CharSequence subSequence = m.javadoc.subSequence(idx - 6, idx + 11);
                             if (idx < 6 || !subSequence.equals("{@link Disposable")) {
-                                e.append("java.lang.RuntimeException: Flowable doc mentions Disposable but not using Flowable\r\n at io.reactivex.")
-                                .append("Flowable (Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Flowable doc mentions Disposable but not using Flowable\r\n at io.reactivex.rxjava3.core.")
+                                .append("Flowable.method(Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
 
@@ -288,8 +288,8 @@ public void flowableDocRefersToFlowableTypes() throws Exception {
                     int idx = m.javadoc.indexOf("Observable", jdx);
                     if (idx >= 0) {
                         if (!m.signature.contains("Observable")) {
-                            e.append("java.lang.RuntimeException: Flowable doc mentions Observable but not in the signature\r\n at io.reactivex.")
-                            .append("Flowable (Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Flowable doc mentions Observable but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Flowable.method(Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -302,8 +302,8 @@ public void flowableDocRefersToFlowableTypes() throws Exception {
                     int idx = m.javadoc.indexOf("ObservableSource", jdx);
                     if (idx >= 0) {
                         if (!m.signature.contains("ObservableSource")) {
-                            e.append("java.lang.RuntimeException: Flowable doc mentions ObservableSource but not in the signature\r\n at io.reactivex.")
-                            .append("Flowable (Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Flowable doc mentions ObservableSource but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Flowable.method(Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
                         jdx = idx + 6;
                     } else {
@@ -342,8 +342,8 @@ public void observableDocRefersToObservableTypes() throws Exception {
                                 && !m.signature.contains("MaybeSource")
                                 && !m.signature.contains("Single")
                                 && !m.signature.contains("SingleSource")) {
-                            e.append("java.lang.RuntimeException: Observable doc mentions onSuccess\r\n at io.reactivex.")
-                            .append("Observable (Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Observable doc mentions onSuccess\r\n at io.reactivex.rxjava3.core.")
+                            .append("Observable.method(Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -358,8 +358,8 @@ public void observableDocRefersToObservableTypes() throws Exception {
                         if (!m.signature.contains("Flowable")
                                 && !m.signature.contains("Publisher")
                         ) {
-                            e.append("java.lang.RuntimeException: Observable doc mentions Subscription but not using Flowable\r\n at io.reactivex.")
-                            .append("Observable (Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Observable doc mentions Subscription but not using Flowable\r\n at io.reactivex.rxjava3.core.")
+                            .append("Observable.method(Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -373,8 +373,8 @@ public void observableDocRefersToObservableTypes() throws Exception {
                     if (idx >= 0) {
                         if (!m.signature.contains("Flowable")) {
                             if (idx < 6 || !m.javadoc.substring(idx - 6, idx + 8).equals("@link Flowable")) {
-                                e.append("java.lang.RuntimeException: Observable doc mentions Flowable but not in the signature\r\n at io.reactivex.")
-                                .append("Observable (Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Observable doc mentions Flowable but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                                .append("Observable.method(Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
 
@@ -388,8 +388,8 @@ public void observableDocRefersToObservableTypes() throws Exception {
                     int idx = m.javadoc.indexOf("Publisher", jdx);
                     if (idx >= 0) {
                         if (!m.signature.contains("Publisher")) {
-                            e.append("java.lang.RuntimeException: Observable doc mentions Publisher but not in the signature\r\n at io.reactivex.")
-                            .append("Observable (Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Observable doc mentions Publisher but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Observable.method(Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
                         jdx = idx + 6;
                     } else {
@@ -402,8 +402,8 @@ public void observableDocRefersToObservableTypes() throws Exception {
                     if (idx >= 0) {
                         if (!m.signature.contains("Publisher")
                                 && !m.signature.contains("Flowable")) {
-                            e.append("java.lang.RuntimeException: Observable doc mentions Subscriber but not using Flowable\r\n at io.reactivex.")
-                            .append("Observable (Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Observable doc mentions Subscriber but not using Flowable\r\n at io.reactivex.rxjava3.core.")
+                            .append("Observable.method(Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -443,8 +443,8 @@ public void singleDocRefersToSingleTypes() throws Exception {
                                 && !m.signature.contains("Flowable")
                                 && !m.signature.contains("Observable")
                                 && !m.signature.contains("ObservableSource")) {
-                            e.append("java.lang.RuntimeException: Single doc mentions onNext but no Flowable/Observable in signature\r\n at io.reactivex.")
-                            .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Single doc mentions onNext but no Flowable/Observable in signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -459,8 +459,8 @@ public void singleDocRefersToSingleTypes() throws Exception {
                         if (!m.signature.contains("Publisher")
                                 && !m.signature.contains("Flowable")
                                 && !m.signature.contains("TestSubscriber")) {
-                            e.append("java.lang.RuntimeException: Single doc mentions Subscriber but not using Flowable\r\n at io.reactivex.")
-                            .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Single doc mentions Subscriber but not using Flowable\r\n at io.reactivex.rxjava3.core.")
+                            .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -475,8 +475,8 @@ public void singleDocRefersToSingleTypes() throws Exception {
                         if (!m.signature.contains("Flowable")
                                 && !m.signature.contains("Publisher")
                         ) {
-                            e.append("java.lang.RuntimeException: Single doc mentions Subscription but not using Flowable\r\n at io.reactivex.")
-                            .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Single doc mentions Subscription but not using Flowable\r\n at io.reactivex.rxjava3.core.")
+                            .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -493,8 +493,8 @@ public void singleDocRefersToSingleTypes() throws Exception {
                                 && !m.signature.contains("TestObserver")) {
 
                             if (idx < 6 || !m.javadoc.substring(idx - 6, idx + 8).equals("SingleObserver")) {
-                                e.append("java.lang.RuntimeException: Single doc mentions Observer but not using Observable\r\n at io.reactivex.")
-                                .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Single doc mentions Observer but not using Observable\r\n at io.reactivex.rxjava3.core.")
+                                .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
 
@@ -509,8 +509,8 @@ public void singleDocRefersToSingleTypes() throws Exception {
                     if (idx >= 0) {
                         if (!m.signature.contains("Publisher")) {
                             if (idx == 0 || !m.javadoc.substring(idx - 1, idx + 9).equals("(Publisher")) {
-                                e.append("java.lang.RuntimeException: Single doc mentions Publisher but not in the signature\r\n at io.reactivex.")
-                                .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Single doc mentions Publisher but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                                .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
 
@@ -524,8 +524,8 @@ public void singleDocRefersToSingleTypes() throws Exception {
                     int idx = m.javadoc.indexOf(" Flowable", jdx);
                     if (idx >= 0) {
                         if (!m.signature.contains("Flowable")) {
-                            e.append("java.lang.RuntimeException: Single doc mentions Flowable but not in the signature\r\n at io.reactivex.")
-                            .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Single doc mentions Flowable but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
                         jdx = idx + 6;
                     } else {
@@ -537,8 +537,8 @@ public void singleDocRefersToSingleTypes() throws Exception {
                     int idx = m.javadoc.indexOf(" Maybe", jdx);
                     if (idx >= 0) {
                         if (!m.signature.contains("Maybe")) {
-                            e.append("java.lang.RuntimeException: Single doc mentions Maybe but not in the signature\r\n at io.reactivex.")
-                            .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Single doc mentions Maybe but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
                         jdx = idx + 6;
                     } else {
@@ -550,8 +550,8 @@ public void singleDocRefersToSingleTypes() throws Exception {
                     int idx = m.javadoc.indexOf(" MaybeSource", jdx);
                     if (idx >= 0) {
                         if (!m.signature.contains("MaybeSource")) {
-                            e.append("java.lang.RuntimeException: Single doc mentions SingleSource but not in the signature\r\n at io.reactivex.")
-                            .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Single doc mentions SingleSource but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
                         jdx = idx + 6;
                     } else {
@@ -563,8 +563,8 @@ public void singleDocRefersToSingleTypes() throws Exception {
                     int idx = m.javadoc.indexOf(" Observable", jdx);
                     if (idx >= 0) {
                         if (!m.signature.contains("Observable")) {
-                            e.append("java.lang.RuntimeException: Single doc mentions Observable but not in the signature\r\n at io.reactivex.")
-                            .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Single doc mentions Observable but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
                         jdx = idx + 6;
                     } else {
@@ -576,8 +576,8 @@ public void singleDocRefersToSingleTypes() throws Exception {
                     int idx = m.javadoc.indexOf(" ObservableSource", jdx);
                     if (idx >= 0) {
                         if (!m.signature.contains("ObservableSource")) {
-                            e.append("java.lang.RuntimeException: Single doc mentions ObservableSource but not in the signature\r\n at io.reactivex.")
-                            .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Single doc mentions ObservableSource but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
                         jdx = idx + 6;
                     } else {
@@ -617,8 +617,8 @@ public void completableDocRefersToCompletableTypes() throws Exception {
                                 && !m.signature.contains("Flowable")
                                 && !m.signature.contains("Observable")
                                 && !m.signature.contains("ObservableSource")) {
-                            e.append("java.lang.RuntimeException: Completable doc mentions onNext but no Flowable/Observable in signature\r\n at io.reactivex.")
-                            .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Completable doc mentions onNext but no Flowable/Observable in signature\r\n at io.reactivex.rxjava3.core.")
+                            .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -633,8 +633,8 @@ public void completableDocRefersToCompletableTypes() throws Exception {
                         if (!m.signature.contains("Publisher")
                                 && !m.signature.contains("Flowable")
                                 && !m.signature.contains("TestSubscriber")) {
-                            e.append("java.lang.RuntimeException: Completable doc mentions Subscriber but not using Flowable\r\n at io.reactivex.")
-                            .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Completable doc mentions Subscriber but not using Flowable\r\n at io.reactivex.rxjava3.core.")
+                            .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -649,8 +649,8 @@ public void completableDocRefersToCompletableTypes() throws Exception {
                         if (!m.signature.contains("Flowable")
                                 && !m.signature.contains("Publisher")
                         ) {
-                            e.append("java.lang.RuntimeException: Completable doc mentions Subscription but not using Flowable\r\n at io.reactivex.")
-                            .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                            e.append("java.lang.RuntimeException: Completable doc mentions Subscription but not using Flowable\r\n at io.reactivex.rxjava3.core.")
+                            .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                         }
 
                         jdx = idx + 6;
@@ -667,8 +667,8 @@ public void completableDocRefersToCompletableTypes() throws Exception {
                                 && !m.signature.contains("TestObserver")) {
 
                             if (idx < 11 || !m.javadoc.substring(idx - 11, idx + 8).equals("CompletableObserver")) {
-                                e.append("java.lang.RuntimeException: Completable doc mentions Observer but not using Observable\r\n at io.reactivex.")
-                                .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Completable doc mentions Observer but not using Observable\r\n at io.reactivex.rxjava3.core.")
+                                .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
 
@@ -683,8 +683,8 @@ public void completableDocRefersToCompletableTypes() throws Exception {
                     if (idx >= 0) {
                         if (!m.signature.contains("Publisher")) {
                             if (idx == 0 || !m.javadoc.substring(idx - 1, idx + 9).equals("(Publisher")) {
-                                e.append("java.lang.RuntimeException: Completable doc mentions Publisher but not in the signature\r\n at io.reactivex.")
-                                .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Completable doc mentions Publisher but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                                .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
 
@@ -700,8 +700,8 @@ public void completableDocRefersToCompletableTypes() throws Exception {
                         if (!m.signature.contains("Flowable")) {
                             Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*Flowable");
                             if (!p.matcher(m.javadoc).find()) {
-                                e.append("java.lang.RuntimeException: Completable doc mentions Flowable but not in the signature\r\n at io.reactivex.")
-                                .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Completable doc mentions Flowable but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                                .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
                         jdx = idx + 6;
@@ -716,8 +716,8 @@ public void completableDocRefersToCompletableTypes() throws Exception {
                         if (!m.signature.contains("Single")) {
                             Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*Single");
                             if (!p.matcher(m.javadoc).find()) {
-                                e.append("java.lang.RuntimeException: Completable doc mentions Single but not in the signature\r\n at io.reactivex.")
-                                .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Completable doc mentions Single but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                                .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
                         jdx = idx + 6;
@@ -732,8 +732,8 @@ public void completableDocRefersToCompletableTypes() throws Exception {
                         if (!m.signature.contains("SingleSource")) {
                             Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*SingleSource");
                             if (!p.matcher(m.javadoc).find()) {
-                                e.append("java.lang.RuntimeException: Completable doc mentions SingleSource but not in the signature\r\n at io.reactivex.")
-                                .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Completable doc mentions SingleSource but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                                .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
                         jdx = idx + 6;
@@ -748,8 +748,8 @@ public void completableDocRefersToCompletableTypes() throws Exception {
                         if (!m.signature.contains("Observable")) {
                             Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*Observable");
                             if (!p.matcher(m.javadoc).find()) {
-                                e.append("java.lang.RuntimeException: Completable doc mentions Observable but not in the signature\r\n at io.reactivex.")
-                                .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Completable doc mentions Observable but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                                .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
                         jdx = idx + 6;
@@ -764,8 +764,8 @@ public void completableDocRefersToCompletableTypes() throws Exception {
                         if (!m.signature.contains("ObservableSource")) {
                             Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*ObservableSource");
                             if (!p.matcher(m.javadoc).find()) {
-                                e.append("java.lang.RuntimeException: Completable doc mentions ObservableSource but not in the signature\r\n at io.reactivex.")
-                                .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
+                                e.append("java.lang.RuntimeException: Completable doc mentions ObservableSource but not in the signature\r\n at io.reactivex.rxjava3.core.")
+                                .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                             }
                         }
                         jdx = idx + 6;
@@ -807,9 +807,9 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str
             if (idx >= 0) {
                 e.append("java.lang.RuntimeException: a/an typo ")
                 .append(word)
-                .append("\r\n at io.reactivex.")
+                .append("\r\n at io.reactivex.rxjava3.core.")
                 .append(baseTypeName)
-                .append(" (")
+                .append(".method(")
                 .append(baseTypeName)
                 .append(".java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                 jdx = idx + 6;
@@ -824,9 +824,9 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str
             if (idx >= 0) {
                 e.append("java.lang.RuntimeException: a/an typo ")
                 .append(word)
-                .append("\r\n at io.reactivex.")
+                .append("\r\n at io.reactivex.rxjava3.core.")
                 .append(baseTypeName)
-                .append(" (")
+                .append(".method(")
                 .append(baseTypeName)
                 .append(".java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                 jdx = idx + 6;
@@ -841,9 +841,9 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str
             if (idx >= 0) {
                 e.append("java.lang.RuntimeException: a/an typo ")
                 .append(word)
-                .append("\r\n at io.reactivex.")
+                .append("\r\n at io.reactivex.rxjava3.core.")
                 .append(baseTypeName)
-                .append(" (")
+                .append(".method(")
                 .append(baseTypeName)
                 .append(".java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                 jdx = idx + 6;
@@ -858,9 +858,9 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str
             if (idx >= 0) {
                 e.append("java.lang.RuntimeException: a/an typo ")
                 .append(word)
-                .append("\r\n at io.reactivex.")
+                .append("\r\n at io.reactivex.rxjava3.core.")
                 .append(baseTypeName)
-                .append(" (")
+                .append(".method(")
                 .append(baseTypeName)
                 .append(".java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n");
                 jdx = idx + 6;
@@ -895,9 +895,9 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str
             if (idx >= 0) {
                 e.append("java.lang.RuntimeException: a/an typo ")
                 .append(word)
-                .append("\r\n at io.reactivex.")
+                .append("\r\n at io.reactivex.rxjava3.core.")
                 .append(baseTypeName)
-                .append(" (")
+                .append(".method(")
                 .append(baseTypeName)
                 .append(".java:").append(m.javadocLine).append(")\r\n\r\n");
                 jdx = idx + wrongPre.length() + 1 + word.length();
@@ -923,9 +923,9 @@ static void missingClosingDD(StringBuilder e, RxMethod m, String baseTypeName) {
                 jdx = idx2 + 5;
             } else {
                 e.append("java.lang.RuntimeException: unbalanced <dd></dd> ")
-                .append("\r\n at io.reactivex.")
+                .append("\r\n at io.reactivex.rxjava3.core.")
                 .append(baseTypeName)
-                .append(" (")
+                .append(".method(")
                 .append(baseTypeName)
                 .append(".java:").append(m.javadocLine + lineNumber(m.javadoc, idx1) - 1).append(")\r\n\r\n");
                 break;
@@ -936,9 +936,9 @@ static void missingClosingDD(StringBuilder e, RxMethod m, String baseTypeName) {
     static void backpressureMentionedWithoutAnnotation(StringBuilder e, RxMethod m, String baseTypeName) {
         if (m.backpressureDocLine > 0 && m.backpressureKind == null) {
             e.append("java.lang.RuntimeException: backpressure documented but not annotated ")
-            .append("\r\n at io.reactivex.")
+            .append("\r\n at io.reactivex.rxjava3.core.")
             .append(baseTypeName)
-            .append(" (")
+            .append(".method(")
             .append(baseTypeName)
             .append(".java:").append(m.backpressureDocLine).append(")\r\n\r\n");
         }
diff --git a/src/test/java/io/reactivex/rxjava3/validators/ParamValidationCheckerTest.java b/src/test/java/io/reactivex/rxjava3/validators/ParamValidationCheckerTest.java
index 5bc4306492..d8c3e32636 100644
--- a/src/test/java/io/reactivex/rxjava3/validators/ParamValidationCheckerTest.java
+++ b/src/test/java/io/reactivex/rxjava3/validators/ParamValidationCheckerTest.java
@@ -509,6 +509,10 @@ public void checkParallelFlowable() {
         addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "singleStage", Object.class));
         addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "lastStage", Object.class));
 
+        addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "firstStage", Object.class));
+        addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "singleStage", Object.class));
+        addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "lastStage", Object.class));
+
         addOverride(new ParamOverride(Maybe.class, 0, ParamMode.ANY, "toCompletionStage", Object.class));
         addOverride(new ParamOverride(Completable.class, 0, ParamMode.ANY, "toCompletionStage", Object.class));