π English Documentation | π δΈζζζ‘£
π Java CompletableFuture-Fu ("CF-Fu", pronounced "Shifu" π¦) is a tiny 0-dependency library that improves
the CompletableFuture(CF)
usage experience and reduces misuse, enabling more convenient, efficient, and safe use of CF
in your application. πππ¦Ί
Welcome ππ
- For suggestions and questions, submit an Issue
- For contributions and improvements, play fork and pull request dance
- π§ Features
- π₯ User Guide
- 1.
cffu
usage modes - 2.
cffu
feature introduction- 2.1 support for returning overall results of multiple input
CF
s - 2.2 support for setting default business thread pool
- 2.3 efficient and flexible concurrent execution strategies (
AllFailFast
/AnySuccess
/AllSuccess
/MostSuccess
) - 2.4 support for directly running multiple
action
s instead of wrapping them intoCompletablefuture
s first - 2.5 Support for async parallel processing of collection data, instead of wrapping data and
Action
intoCompletableFuture
s first - 2.6 support for handling specific exception types instead of all
Throwable
exceptions - 2.7 backport support for
Java 8
- 2.8 timeout-safe new implementation of
orTimeout
/completeOnTimeout
- 2.9 support for timeout-enabled
join
method - 2.10
anyOf
method that returns specific types - 2.11
allOf/anyOf
methods that accept broader input types - more feature documentation
- 2.1 support for returning overall results of multiple input
- 1.
- π API Docs
- πͺ Dependencies
- π More Resources
- π About the Library Name
βοΈ Completing missing functionality in business development
- πͺ More convenient features, such as:
- Support for returning overall results of multiple input
CF
s instead of returningCF<Void>
without inputCF
results (CompletableFuture#allOf
)- Such as methods
allResultsFailFastOf
/mSupplyFailFastAsync
/thenMApplyMostSuccessAsync
- Such as methods
- Support for directly running multiple
Action
s instead of wrapping them intoCompletableFuture
s first- Such as methods
mSupplyAsync
/mRunFailFastAsync
/thenMApplyAllSuccessAsync
- Aka. multiple instruction, single data (
MISD
) style processing
- Such as methods
- Support for async parallel processing of collection data,
instead of wrapping data with actions into
CompletableFuture
s first- Such as methods
CfParallelUtils#parApplyFailFastAsync
/CfParallelUtils#thenParAcceptAnySuccessAsync
- Aka. single instruction, multiple data (
SIMD
) style processing
- Such as methods
- Support for inputting collections of
CF
s /Action
s instead of converting them to array first- Such as methods
CfIterableUtils#allResultsFailFastOf
/CfIterableUtils#mSupplyFailFastAsync
/CfIterableUtils#thenMApplyMostSuccessAsync
- Such as methods
- Support for setting a default business thread pool via
CffuFactory#builder(executor)
method, instead of repeatedly passing business thread pool parameters during async execution - Support for handling specific exception types via
catching
methods instead of handling allThrowable
exceptions (CompletableFuture#exceptionally
)
- Support for returning overall results of multiple input
- π¦ More efficient and flexible concurrent execution strategies, such as:
AllFailFast
strategy: fail fast when any of the multiple inputCF
s complete exceptionally instead of futilely waiting for allCF
s to complete (CompletableFuture#allOf
)AnySuccess
strategy: Return the first successfulCF
result instead of the first completed but possibly exceptionalCF
(CompletableFuture#anyOf
)AllSuccess
strategy: Return successful results from multipleCF
s, returning specified default values for exceptionalCF
sMostSuccess
strategy: Return successful results from multipleCF
s within a specified time, returning specified default values for exceptional or timed-outCF
sAll(Complete)
/Any(Complete)
strategies: These two are strategies already supported byCompletableFuture
- π¦Ί Safer usage patterns, such as:
- Timeout-safe new implementation of
orTimeout
/completeOnTimeout
methods- The
CF#orTimeout
/CF#completeOnTimeout
methods can break CompletableFuture's timeout and delay functionalityβοΈ
- The
peek
processing method that definitely won't modifyCF
results- The
whenComplete
method may modify theCF
result, and the returnedCF
result may not be consistent with the input
- The
- Support for timeout-enabled
join(timeout, unit)
method - Support for the forbidding
obtrude
methods viaCffuFactoryBuilder#forbidObtrudeMethods
method - Complete code quality annotations attached to class methods,
enabling
IDE
s to provide early problem warnings during coding- Such as
@NonNull
,@Nullable
,@CheckReturnValue
,@Contract
, etc.
- Such as
- Timeout-safe new implementation of
- π§© Missing basic functionality, in addition to the safety-oriented new implementations above, including:
- Async exception completion,
completeExceptionallyAsync
method - Non-blocking retrieval of successful results, returning specified default values
for exceptional or incomplete
CF
s,getSuccessNow
method - Unwrapping
CF
exceptions into business exceptions,unwrapCfException
method
- Async exception completion,
β³ Backport
support for Java 8
, all new CF
methods from Java 9+
versions
are directly available in Java 8
version, such as:
- Timeout control:
orTimeout
/completeOnTimeout
- Delayed execution:
delayedExecutor
- Factory methods:
failedFuture
/completedStage
/failedStage
- Processing operations:
completeAsync
/exceptionallyAsync
/exceptionallyCompose
/copy
- Non-blocking reads:
resultNow
/exceptionNow
/state
πͺ Enhancement of existing features, such as:
anyOf
method: Returns specific typeT
(type-safe) instead of returningObject
(CompletableFuture#anyOf
)allOf
/anyOf
methods: Accept broaderCompletionStage
parameter types instead ofCompletableFuture
class (CompletableFuture#allOf/anyOf
)
For more information about cffu
features and usage,
see the cffu
Feature Introduction.
Managing concurrent execution is a complex and error-prone problem, and the industry has a large number of tools and frameworks available.
For a broad understanding of concurrency tools and frameworks, you can check out books like "Seven Concurrency Models in Seven Weeks", "Programming Concurrency on the JVM", "Learning Concurrent Programming in Scala (2nd Edition)".
Among them, CompletableFuture(CF)
has its advantages:
- Widely Known and Widely Used
CompletableFuture
was provided inJava 8
released in 2014, 10 years ago- The parent interface
Future
ofCompletableFuture
was provided as early asJava 5
released in 2004, 20 years ago. Although theFuture
interface doesn't support asynchronous retrieval of execution results and orchestration of concurrent execution logic, it has made the majority ofJava
developers familiar with the typical concept and tool ofFuture
- Powerful but Not Excessively Complex
- Sufficient to handle daily business asynchronous concurrency needs
- Other large-scale concurrency frameworks (such as
Akka
,RxJava
) require much more understanding to use. Of course, basic concurrency concerns and their complexity are independent of which specific tool is used and must all be understood and paid attention to
- High-Level Abstraction
- Or expressing technical concurrent processes in the form of business processes
- Can avoid or reduce the use of cumbersome and error-prone basic concurrent coordination tools:
Synchronizers
(such as
CountDownLatch
,CyclicBarrier
,Phaser
), Locks, and atomic classes
- Built into
Java
Standard Library- No additional dependencies required, almost always available
- Believed to have extremely high implementation quality
Like other concurrency tools and frameworks, CompletableFuture
is used for:
- Concurrent execution of business logic, or orchestrating concurrent processing flows or asynchronous tasks
- Multicore parallel processing, making full use of resources
- Shortening request response time and improving business responsiveness
It's worth understanding and applying in more depth. π
- π¦ Using the
Cffu
class - π§ Using the
CompletableFutureUtils
utility class
Compared to calling static methods of the CompletableFutureUtils
utility class:
- Using the
Cffu
class is like usingCompletableFuture
, with new features as instance methods of theCffu
class, which can be called naturally and conveniently- The
Cffu
class to theCompletableFuture
utility classCompletableFutureUtils
is like Guava'sFluentFuture
to theListenableFuture
utility classFutures
- The
- The
Java
language doesn't support extending methods on existing classes (CompletableFuture
), so a new wrapper class (Cffu
) is needed
If you don't want to introduce a new class (Cffu
class) to your project and feel that
this adds complexity, you can completely use the cffu
library as a utility class:
- Utility methods for optimizing
CompletableFuture
usage are very common in business projects CompletableFutureUtils
provides a series of practical, efficient, safe, and reliable utility methods- Some
cffu
features are not provided in this usage mode (and no suitable implementation approach has been found)
Such as support for setting default business thread pools and forbiddingobtrude
methods
To conveniently and naturally use the enhanced features and methods of the cffu
library,
you can migrate existing code that uses the CompletableFuture
class to the Cffu
class.
1) If you can modify code that uses CompletableFuture
Migrating to the Cffu
class involves 2 simple changes:
- In type declaration places, change the
CompletableFuture
class to theCffu
class - In
CompletableFuture
static method call places, change the class nameCompletableFuture
to acffuFactory
instance
The reason this migration is possible is that:
- All instance methods of the
CompletableFuture
class are implemented in theCffu
class with the same method signatures and functionality- All static methods of the
CompletableFuture
class are implemented in theCffuFactory
class with the same method signatures and functionality
2) If you cannot modify code that uses CompletableFuture
(such as CF
returned from external libraries)
Use the CffuFactory.toCffu(CompletionStage)
method
to convert CompletableFuture
or CompletionStage
to Cffu
type.
-
For
Maven
projects:<dependency> <groupId>io.foldright</groupId> <artifactId>cffu2</artifactId> <version>2.0.0</version> </dependency>
-
For
Gradle
projects:Gradle Kotlin DSL
implementation("io.foldright:cffu2:2.0.0")
Gradle Groovy DSL
implementation 'io.foldright:cffu2:2.0.0'
The return type of CompletableFuture#allOf
method is CF<Void>
without the execution results of the input CF
s.
To get the execution results of input CF
s, you need to:
- After the
allOf
method, get results through explicit read operations (likejoin
/get
) on the input parameterCF
s- Cumbersome operation π§π€―
- Read methods like
join
/get
are blocking, increasing the deadlock risk in business logicβοΈ
For more explanation, see article CompletableFuture Principles and Practice - 4.2.2 Thread Pool Circular References Can Cause Deadlocks
- Or set external variables in the passed
CompletableFuture Action
- Need to pay attention to thread safety issues of multithreaded read-write
β οΈ π
Multi-threaded read-write involves the complexity of multithreaded data transfer; omitting correct handling of concurrent logic data read-write is a common problem in business codeβοΈ - Avoid concurrent pitfalls; concurrent logic is complex and bug-prone π
If timeouts are involved, it becomes even more complex; evenJDK CompletableFuture
itself has bug fixes in this area inJava 21
β°
- Need to pay attention to thread safety issues of multithreaded read-write
Methods in cffu
like allResultsFailFastOf
/ mSupplyFailFastAsync
/ thenMApplyMostSuccessAsync
provide functionality to return results of input CF
s.
Using these methods to get overall execution results of input CF
s:
- Convenient and straightforward
- Because the returned result is a
CF
with overall results, you can continue chaining non-blocking operations, naturally reducing the use of blocking read methods (likejoin
/get
) and minimizing deadlock risk in business logic - Avoids complex thread safety issues and logic errors of directly implementing multithreaded read-write logic in business logic
- Using "reliably implemented and tested" library-provided concurrency features instead of implementing them directly is a best practice πβ
Example code:
public class AllResultsOfDemo {
private static final ExecutorService myBizExecutor = Executors.newCachedThreadPool();
private static final CffuFactory cffuFactory = CffuFactory.builder(myBizExecutor).build();
public static void main(String[] args) throws Exception {
//////////////////////////////////////////////////
// CffuFactory#allResultsOf
//////////////////////////////////////////////////
Cffu<Integer> cffu1 = cffuFactory.completedFuture(21);
Cffu<Integer> cffu2 = cffuFactory.completedFuture(42);
Cffu<Void> all = cffuFactory.allOf(cffu1, cffu2);
// result type is Void!
//
// the result can be got by input argument `cf1.get()`, but it's cumbersome.
// so we can see a lot of util methods to enhance `allOf` with result in our project.
MCffu<Integer, List<Integer>> allResults = cffuFactory.allResultsOf(cffu1, cffu2);
System.out.println(allResults.get());
// output: [21, 42]
//////////////////////////////////////////////////
// or CompletableFutureUtils#allResultsOf
//////////////////////////////////////////////////
CompletableFuture<Integer> cf1 = CompletableFuture.completedFuture(21);
CompletableFuture<Integer> cf2 = CompletableFuture.completedFuture(42);
CompletableFuture<Void> all2 = CompletableFuture.allOf(cf1, cf2);
// result type is Void!
CompletableFuture<List<Integer>> allResults2 = allResultsOf(cf1, cf2);
System.out.println(allResults2.get());
// output: [21, 42]
}
}
# Complete runnable demo code can be found at
AllResultsOfDemo.java
.
The default thread pool used by CompletableFuture
async execution (i.e., *Async
methods)
is ForkJoinPool.commonPool()
; using this default thread pool in business is very dangerousβ
ForkJoinPool.commonPool()
has about as many threads as CPUs, suitable for executing CPU-intensive tasks; for business logic, there are often many waiting operations (such as networkIO
, blocking waits) that are not CPU-intensive, leading to low business processing capabilities πForkJoinPool
uses unbounded queues; during high traffic, tasks will accumulate, causing memory exhaustion and service crashes π¨
For more information about this problem and its causes, see this article
The result is that in business logic, when calling CompletableFuture
's *Async
methods,
you almost always need to repeatedly pass in a specified business thread pool;
this makes using CompletableFuture
cumbersome and error-prone π€―β
Additionally, when under-layer operations call back to business logic (such as RPC
callbacks),
it's not appropriate or convenient to provide a thread pool for the business;
using Cffu
to set the default business thread pool specified by upper-level business
is both convenient, reasonable, and safe.
For more information about this usage scenario, see
CompletableFuture Principles and Practice - 4.2.3 Asynchronous RPC Calls Should Not Block IO Thread Pools
Example code:
public class NoDefaultExecutorSettingForCompletableFuture {
public static final Executor myBizExecutor = Executors.newCachedThreadPool();
public static void main(String[] args) {
CompletableFuture<Void> cf1 = CompletableFuture.runAsync(
() -> System.out.println("doing a long time work!"),
myBizExecutor);
CompletableFuture<Void> cf2 = CompletableFuture
.supplyAsync(
() -> {
System.out.println("doing another long time work!");
return 42;
},
myBizExecutor)
.thenAcceptAsync(
i -> System.out.println("doing third long time work!"),
myBizExecutor);
CompletableFuture.allOf(cf1, cf2).join();
}
}
# Complete runnable demo code can be found at
NoDefaultExecutorSettingForCompletableFuture.java
.
The Cffu
class supports setting a default business thread pool,
avoiding the above cumbersomeness and dangers. Example code:
public class DefaultExecutorSettingForCffu {
private static final ExecutorService myBizExecutor = Executors.newCachedThreadPool();
private static final CffuFactory cffuFactory = CffuFactory.builder(myBizExecutor).build();
public static void main(String[] args) {
Cffu<Void> cf1 = cffuFactory.runAsync(() -> System.out.println("doing a long time work!"));
Cffu<Void> cf2 = cffuFactory.supplyAsync(() -> {
System.out.println("doing another long time work!");
return 42;
}).thenAcceptAsync(i -> System.out.println("doing third long time work!"));
cffuFactory.allOf(cf1, cf2).join();
}
}
# Complete runnable demo code can be found at
DefaultExecutorSettingForCffu.java
.
2.3 efficient and flexible concurrent execution strategies (AllFailFast
/ AnySuccess
/ AllSuccess
/ MostSuccess
)
CompletableFuture
'sallOf
method waits for all inputCF
s to complete; even if aCF
completes exceptionally, it still waits for subsequentCF
s to complete before returning an exceptionalCF
.- For business logic, this failure-and-continue-waiting strategy (
AllComplete
) slows down business responsiveness; businesses would prefer fail fast when any of inputCF
complete exceptionally, instead of futilely waiting cffu
provides corresponding methods likeallResultsFailFastOf
that support theAllFailFast
concurrent execution strategy- Both
allOf
andallResultsFailFastOf
return successful results only when all inputCF
s successful
- For business logic, this failure-and-continue-waiting strategy (
CompletableFuture
'sanyOf
method returns the first completedCF
without waiting for subsequent incompleteCF
s; even if the first completedCF
is exceptional, it returns this exceptionalCF
result.- For business logic, what's often wanted is not the first completed but exceptional
CF
result (AnyComplete
), but rather the first successfulCF
result cffu
provides corresponding methods likeanySuccessOf
that support theAnySuccess
concurrent execution strategyanySuccessOf
returns a exceptional result only when all inputCF
s exceptional
- For business logic, what's often wanted is not the first completed but exceptional
- Return successful results from multiple
CF
s, returning specified default values for exceptionalCF
s- When business logic includes fault tolerance, successful partial results can be used,
instead of overall failure when some
CF
s fail cffu
provides corresponding methods likeallSuccessOf
that support theAllSuccess
concurrent execution strategy
- When business logic includes fault tolerance, successful partial results can be used,
instead of overall failure when some
- Return successful results from multiple
CF
s within a specified time, returning specified default values for incomplete or timed-outCF
s- When business is eventually consistent, return available results as much as possible;
for
CF
s that couldn't return in time, results will be written to distributed cache for the next business request, avoiding duplicate calculations - This is a common business usage pattern;
cffu
provides corresponding methods likemostSuccessResultsOf
that support theMostSuccess
concurrent execution strategy
- When business is eventually consistent, return available results as much as possible;
for
π For more about concurrent execution strategies for multiple
CF
s, see the JavaScript specificationPromise Concurrency
; in JavaScript,Promise
corresponds toCompletableFuture
.JavaScript
Promise
provides 4 concurrent execution methods:
Promise.all()
: Wait for allPromise
s to complete, immediately return exceptionally if any input complete exceptionally (AllFailFast
)Promise.allSettled()
: Wait for allPromise
s to complete, regardless of success or failure (AllComplete
)Promise.any()
: Racing mode, immediately return the first successfulPromise
(AnySuccess
)Promise.race()
: Racing mode, immediately return the first completedPromise
(AnyComplete
)PS: The method naming of JavaScript
Promise
is well-considered~ π
cffu
's new methods support the concurrent execution approaches of the JavaScriptPromise
specification~
Example code:
public class ConcurrencyStrategyDemo {
private static final ExecutorService myBizExecutor = Executors.newCachedThreadPool();
private static final CffuFactory cffuFactory = CffuFactory.builder(myBizExecutor).build();
public static void main(String[] args) throws Exception {
////////////////////////////////////////////////////////////////////////
// CffuFactory#allResultsFailFastOf
// CffuFactory#anySuccessOf
// CffuFactory#mostSuccessResultsOf
////////////////////////////////////////////////////////////////////////
final Cffu<Integer> success = cffuFactory.supplyAsync(() -> {
sleep(300); // sleep SHORT time
return 42;
});
final Cffu<Integer> successAfterLongTime = cffuFactory.supplyAsync(() -> {
sleep(3000); // sleep LONG time
return 4242;
});
final Cffu<Integer> failed = cffuFactory.failedFuture(new RuntimeException("Bang!"));
MCffu<Integer, List<Integer>> failFast = cffuFactory.allResultsFailFastOf(success, successAfterLongTime, failed);
// fail fast without waiting successAfterLongTime
System.out.println(failFast.exceptionNow());
// output: java.lang.RuntimeException: Bang!
Cffu<Integer> anySuccess = cffuFactory.anySuccessOf(success, successAfterLongTime, failed);
System.out.println(anySuccess.get());
// output: 42
MCffu<Integer, List<Integer>> mostSuccess = cffuFactory.mostSuccessResultsOf(
-1, 100, TimeUnit.MILLISECONDS, success, successAfterLongTime, failed);
System.out.println(mostSuccess.get());
// output: [42, -1, -1]
////////////////////////////////////////////////////////////////////////
// or CompletableFutureUtils#allResultsFailFastOf
// CompletableFutureUtils#anySuccessOf
// CompletableFutureUtils#mostSuccessResultsOf
////////////////////////////////////////////////////////////////////////
final CompletableFuture<Integer> successCf = CompletableFuture.supplyAsync(() -> {
sleep(300); // sleep SHORT time
return 42;
});
final CompletableFuture<Integer> successAfterLongTimeCf = CompletableFuture.supplyAsync(() -> {
sleep(3000); // sleep LONG time
return 4242;
});
final CompletableFuture<Integer> failedCf = failedFuture(new RuntimeException("Bang!"));
CompletableFuture<List<Integer>> failFast2 = allResultsFailFastOf(successCf, successAfterLongTimeCf, failedCf);
// fail fast without waiting successAfterLongTime
System.out.println(exceptionNow(failFast2));
// output: java.lang.RuntimeException: Bang!
CompletableFuture<Integer> anySuccess2 = anySuccessOf(successCf, successAfterLongTimeCf, failedCf);
System.out.println(anySuccess2.get());
// output: 42
CompletableFuture<List<Integer>> mostSuccess2 = mostSuccessResultsOf(
-1, 100, TimeUnit.MILLISECONDS, successCf, successAfterLongTime, failed);
System.out.println(mostSuccess2.get());
// output: [42, -1, -1]
}
}
# Complete runnable demo code can be found at
ConcurrencyStrategyDemo.java
.
2.4 support for directly running multiple action
s instead of wrapping them into Completablefuture
s first
The allOf/anyOf
methods of CompletableFuture
take CompletableFuture
as input;
when business logic directly has methods to orchestrate,
you still need to wrap them into CompletableFuture
first before running:
- Cumbersome
- Blurs business processes
- Simply wrapping multiple
Action
s intoCF
s and submitting them toallOf/anyOf
(which is how business code often implements this) will swallow exceptionsβοΈ- When multiple input
Action
s throw exceptions during execution, at most one of these exceptions can be fed back to the business through the returnedCF
; other exceptions are silently swallowed, affecting business problem troubleshooting
- When multiple input
cffu
provides methods for directly running multiple Action
s, solving the above problems:
- Convenient and clear expression and orchestration of business processes
- Doesn't swallow exceptions, facilitating business problem troubleshooting
- When multiple input
Action
s throw exceptions during execution, log reports will be printed for exceptions not fed back to the business through the returnedCF
- When multiple input
Example code:
public class MultipleActionsDemo {
private static final ExecutorService myBizExecutor = Executors.newCachedThreadPool();
private static final CffuFactory cffuFactory = CffuFactory.builder(myBizExecutor).build();
static void mRunAsyncDemo() {
// wrap actions to CompletableFutures first, AWKWARD! π
CompletableFuture.allOf(
CompletableFuture.runAsync(() -> System.out.println("task1")),
CompletableFuture.runAsync(() -> System.out.println("task2")),
CompletableFuture.runAsync(() -> System.out.println("task3"))
);
completedFuture("task").thenCompose(v ->
CompletableFuture.allOf(
CompletableFuture.runAsync(() -> System.out.println(v + "1")),
CompletableFuture.runAsync(() -> System.out.println(v + "2")),
CompletableFuture.runAsync(() -> System.out.println(v + "3"))
)
);
// just run multiple actions, fresh and cool π
CompletableFutureUtils.mRunAsync(
() -> System.out.println("task1"),
() -> System.out.println("task2"),
() -> System.out.println("task3")
);
cffuFactory.completedFuture("task").thenMAcceptAsync(
(String v) -> System.out.println(v + "1"),
v -> System.out.println(v + "2"),
v -> System.out.println(v + "3")
);
}
}
These multiple-Action
methods also support "different concurrent execution strategies"
and "returning results of multiple input CF
s".
Example code:
public class MultipleActionsDemo {
private static final ExecutorService myBizExecutor = Executors.newCachedThreadPool();
private static final CffuFactory cffuFactory = CffuFactory.builder(myBizExecutor).build();
static void thenMApplyAsyncDemo() {
// wrap actions to CompletableFutures first, AWKWARD! π
completedFuture(42).thenCompose(v ->
CompletableFutureUtils.allResultsFailFastOf(
CompletableFuture.supplyAsync(() -> v + 1),
CompletableFuture.supplyAsync(() -> v + 2),
CompletableFuture.supplyAsync(() -> v + 3)
)
).thenAccept(System.out::println);
// output: [43, 44, 45]
cffuFactory.completedFuture(42).thenCompose(v ->
CompletableFutureUtils.allSuccessResultsOf(
-1,
CompletableFuture.supplyAsync(() -> v + 1),
CompletableFuture.supplyAsync(() -> v + 2),
CompletableFuture.supplyAsync(() -> v + 3)
)
).thenAccept(System.out::println);
// output: [43, 44, 45]
// just run multiple actions, fresh and cool π
CompletableFutureUtils.thenMApplyFailFastAsync(
completedFuture(42),
v -> v + 1,
v -> v + 2,
v -> v + 3
).thenAccept(System.out::println);
// output: [43, 44, 45]
cffuFactory.completedFuture(42).thenMApplyAllSuccessAsync(
-1,
v -> v + 1,
v -> v + 2,
v -> v + 3
).thenAccept(System.out::println);
// output: [43, 44, 45]
}
}
# Complete runnable demo code can be found at
MultipleActionsDemo.java
.
2.5 Support for async parallel processing of collection data, instead of wrapping data and Action
into CompletableFuture
s first
Async parallel processing of multiple data items is a common business requirement,
but implementing it via the CompletableFuture
is cumbersome and complex,
also obscures the business logic, and a simple implementation may swallow exceptionsβ
cffu
provides methods for async parallel data processing to solve these problems.
Example code:
public class CfParallelDemo {
private static final ExecutorService myBizExecutor = Executors.newCachedThreadPool();
private static final CffuFactory cffuFactory = CffuFactory.builder(myBizExecutor).build();
static void parApplyFailFastAsyncDemo() {
final Function<Integer, Integer> fn = x -> x + 1;
final List<Integer> list = asList(42, 43, 44);
// wrap data with action to CompletableFutures first, AWKWARD and COMPLEX! π
CompletableFuture<Integer>[] cfs = new CompletableFuture[list.size()];
for (int i = 0; i < list.size(); i++) {
Integer e = list.get(i);
cfs[i] = CompletableFuture.supplyAsync(() -> fn.apply(e));
}
CompletableFutureUtils.allResultsFailFastOf(cfs).thenAccept(System.out::println);
// output: [43, 44, 45]
cffuFactory.allResultsFailFastOf(cfs).thenAccept(System.out::println);
// output: [43, 44, 45]
// just parallel process multiple data, fresh and cool π
CfParallelUtils.parApplyFailFastAsync(
asList(42, 43, 44),
x -> x + 1
).thenAccept(System.out::println);
// output: [43, 44, 45]
cffuFactory.parOps().parApplyFailFastAsync(
asList(42, 43, 44),
x -> x + 1
).thenAccept(System.out::println);
// output: [43, 44, 45]
}
static void thenParApplyFailFastAsyncDemo() {
final Function<Integer, Integer> fn = x -> x + 1;
final CompletableFuture<List<Integer>> cf = completedFuture(asList(42, 43, 44));
// wrap data with action to CompletableFutures first, AWKWARD and COMPLEX! π
cf.thenCompose(list -> {
CompletableFuture<Integer>[] cfs = new CompletableFuture[list.size()];
for (int i = 0; i < list.size(); i++) {
Integer e = list.get(i);
cfs[i] = CompletableFuture.supplyAsync(() -> fn.apply(e));
}
return CompletableFutureUtils.allResultsFailFastOf(cfs);
}).thenAccept(System.out::println);
// output: [43, 44, 45]
final MCffu<Integer, List<Integer>> mCffu = cffuFactory.completedMCffu(asList(42, 43, 44));
mCffu.thenCompose(list -> {
CompletableFuture<Integer>[] cfs = new CompletableFuture[list.size()];
for (int i = 0; i < list.size(); i++) {
Integer e = list.get(i);
cfs[i] = CompletableFuture.supplyAsync(() -> fn.apply(e));
}
return CompletableFutureUtils.allResultsFailFastOf(cfs);
}).thenAccept(System.out::println);
// output: [43, 44, 45]
// just parallel process multiple data, fresh and cool π
CfParallelUtils.thenParApplyFailFastAsync(cf, x -> x + 1)
.thenAccept(System.out::println);
// output: [43, 44, 45]
mCffu.parOps().thenParApplyFailFastAsync(x -> x + 1)
.thenAccept(System.out::println);
// output: [43, 44, 45]
}
}
# Complete runnable demo code can be found at
CfParallelDemo.java
.
In business processing try-catch
statements, catching all exceptions (Throwable
) is often not a good practice.
Similarly, the CompletableFuture#exceptionally
method also handles all exceptions (Throwable
).
You should only handle specific exceptions that the current business clearly understands and can recover from, letting outer layers handle other exceptions; avoid masking bugs or incorrectly handling exceptions that you cannot recover from.
cffu
provides corresponding catching*
methods
that support specifying exception types to handle; compared to the CF#exceptionally
method,
it adds an exception type parameter, with similar usage, so no code example is provided.
All new CF
methods from Java 9+
higher versions are directly available in Java 8
lower versions.
Important backport
features include:
- Timeout control:
orTimeout
/completeOnTimeout
- Delayed execution:
delayedExecutor
- Factory methods:
failedFuture
/completedStage
/failedStage
- Processing operations:
completeAsync
/exceptionallyAsync
/exceptionallyCompose
/copy
- Non-blocking reads:
resultNow
/exceptionNow
/state
These backport
methods are existing functionality of CompletableFuture
, so no code examples are provided.
The CF#orTimeout()
/ CF#completeOnTimeout()
methods use
the internal single-threaded ScheduledThreadPoolExecutor
of CF
to trigger business logic execution
when timeouts occur, which can break CompletableFuture's timeout and delay functionalityβ
Because timeout and delayed execution are basic functionalities, once they fail, it can lead to:
- Business functionality correctness issues, with timeout triggers being inaccurate and delayed
- System stability issues, such as waiting operations in threads not returning,
other dependent
CF
s not completing, and thread pool exhaustion and memory leaks
The cffu
library provides timeout-safe new implementation methods:
Cffu#orTimeout()
/Cffu#completeOnTimeoutTimeout()
CFU#cffuOrTimeout()
/CFU#cffuCompleteOnTimeout()
Ensuring business logic won't execute in CF
's single-threaded ScheduledThreadPoolExecutor
.
For more information, see:
- Problem demonstration
DelayDysfunctionDemo.java
cffu backport
method JavaDoc:CFU#orTimeout()
/CFU#completeOnTimeout()
- Article Improper Use of
CompletableFuture
Timeout Functionality Causes Production Incidents
The cf.join()
method "waits forever without timeout", which is very dangerous in businessβοΈ
When unexpected long waits occur, it can lead to:
- Main business logic blocking, with no opportunity for appropriate handling to respond to users in time
- Consuming a thread, and threads are very limited resources (usually a few hundred); exhausting threads means service paralysis and failure
The join(timeout, unit)
method is a join
method that supports timeout;
it's like cf.get(timeout, unit)
compared to cf.get()
.
This new method is simple and similar to use, so no code example is provided.
CompletableFuture
's anyOf()
method returns type Object
, losing specific types,
making it inconvenient to use return values requiring casting operations, and it's not type-safe.
cffu
's anySuccessOf()
/ anyOf()
methods return specific type T
instead of returning Object
.
This method is simple and similar to use, so no code example is provided.
CompletableFuture
's allOf()
/ anyOf()
methods take CompletableFuture
parameter types,
not the broader CompletionStage
types; for CompletionStage
type inputs,
you need to call the CompletionStage#toCompletableFuture
method for conversion.
cffu
's allOf()
/ anyOf()
methods accept broader CompletionStage
parameter types,
making them more convenient to use.
The method usage is simple and similar, so no code example is provided.
You can refer to:
Java API
Documentation- Implementation source code, such as
- Current version
Java API
documentation: https://foldright.io/api-docs/cffu2/
You can check the latest available version list at central.sonatype.com.
cffu
library (including enhancedCompletableFutureUtils
forJava CompletableFuture
):-
For
Maven
projects:<dependency> <groupId>io.foldright</groupId> <artifactId>cffu2</artifactId> <version>2.0.0</version> </dependency>
-
For
Gradle
projects:Gradle Kotlin DSL
implementation("io.foldright:cffu2:2.0.0")
Gradle Groovy DSL
implementation 'io.foldright:cffu2:2.0.0'
-
- π
TransmittableThreadLocal(TTL)
cffu executor wrapper SPI
implementation:-
For
Maven
projects:<dependency> <groupId>io.foldright</groupId> <artifactId>cffu2-ttl-executor-wrapper</artifactId> <version>2.0.0</version> <scope>runtime</scope> </dependency>
-
For
Gradle
projects:Gradle Kotlin DSL
runtimeOnly("io.foldright:cffu2-ttl-executor-wrapper:2.0.0")
Gradle Groovy DSL
runtimeOnly 'io.foldright:cffu2-ttl-executor-wrapper:2.0.0'
-
cffu bom
:-
For
Maven
projects:<dependency> <groupId>io.foldright</groupId> <artifactId>cffu2-bom</artifactId> <version>2.0.0</version> <type>pom</type> <scope>import</scope> </dependency>
-
For
Gradle
projects:Gradle Kotlin DSL
implementation(platform("io.foldright:cffu2-bom:2.0.0"))
Gradle Groovy DSL
implementation platform('io.foldright:cffu2-bom:2.0.0')
-
- Official Documentation
CF/cffu
Juejin Column bycffu
developer@linzee1
CompletableFuture
Guide- Complete explanation of
CompletableFuture
usage - Provides best practice recommendations and usage pitfall warnings
- For more effective and safe use of
CompletableFuture
in business
- Complete explanation of
cffu
is short for CompletableFuture-Fu
; pronounced as C Fu
, which sounds like Shifu
.
Yes, it may remind you of the cute raccoon shifu from "Kung Fu Panda"~ π¦