What is the CompletableFuture
API?
WARNING
This tutorial assumes that you are familiar with lambda expressions. Take a look at the lambda introduction first if you are not!
AT4J uses Futures for asynchronous requests. To understand how AT4J works, you must understand the concept of Futures in general, as well as their most common implementation, the CompletableFuture.
CompletableFuture
is a class introduced in Java 8 that represents a future result of an asynchronous computation. It is a part of the java.util.concurrent
package and is used for asynchronous programming in Java.
This little introduction gives you a quick overview of the basics you need to know to work with Futures.
What is a Future
?
A Future
is a placeholder for a result that is not yet available. It represents the result of an asynchronous computation.
This is useful if a method call requires some time and should not block the execution of your current code. You can see the difference with a primitive speed comparison:
long currentTime = System.currentTimeMillis();
TranslateParams params =
new TranslateParams("Hello World!", List.of("pt", "es", "fr"));
azureApi.translate(params);
azureApi.translate(params);
azureApi.translate(params);
azureApi.translate(params);
azureApi.translate(params);
System.out.println((System.currentTimeMillis() - currentTime) + " ms");
long currentTime = System.currentTimeMillis();
TranslateParams params =
new TranslateParams("Hello World!", List.of("pt", "es", "fr"));
azureApi.translate(params).join();
azureApi.translate(params).join();
azureApi.translate(params).join();
azureApi.translate(params).join();
azureApi.translate(params).join();
System.out.println((System.currentTimeMillis() - currentTime) + " ms");
The first example will take a few milliseconds to execute, while the second example will take a few seconds to execute. This is because the first example does not wait for the result of the translate
method, while the second example waits for the result of each translate
method call.
CompletableFuture
Methods
join()
CAUTION
The join
method is a blocking operation, which means that it will wait for the result of the CompletableFuture
to be available. If the computation fails, a CompletionException
will be thrown. It is recommended to use the join
method only when you are sure that the computation will succeed. Avoid using the join
method in production code, and methods which will be called by other methods, as it can lead to deadlocks.
The join
method is used to wait for the result of the CompletableFuture
. It returns the result of the computation when it is done or throws a CompletionException
if anything failed.
The following example demonstrates how to use the join
method:
TranslateParams params =
new TranslateParams("Hello World!", List.of("pt", "es", "fr"));
Optional<TranslationResponse> translationResult = azureApi.translate(params).join();
translationResult.ifPresent(
response -> response.getFirstResult().getTranslations().forEach(System.out::println));
This example translates the text "Hello World!" into Portuguese, Spanish, and French. The join
method waits for the result of the translation and prints the translations to the console.
TIP
While join() can become a performance issue when you call it very frequently, it is very convenient to use and easy to understand. If you are new to programming and just want to get your first application working, this is a good method to start with. Once you gathered more experience, we highly advise against using join()
as it negatively impacts the performance of your application.
thenAccept()
The thenAccept
method is used to perform an action when the CompletableFuture
completes. It takes a Consumer
as a parameter, which is called with the result of the computation when it is done.
The following example demonstrates how to use the thenAccept
method:
TranslateParams params =
new TranslateParams("Hello World!", List.of("pt", "es", "fr"));
azureApi.translate(params).thenAccept(
response -> response.getFirstResult().getTranslations().forEach(System.out::println));
CAUTION
The example above does not handle exceptions. If the translation fails, the exception will be thrown and not caught. This can lead to unexpected behavior in your application and make it harder to debug. It is recommended to use the exceptionally
method to handle exceptions.
exceptionally(...)
The exceptionally
method is used to handle exceptions that occur during the computation of the CompletableFuture
. It takes a Function
as a parameter, which is called with the exception when it occurs.
The following example demonstrates how to use the exceptionally
method:
TranslateParams params =
new TranslateParams("Hello World!", List.of("pt", "es", "fr"));
azureApi.translate(params).exceptionally(
exception -> {
System.err.println("An error occurred: " + exception.getMessage());
return null;
}).thenAccept(
response -> response.getFirstResult().getTranslations().forEach(System.out::println));
thenCompose()
The thenCompose
method is used to chain multiple CompletableFuture
instances together. It takes a Function
as a parameter, which is called with the result of the computation when it is done.
The following example demonstrates how to use the thenCompose
method:
TranslateParams params =
new TranslateParams("Hello World!", List.of("pt", "es", "fr"));
azureApi.translate(params).thenCompose(
response -> azureApi.translate(params)).thenAccept(
response -> response.getFirstResult().getTranslations().forEach(System.out::println));
Further Reading
To learn more about CompletableFuture
, check out the following resources: