👉 cffu (CompletableFuture Fu 🦝) is a lightweight enhancement library for CompletableFuture(CF)
(CF),
designed to give you quick and easy development experience with less pitfalls, and to provide more convenient, efficient, and safe application of CF in business contexts.
Welcome! 👏 💖
Feel free to:
- suggest and ask questions: Submit issues.
- contribute and improve: Forking it and submitting pull request.
- 🔧 Features
- 👥 User Guide
- 1. Three ways to use
cffu
- 2.
cffu
Functionality Introduction- 2.1 Return results from multiple Running
CF
- 2.1.1 Returning Results from Multiple Different Types of
CF
- 2.2 Default business-related thread pool encapsulation
- 2.3 Efficient and Flexible Concurrent Execution Strategies (
AllFailFast
/AnySuccess
/MostSuccess
) - 2.4 Support for Timed
join
Methods - 2.5
Java 8``Backport
support - 2.6 Return Type-Specific
anyOf
Method - More Function Descriptions
- 2.1 Return results from multiple Running
- 3. How to Migrate from Direct Use of
CompletableFuture
toCffu
- 1. Three ways to use
- 🔌 API Docs
- 🍪Dependencies
- 📚 See also
- 👋 About the Library Name
🔧 Features
For more details on the usage and features of cffu, refer to the User Guide.
The provided features include:
🏪 More convenient methods, such as:
- Return multiple CF results instead of void (allOf), e.g.,
allResultsFailFastOf
,allResultsOf
,mSupplyFailFastAsync
,thenMApplyFailFastAsync
. - Return multiple different type CF results instead of the same type, e.g.,
allTupleFailFastOf
,allTupleOf
,mSupplyTupleFailFastAsync
,thenMApplyTupleFailFastAsync
. - Direct execution of multiple actions without wrapping them into CompletableFuture, e.g.,
mSupplyTupleFailFastAsync
,mSupplyMostSuccessAsync
,thenMApplyTupleFailFastAsync
,thenMRunFailFastAsync
.
⚙️ More efficient and flexible concurrent execution strategies, such as:
- AllFailFast strategy: Fail fast and return when any input CF fails, rather than waiting for all CFs to complete (allOf).
- AnySuccess strategy: Returns the first successful CF result, rather than the first completed (which might be a failure) (anyOf).
- MostSuccess strategy: Returns the successful results of multiple CFs within a specified time, ignoring failed or incomplete CFs (returns a default value).
🦺 Safer usage, such as:
- Timeout-enabled join methods with
join(timeout, unit)
. - Safe timeout execution with
cffuOrTimeout
/cffuCompleteOnTimeout
. - Peek method that ensures the result won't be modified.
- Forbidding forceful write with
CffuFactoryBuilder#forbidObtrudeMethods
. - Comprehensive code quality annotations on class methods to prompt IDE issues early, such as
@NonNull
,@Nullable
,@CheckReturnValue
,@Contract
, etc.
🧩 New methods not provided by Java CF (e.g., join(timeout, unit)
, cffuOrTimeout
, peek
), such as:
- Asynchronous exceptional completion with
completeExceptionallyAsync
. - Non-blocking successful result retrieval with
getSuccessNow
. - Unwrapping CF wrapped exception into business exception with
unwrapCfException
.
💪 Enhanced existing methods, such as:
anySuccessOf
/anyOf
methods: Return specific type T (type-safe) instead ofObject
(CompletableFuture#anyOf
).
⏳ Backport support for Java 8: All new CF features from Java 9+ are available in Java 8, such as:
- Timeout control with
orTimeout
/completeOnTimeout
. - Delayed execution with
delayedExecutor
. - Factory methods like
failedFuture
,completedStage
,failedStage
. - Handling operations with
completeAsync
,exceptionallyAsync
,exceptionallyCompose
,copy
.
🍩 Support Kotlin as first-class citizen.
For more details on the usage modes and functionalities of cffu, refer to the User Guide.
Managing concurrent execution is complex and error-prone, but there are numerous tools and frameworks available in the industry.
For a comprehensive understanding of concurrency tools and frameworks, refer to books like "Seven Concurrency Models in Seven Weeks," "Java Concurrency in Practice," and "Programming Scala, Second Edition." More books on concurrency can be found in this book list.
CompletableFuture (CF)
has its advantages:
- Built into the
Java
standard library- No additional dependencies are needed, making it almost always available
- Trusted to have high code quality
- Widely known and used with a strong community foundation
- Released with
Java 8
in 2014,CompletableFuture
has been around for 10 years - Its parent interface,
Future
, has been available sinceJava 5
in 2004, totally 20 years - Although the
Future
interface does not support asynchronous result retrieval and concurrent task orchestration, it has made this concept and tool well-known among manyJava
developers.
- Released with
- Powerful yet not overly complex
- High-level abstraction
- Allow expressing technical concurrency flows as business processes
- Avoids cumbersome and error-prone low-level coordination tools such as locks (
Lock
),CountDownLatch
, semaphores (Semaphore
), andCyclicBarrier
Like other concurrency tools and frameworks, CompletableFuture
is used for:
- Concurrent execution of business logic or orchestration of concurrent processes/tasks
- Leveraging multi-core parallel processing
- Enhancing business responsiveness
A deeper understanding is essential before applying it to real-world scenarios. 💕
cffu
supports three ways to use:
- 🦝 1) Using the
Cffu
Class- Recommended when using
Java
in your project. - Migration from direct use of
CompletableFuture
toCffu
involves two changes:- Change type declarations from
CompletableFuture
toCffu
. - Replace static method calls from
CompletableFuture
class with thecffuFactory
instance. - For more details, see How to Migrate from Direct Use of the
CompletableFuture
Class to theCffu
Class.
- Change type declarations from
- Depends on the
io.foldright:cffu
library.
- Recommended when using
- 🛠️️ 2) Using the
CompletableFutureUtils
utility Class- If you prefer not to introduce new classes (
Cffu
) to your project, as it might add some complexity:- You can use
cffu
library as a utility class. - Optimizing
CompletableFuture
usage with utility methods is common in business projects, andCompletableFutureUtils
offers a set of practical, reliable, efficient, and safe utility methods.
- You can use
- Some features of
cffu
are not available in this approach (and no implementation solution is planned) 😔, such as setting a default business thread pool and preventing forced write. - Depends on the
io.foldright:cffu
library.
- If you prefer not to introduce new classes (
- 🍩 3) Using
Kotlin
Extension Methods- Recommended for projects using
Kotlin
. - Requires the
io.foldright:cffu-kotlin
library.
- Recommended for projects using
Before diving into the feature points, check out the examples of the different ways to use cffu
. 🎪
public class CffuDemo {
private static final ExecutorService myBizThreadPool = Executors.newCachedThreadPool();
// Create a CffuFactory with configuration of the customized thread pool
private static final CffuFactory cffuFactory = CffuFactory.builder(myBizThreadPool).build();
public static void main(String[] args) throws Exception {
final Cffu<Integer> cf42 = cffuFactory
.supplyAsync(() -> 21) // Run in myBizThreadPool
.thenApply(n -> n * 2);
// Below tasks all run in myBizThreadPool
final Cffu<Integer> longTaskA = cf42.thenApplyAsync(n -> {
sleep(1001);
return n / 2;
});
final Cffu<Integer> longTaskB = cf42.thenApplyAsync(n -> {
sleep(1002);
return n / 2;
});
final Cffu<Integer> longTaskC = cf42.thenApplyAsync(n -> {
sleep(100);
return n * 2;
});
final Cffu<Integer> longFailedTask = cf42.thenApplyAsync(unused -> {
sleep(1000);
throw new RuntimeException("Bang!");
});
final Cffu<Integer> combined = longTaskA.thenCombine(longTaskB, Integer::sum)
.orTimeout(1500, TimeUnit.MILLISECONDS);
System.out.println("combined result: " + combined.get());
final Cffu<Integer> anySuccess = cffuFactory.anySuccessOf(longTaskC, longFailedTask);
System.out.println("any success result: " + anySuccess.get());
}
}
# See complete runnable demo code in
CffuDemo.java
。
public class CompletableFutureUtilsDemo {
private static final ExecutorService myBizThreadPool = Executors.newCachedThreadPool();
public static void main(String[] args) throws Exception {
final CompletableFuture<Integer> cf42 = CompletableFuture
.supplyAsync(() -> 21, myBizThreadPool) // Run in myBizThreadPool
.thenApply(n -> n * 2);
final CompletableFuture<Integer> longTaskA = cf42.thenApplyAsync(n -> {
sleep(1001);
return n / 2;
}, myBizThreadPool);
final CompletableFuture<Integer> longTaskB = cf42.thenApplyAsync(n -> {
sleep(1002);
return n / 2;
}, myBizThreadPool);
final CompletableFuture<Integer> longTaskC = cf42.thenApplyAsync(n -> {
sleep(100);
return n * 2;
}, myBizThreadPool);
final CompletableFuture<Integer> longFailedTask = cf42.thenApplyAsync(unused -> {
sleep(1000);
throw new RuntimeException("Bang!");
}, myBizThreadPool);
final CompletableFuture<Integer> combined = longTaskA.thenCombine(longTaskB, Integer::sum);
final CompletableFuture<Integer> combinedWithTimeout =
orTimeout(combined, 1500, TimeUnit.MILLISECONDS);
System.out.println("combined result: " + combinedWithTimeout.get());
final CompletableFuture<Integer> anySuccess = anySuccessOf(longTaskC, longFailedTask);
System.out.println("any success result: " + anySuccess.get());
}
}
# See complete runnable demo code in
CompletableFutureUtilsDemo.java
。
private val myBizThreadPool: ExecutorService = Executors.newCachedThreadPool()
// Create a CffuFactory with configuration of the customized thread pool
private val cffuFactory: CffuFactory = CffuFactory.builder(myBizThreadPool).build()
fun main() {
val cf42 = cffuFactory
.supplyAsync { 21 } // Run in myBizThreadPool
.thenApply { it * 2 }
// Below tasks all run in myBizThreadPool
val longTaskA = cf42.thenApplyAsync { n: Int ->
sleep(1001)
n / 2
}
val longTaskB = cf42.thenApplyAsync { n: Int ->
sleep(1002)
n / 2
}
val longTaskC = cf42.thenApplyAsync { n: Int ->
sleep(100)
n * 2
}
val longFailedTask = cf42.thenApplyAsync<Int> { _ ->
sleep(1000)
throw RuntimeException("Bang!")
}
val combined = longTaskA.thenCombine(longTaskB, Integer::sum)
.orTimeout(1500, TimeUnit.MILLISECONDS)
println("combined result: ${combined.get()}")
val anySuccess: Cffu<Int> = listOf(longTaskC, longFailedTask).anySuccessOfCffu()
println("any success result: ${anySuccess.get()}")
}
# See complete runnable demo code in
CffuDemo.kt
。
The allOf
method in CompletableFuture
does not return results directly; it returns Void
, making it inconvenient to retrieve results from multiple running CF
:
- You need to perform additional read operations (like
join
/get
) on the inputCF
afterallOf
.- This approach is cumbersome.
- Read methods (like
join
/get
) are blocking, increasing the risk of deadlock in business logic. ❗️ For more details, refer to Principles and Practices of CompletableFuture - 4.2.2 ThreadPool Circular Reference Leading to Deadlock
- Alternatively, you can pass an
Action
and set external variables within it, but this requires careful consideration of thread safety⚠️ .- It is complex to manage concurrent data transfers across multiple threads, and it is common to mishandle the logic for reading and writing data concurrently in business code.❗️
cffu
provides allResultsFailFastOf
/allResultsOf
methods to facilitate retrieving results from multiple CF
:
- Convenient and direct retrieval of results using library functions.
- Reduces the complexity of thread safety and logical errors that come with managing read/write operations across multiple threads.
- Returns a CF with combined results, allowing further chaining with non-blocking operations. This naturally reduces the need for blocking methods like join or get and lowers the risk of deadlocks.
Example code:
public class AllResultsOfDemo {
public static final Executor myBizExecutor = Executors.newCachedThreadPool();
public 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.
Cffu<List<Integer>> allResults = cffuFactory.allResultsOf(cffu1, cffu2);
System.out.println(allResults.get());
//////////////////////////////////////////////////
// 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());
}
}
# See complete runnable demo code in
AllResultsOfDemo.java
。
In addition to handling multiple CF
instances with the same result type, cffu
also offers methods to handle multiple CF
instances with different result types using allTupleFailFastOf
/allTupleOf
methods.
Example code:
public class AllTupleOfDemo {
public static final Executor myBizExecutor = Executors.newCachedThreadPool();
public static final CffuFactory cffuFactory = CffuFactory.builder(myBizExecutor).build();
public static void main(String[] args) throws Exception {
//////////////////////////////////////////////////
// allTupleFailFastOf / allTupleOf
//////////////////////////////////////////////////
Cffu<String> cffu1 = cffuFactory.completedFuture("21");
Cffu<Integer> cffu2 = cffuFactory.completedFuture(42);
Cffu<Tuple2<String, Integer>> allTuple = cffuFactory.allTupleFailFastOf(cffu1, cffu2);
System.out.println(allTuple.get());
//////////////////////////////////////////////////
// or CompletableFutureUtils.allTupleFailFastOf / allTupleOf
//////////////////////////////////////////////////
CompletableFuture<String> cf1 = CompletableFuture.completedFuture("21");
CompletableFuture<Integer> cf2 = CompletableFuture.completedFuture(42);
CompletableFuture<Tuple2<String, Integer>> allTuple2 = allTupleFailFastOf(cf1, cf2);
System.out.println(allTuple2.get());
}
}
# See complete runnable demo code in
AllTupleOfDemo.java
。
- By default,
CompletableFuture
executes its*Async
methods using theForkJoinPool.commonPool()
. - This thread pool typically has a number of threads equal to the number of CPUs, making it suitable for CPU-intensive tasks. However, business logic often involves many waiting operations (such as network IO and blocking waits), which are not CPU-intensive.
- Using the default thread pool
ForkJoinPool.commonPool()
for business logic is risky❗
As a result,
- In business logic, calling
CompletableFuture
's*Async
methods often requires repeatedly passing a specific business thread pool, making the use ofCompletableFuture
cumbersome and error-prone 🤯. - In underlying logic, when callbacks to business operations occur (such as RPC callbacks), it is neither suitable nor convenient to provide a thread pool for the business. In such cases, using the thread pool encapsulated by
Cffu
is convenient, reasonable, and safe.
For more on use case, see Principles and Practices of CompletableFuture - 4.2.3 Asynchronous RPC Calls Should Avoid Blocking 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();
}
}
# See complete runnable demo code in
NoDefaultExecutorSettingForCompletableFuture.java
。
Cffu
supports setting a default business thread pool, avoiding the aforementioned complexities and pitfalls.
Example code:
public class DefaultExecutorSettingForCffu {
public static final Executor myBizExecutor = Executors.newCachedThreadPool();
public 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();
}
}
# See complete runnable demo code in
DefaultExecutorSettingForCffu.java
。
- The
allOf
method inCompletableFuture
waits for all inputCF
to complete; even if oneCF
fails, it waits for the remainingCF
to complete before returning a failedCF
.- For business logic, a fail-and-continue-waiting strategy slows down responsiveness. It's better to fail fast if any input CF fails, so you can avoid unnecessary waiting.
cffu
provides corresponding methods likeallResultsFailFastOf
.- Both
allOf
andallResultsFailFastOf
return a successful result only when all inputCF
succeed.
- The
anyOf
method inCompletableFuture
returns the first completedCF
(without waiting for the others to complete, a "race" mode); even if the first completedCF
is a failure, it returns this failedCF
result.- For business logic, it's preferable for the race mode to return the first successful
CF
result, rather than the first completed but failedCF
. cffu
provides corresponding methods likeanySuccessOf
.anySuccessOf
returns a failed result only if all inputCF
fail.
- For business logic, it's preferable for the race mode to return the first successful
- Return successful results from multiple
CF
within a specified time, ignoring failed or runningCF
(returning a specified default value).- To maintain eventual business consistency, return whatever results are available. You can store results from running
CF
in a distributed cache to avoid recalculation and make them accessible for future use. - This is a common business use case.
cffu
provides corresponding methods likemostSuccessResultsOf
.
- To maintain eventual business consistency, return whatever results are available. You can store results from running
📔 For more on concurrent execution strategies of multiple
CF
, see the JavaScript specification forPromise Concurrency
; in JavaScript,Promise
corresponds toCompletableFuture
.JavaScript Promise provides four concurrent execution methods:
Promise.all()
: Waits for allPromise
to succeed; if any one fails, it immediately returns a failure (corresponding tocffu
'sallResultsFailFastOf
method).Promise.allSettled()
: Waits for allPromise
to complete, regardless of success or failure (corresponding tocffu
'sallResultsOf
method).Promise.any()
: Race mode, immediately returns the first successfulPromise
(corresponding tocffu
'sanySuccessOf
method).Promise.race()
: Race mode, immediately returns the first completedPromise
(corresponding tocffu
'sanyOf
method).PS: The naming of JavaScript Promise methods is really well thought out~ 👍
With the addition of two new methods,
cffu
aligns with the concurrent methods in the JavaScript Promise specification~ 👏
Example code:
public class ConcurrencyStrategyDemo {
public static final Executor myBizExecutor = Executors.newCachedThreadPool();
public 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> successAfterLongTime = cffuFactory.supplyAsync(() -> {
sleep(3000); // sleep LONG time
return 42;
});
final Cffu<Integer> failed = cffuFactory.failedFuture(new RuntimeException("Bang!"));
Cffu<List<Integer>> failFast = cffuFactory.allResultsFailFastOf(successAfterLongTime, failed);
// fail fast without waiting successAfterLongTime
System.out.println(failFast.exceptionNow());
Cffu<Integer> anySuccess = cffuFactory.anySuccessOf(successAfterLongTime, failed);
System.out.println(anySuccess.get());
Cffu<List<Integer>> mostSuccess = cffuFactory.mostSuccessResultsOf(
0, 100, TimeUnit.MILLISECONDS, successAfterLongTime, failed);
System.out.println(mostSuccess.get());
////////////////////////////////////////////////////////////////////////
// or CompletableFutureUtils#allResultsFailFastOf
// CompletableFutureUtils#anySuccessOf
// CompletableFutureUtils#mostSuccessResultsOf
////////////////////////////////////////////////////////////////////////
final CompletableFuture<Integer> successAfterLongTimeCf = CompletableFuture.supplyAsync(() -> {
sleep(3000); // sleep LONG time
return 42;
});
final CompletableFuture<Integer> failedCf = failedFuture(new RuntimeException("Bang!"));
CompletableFuture<List<Integer>> failFast2 = allResultsFailFastOf(successAfterLongTimeCf, failedCf);
// fail fast without waiting successAfterLongTime
System.out.println(exceptionNow(failFast2));
CompletableFuture<Integer> anySuccess2 = anySuccessOf(successAfterLongTimeCf, failedCf);
System.out.println(anySuccess2.get());
CompletableFuture<List<Integer>> mostSuccess2 = mostSuccessResultsOf(
0, 100, TimeUnit.MILLISECONDS, successAfterLongTime, failed);
System.out.println(mostSuccess2.get());
}
}
# See complete runnable demo code in
ConcurrencyStrategyDemo.java
。
cf.join()
waits indefinitely, which is very dangerous in business applications❗️ When unexpectedly long waits occur, they can:
- Block the main business logic, delaying user responses.
- Tie up threads, which are a limited resource (usually only a few hundred). If too many threads are consumed, it can lead to service outages.
The join(timeout, unit)
method supports a timed join
, similar to how cf.get(timeout, unit)
complements cf.get()
.
This new method is simple to use and does not require a code example.
All new CF
features from higher versions (Java 9+
) are directly available in the lower version Java 8
.
Key backport features include:
- Timeout control:
orTimeout
/completeOnTimeout
methods - Delayed execution:
delayedExecutor
method - Factory methods:
failedFuture
/completedStage
/failedStage
- Handling operations:
completeAsync
/exceptionallyAsync
/exceptionallyCompose
/copy
These backported methods are existing functionalities of CompletableFuture
and do not require code examples.
The CompletableFuture.anyOf method returns an Object, which loses type specificity and type safety, making it inconvenient to use since it requires explicit casting.
cffu
provides anySuccessOf
/ anyOf
methods that return a specific type T
instead of returning Object
.
This new method is simple to use and does not require a code example.
For more information, refer to:
API
Documentation- Source Code
cffu
:Cffu.java
,CffuFactory.java
CompletableFuture utils
:CompletableFutureUtils.java
Kotlin extensions
:CffuExtensions.kt
,CompletableFutureExtensions.kt
To utilize cffu
's enhanced features, you can migrate existing code that directly uses the CompletableFuture
class to the Cffu
class with two modifications:
- Change the type declarations from the
CompletableFuture
class to theCffu
class. - Replace static method calls from
CompletableFuture
withcffuFactory
instance calls.
This migration is possible because:
- All instance methods of the
CompletableFuture
class are present in theCffu
class with the same method signatures and functionalities. - All static methods of the
CompletableFuture
class are present in theCffuFactory
class with the same method signatures and functionalities.
- Current version of
Java API
documentation: https://foldright.io/api-docs/cffu/ - Current version of
Kotlin API
documentation: https://foldright.io/api-docs/cffu-kotlin/
check out central.sonatype.com for new or available versions。
cffu
library(includingJava CompletableFuture
enhancedCompletableFutureUtils
):-
For
Maven
projects:<dependency> <groupId>io.foldright</groupId> <artifactId>cffu</artifactId> <version>1.0.0-Alpha15</version> </dependency>
-
For
Gradle
projects:// Gradle Kotlin DSL implementation("io.foldright:cffu:1.0.0-Alpha15")
// Gradle Groovy DSL implementation 'io.foldright:cffu:1.0.0-Alpha15'
-
cffu Kotlin
support library:-
For
Maven
projects:<dependency> <groupId>io.foldright</groupId> <artifactId>cffu-kotlin</artifactId> <version>1.0.0-Alpha15</version> </dependency>
-
For
Gradle
projects:// Gradle Kotlin DSL implementation("io.foldright:cffu-kotlin:1.0.0-Alpha15")
// Gradle Groovy DSL implementation 'io.foldright:cffu-kotlin:1.0.0-Alpha15'
-
cffu bom
:-
For
Maven
projects:<dependency> <groupId>io.foldright</groupId> <artifactId>cffu-bom</artifactId> <version>1.0.0-Alpha15</version> <type>pom</type> <scope>import</scope> </dependency>
-
For
Gradle
projects:// Gradle Kotlin DSL implementation(platform("io.foldright:cffu-bom:1.0.0-Alpha15"))
// Gradle Groovy DSL implementation platform('io.foldright:cffu-bom:1.0.0-Alpha15')
-
- 📌
TransmittableThreadLocal(TTL)
implementation forcffu executor wrapper SPI
:-
For
Maven
projects:<dependency> <groupId>io.foldright</groupId> <artifactId>cffu-ttl-executor-wrapper</artifactId> <version>1.0.0-Alpha15</version> <scope>runtime</scope> </dependency>
-
For
Gradle
projects:// Gradle Kotlin DSL runtimeOnly("io.foldright:cffu-ttl-executor-wrapper:1.0.0-Alpha15")
// Gradle Groovy DSL runtimeOnly 'io.foldright:cffu-ttl-executor-wrapper:1.0.0-Alpha15'
-
- A comprehensive guide to use
CompletableFuture
- Offers best practices and pitfalls to avoid
- Provides strategies for effectively and safely integrating
CompletableFuture
into business applications
The library name cffu
is short for CompletableFuture-Fu
, pronounced as "C Fu", which sounds like "Shifu" in Chinese, reminiscent of the beloved raccoon master from "Kung Fu Panda" 🦝