diff --git a/pom.xml b/pom.xml
index c1b076135..3fdbfcb98 100644
--- a/pom.xml
+++ b/pom.xml
@@ -134,6 +134,13 @@
true
+
+ io.reactivex.rxjava3
+ rxjava
+ ${rxjava3}
+ true
+
+
diff --git a/src/main/java/org/springframework/data/repository/reactive/RxJava3CrudRepository.java b/src/main/java/org/springframework/data/repository/reactive/RxJava3CrudRepository.java
new file mode 100644
index 000000000..2897b5d01
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/reactive/RxJava3CrudRepository.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2020 the original author or authors.
+ *
+ * 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
+ *
+ * https://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 org.springframework.data.repository.reactive;
+
+import io.reactivex.rxjava3.core.Completable;
+import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.core.Maybe;
+import io.reactivex.rxjava3.core.Single;
+
+import org.springframework.data.repository.NoRepositoryBean;
+import org.springframework.data.repository.Repository;
+
+/**
+ * Interface for generic CRUD operations on a repository for a specific type. This repository follows reactive paradigms
+ * and uses RxJava 3 types.
+ *
+ * @author Mark Paluch
+ * @since 2.4
+ * @see Maybe
+ * @see Single
+ * @see Flowable
+ * @see Completable
+ */
+@NoRepositoryBean
+public interface RxJava3CrudRepository extends Repository {
+
+ /**
+ * Saves a given entity. Use the returned instance for further operations as the save operation might have changed the
+ * entity instance completely.
+ *
+ * @param entity must not be {@literal null}.
+ * @return {@link Single} emitting the saved entity.
+ * @throws IllegalArgumentException in case the given {@literal entity} is {@literal null}.
+ */
+ Single save(S entity);
+
+ /**
+ * Saves all given entities.
+ *
+ * @param entities must not be {@literal null}.
+ * @return {@link Flowable} emitting the saved entities.
+ * @throws IllegalArgumentException in case the given {@link Iterable entities} or one of its entities is
+ * {@literal null}.
+ */
+ Flowable saveAll(Iterable entities);
+
+ /**
+ * Saves all given entities.
+ *
+ * @param entityStream must not be {@literal null}.
+ * @return {@link Flowable} emitting the saved entities.
+ * @throws IllegalArgumentException in case the given {@link Flowable entityStream} is {@literal null}.
+ */
+ Flowable saveAll(Flowable entityStream);
+
+ /**
+ * Retrieves an entity by its id.
+ *
+ * @param id must not be {@literal null}.
+ * @return {@link Maybe} emitting the entity with the given id or {@link Maybe#empty()} if none found.
+ * @throws IllegalArgumentException in case the given {@literal id} is {@literal null}.
+ */
+ Maybe findById(ID id);
+
+ /**
+ * Retrieves an entity by its id supplied by a {@link Single}.
+ *
+ * @param id must not be {@literal null}. Uses the first emitted element to perform the find-query.
+ * @return {@link Maybe} emitting the entity with the given id or {@link Maybe#empty()} if none found.
+ * @throws IllegalArgumentException in case the given {@link Single id} is {@literal null}.
+ */
+ Maybe findById(Single id);
+
+ /**
+ * Returns whether an entity with the given {@literal id} exists.
+ *
+ * @param id must not be {@literal null}.
+ * @return {@link Single} emitting {@literal true} if an entity with the given id exists, {@literal false} otherwise.
+ * @throws IllegalArgumentException in case the given {@literal id} is {@literal null}.
+ */
+ Single existsById(ID id);
+
+ /**
+ * Returns whether an entity with the given id, supplied by a {@link Single}, exists.
+ *
+ * @param id must not be {@literal null}.
+ * @return {@link Single} emitting {@literal true} if an entity with the given id exists, {@literal false} otherwise.
+ * @throws IllegalArgumentException in case the given {@link Single id} is {@literal null}.
+ */
+ Single existsById(Single id);
+
+ /**
+ * Returns all instances of the type.
+ *
+ * @return {@link Flowable} emitting all entities.
+ */
+ Flowable findAll();
+
+ /**
+ * Returns all instances of the type {@code T} with the given IDs.
+ *
+ * If some or all ids are not found, no entities are returned for these IDs.
+ *
+ * Note that the order of elements in the result is not guaranteed.
+ *
+ * @param ids must not be {@literal null} nor contain any {@literal null} values.
+ * @return {@link Flowable} emitting the found entities. The size can be equal or less than the number of given
+ * {@literal ids}.
+ * @throws IllegalArgumentException in case the given {@link Iterable ids} or one of its items is {@literal null}.
+ */
+ Flowable findAllById(Iterable ids);
+
+ /**
+ * Returns all instances of the type {@code T} with the given IDs supplied by a {@link Flowable}.
+ *
+ * If some or all ids are not found, no entities are returned for these IDs.
+ *
+ * Note that the order of elements in the result is not guaranteed.
+ *
+ * @param idStream must not be {@literal null}.
+ * @return {@link Flowable} emitting the found entities.
+ * @throws IllegalArgumentException in case the given {@link Flowable idStream} is {@literal null}.
+ */
+ Flowable findAllById(Flowable idStream);
+
+ /**
+ * Returns the number of entities available.
+ *
+ * @return {@link Single} emitting the number of entities.
+ */
+ Single count();
+
+ /**
+ * Deletes the entity with the given id.
+ *
+ * @param id must not be {@literal null}.
+ * @return {@link Completable} signaling when operation has completed.
+ * @throws IllegalArgumentException in case the given {@literal id} is {@literal null}.
+ */
+ Completable deleteById(ID id);
+
+ /**
+ * Deletes a given entity.
+ *
+ * @param entity must not be {@literal null}.
+ * @return {@link Completable} signaling when operation has completed.
+ * @throws IllegalArgumentException in case the given entity is {@literal null}.
+ */
+ Completable delete(T entity);
+
+ /**
+ * Deletes the given entities.
+ *
+ * @param entities must not be {@literal null}.
+ * @return {@link Completable} signaling when operation has completed.
+ * @throws IllegalArgumentException in case the given {@link Iterable entities} or one of its entities is
+ * {@literal null}.
+ */
+ Completable deleteAll(Iterable extends T> entities);
+
+ /**
+ * Deletes the given entities supplied by a {@link Flowable}.
+ *
+ * @param entityStream must not be {@literal null}.
+ * @return {@link Completable} signaling when operation has completed.
+ * @throws IllegalArgumentException in case the given {@link Flowable entityStream} is {@literal null}.
+ */
+ Completable deleteAll(Flowable extends T> entityStream);
+
+ /**
+ * Deletes all entities managed by the repository.
+ *
+ * @return {@link Completable} signaling when operation has completed.
+ */
+ Completable deleteAll();
+}
diff --git a/src/main/java/org/springframework/data/repository/reactive/RxJava3SortingRepository.java b/src/main/java/org/springframework/data/repository/reactive/RxJava3SortingRepository.java
new file mode 100644
index 000000000..1b8829343
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/reactive/RxJava3SortingRepository.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 the original author or authors.
+ *
+ * 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
+ *
+ * https://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 org.springframework.data.repository.reactive;
+
+import io.reactivex.rxjava3.core.Flowable;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.data.repository.NoRepositoryBean;
+
+/**
+ * Extension of {@link RxJava3CrudRepository} to provide additional methods to retrieve entities using the sorting
+ * abstraction.
+ *
+ * @author Mark Paluch
+ * @since 2.4
+ * @see Sort
+ * @see Flowable
+ * @see RxJava2CrudRepository
+ */
+@NoRepositoryBean
+public interface RxJava3SortingRepository extends RxJava3CrudRepository {
+
+ /**
+ * Returns all entities sorted by the given options.
+ *
+ * @param sort must not be {@literal null}.
+ * @return all entities sorted by the given options.
+ */
+ Flowable findAll(Sort sort);
+}
diff --git a/src/main/java/org/springframework/data/repository/util/ReactiveWrapperConverters.java b/src/main/java/org/springframework/data/repository/util/ReactiveWrapperConverters.java
index 4e10ab163..288bb9214 100644
--- a/src/main/java/org/springframework/data/repository/util/ReactiveWrapperConverters.java
+++ b/src/main/java/org/springframework/data/repository/util/ReactiveWrapperConverters.java
@@ -52,6 +52,8 @@ import org.springframework.util.ClassUtils;
*
* This class discovers reactive wrapper availability and their conversion support based on the class path. Reactive
* wrapper types might be supported/on the class path but conversion may require additional dependencies.
+ *
+ * Note: As of Spring Data 2.4, support for RxJava 1.x is deprecated in favor of RxJava 2 and 3.
*
* @author Mark Paluch
* @author Oliver Gierke
@@ -80,6 +82,14 @@ public abstract class ReactiveWrapperConverters {
REACTIVE_WRAPPERS.add(RxJava2FlowableWrapper.INSTANCE);
}
+ if (ReactiveWrappers.isAvailable(ReactiveLibrary.RXJAVA3)) {
+
+ REACTIVE_WRAPPERS.add(RxJava3SingleWrapper.INSTANCE);
+ REACTIVE_WRAPPERS.add(RxJava3MaybeWrapper.INSTANCE);
+ REACTIVE_WRAPPERS.add(RxJava3ObservableWrapper.INSTANCE);
+ REACTIVE_WRAPPERS.add(RxJava3FlowableWrapper.INSTANCE);
+ }
+
if (ReactiveWrappers.isAvailable(ReactiveLibrary.PROJECT_REACTOR)) {
REACTIVE_WRAPPERS.add(FluxWrapper.INSTANCE);
@@ -278,6 +288,10 @@ public abstract class ReactiveWrapperConverters {
}
}
+ // -------------------------------------------------------------------------
+ // RxJava 1 converters
+ // -------------------------------------------------------------------------
+
/**
* Wrapper for RxJava 1's {@link Single}.
*/
@@ -314,6 +328,10 @@ public abstract class ReactiveWrapperConverters {
}
}
+ // -------------------------------------------------------------------------
+ // RxJava 2 converters
+ // -------------------------------------------------------------------------
+
/**
* Wrapper for RxJava 2's {@link io.reactivex.Single}.
*/
@@ -386,6 +404,82 @@ public abstract class ReactiveWrapperConverters {
}
}
+ // -------------------------------------------------------------------------
+ // RxJava 3 converters
+ // -------------------------------------------------------------------------
+
+ /**
+ * Wrapper for RxJava 3's {@link io.reactivex.rxjava3.core.Single}.
+ */
+ private static enum RxJava3SingleWrapper implements ReactiveTypeWrapper> {
+
+ INSTANCE;
+
+ @Override
+ public Class super io.reactivex.rxjava3.core.Single>> getWrapperClass() {
+ return io.reactivex.rxjava3.core.Single.class;
+ }
+
+ @Override
+ public io.reactivex.rxjava3.core.Single> map(Object wrapper, Function