diff --git a/java-batching-spliterator/README.MD b/java-batching-spliterator/README.MD
new file mode 100644
index 0000000..e69de29
diff --git a/java-batching-spliterator/pom.xml b/java-batching-spliterator/pom.xml
new file mode 100644
index 0000000..ea3dc73
--- /dev/null
+++ b/java-batching-spliterator/pom.xml
@@ -0,0 +1,64 @@
+
+ 4.0.0
+
+ com.pivovarit
+ 1.0
+ java-batching-spliterator
+
+ java-batching-spliterator
+
+
+ 1.35
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 21
+
+
+
+ maven-surefire-plugin
+ 2.19.1
+
+
+ org.junit.platform
+ junit-platform-surefire-provider
+ 1.1.0
+
+
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.8.2
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.22.0
+ test
+
+
+ org.openjdk.jmh
+ jmh-core
+ ${jmh.version}
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ ${jmh.version}
+ provided
+
+
+
diff --git a/java-batching-spliterator/src/main/java/com/pivovarit/stream/BatchingSpliterator.java b/java-batching-spliterator/src/main/java/com/pivovarit/stream/BatchingSpliterator.java
new file mode 100644
index 0000000..3057198
--- /dev/null
+++ b/java-batching-spliterator/src/main/java/com/pivovarit/stream/BatchingSpliterator.java
@@ -0,0 +1,96 @@
+package com.pivovarit.stream;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import static java.util.stream.Stream.empty;
+import static java.util.stream.Stream.of;
+import static java.util.stream.StreamSupport.stream;
+
+/**
+ * @author Grzegorz Piwowarek
+ */
+final class BatchingSpliterator implements Spliterator> {
+
+ private final List source;
+ private final int maxChunks;
+
+ private int chunks;
+ private int chunkSize;
+ private int consumed;
+
+ private BatchingSpliterator(List list, int batches) {
+ if (batches < 1) {
+ throw new IllegalArgumentException("batches can't be lower than one");
+ }
+ source = list;
+ chunks = batches;
+ maxChunks = Math.min(list.size(), batches);
+ chunkSize = (int) Math.ceil(((double) source.size()) / batches);
+ }
+
+ static Stream> partitioned(List list, int numberOfParts) {
+ int size = list.size();
+
+ if (size <= numberOfParts) {
+ return asSingletonListStream(list);
+ } else if (size == 0) {
+ return empty();
+ } else if (numberOfParts == 1) {
+ return of(list);
+ } else {
+ return stream(new BatchingSpliterator<>(list, numberOfParts), false);
+ }
+ }
+
+ private static Stream> asSingletonListStream(List list) {
+ Stream.Builder> acc = Stream.builder();
+ for (T t : list) {
+ acc.add(Collections.singletonList(t));
+ }
+ return acc.build();
+ }
+
+ static Function, List> batching(Function mapper) {
+ return batch -> {
+ List list = new ArrayList<>(batch.size());
+ for (T t : batch) {
+ list.add(mapper.apply(t));
+ }
+ return list;
+ };
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer super List> action) {
+ if (consumed < source.size() && chunks != 0) {
+ List batch = source.subList(consumed, consumed + chunkSize);
+ consumed += chunkSize;
+ chunkSize = (int) Math.ceil(((double) (source.size() - consumed)) / --chunks);
+ action.accept(batch);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Spliterator> trySplit() {
+ return null;
+ }
+
+ @Override
+ public long estimateSize() {
+ return maxChunks;
+ }
+
+ @Override
+ public int characteristics() {
+ return ORDERED | SIZED;
+ }
+}
diff --git a/pom.xml b/pom.xml
index da6f3ab..c20fef1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,7 @@
java-archunit
java-sealed-classes
java-completable-future-timeouts
+ java-batching-spliterator
java-event-sourcing
java-completable-future-allof
java-advanced-groupingby