Exercises
In these exercises, you’ll practice some of the concepts taught in this module.
First, either create a new Java project, adding the reactor-bom
and reactor-core
dependencies to your build file (Maven or Gradle) or use the stub you can find at: https://github.com/eh3rrera/project-reactor-course/tree/main/05/before/reactor-demo-exercises.
I’ll give you the instructions (and sometimes hints) so you can put all the code together in the main
method of a class and observe the output.
Here you can find the solution for the coding exercises: https://github.com/eh3rrera/project-reactor-course/tree/main/05/after/reactor-demo-exercises.
Exercise 1
This exercise simulates fetching user data from a remote API. The API can return either a list of user names or an error.
- Given the following code where the method
simulateApiCall
simulates an API call and based on a random value, either returns a list of user names or throws an exception:import reactor.core.publisher.Flux; import java.util.Arrays; import java.util.List; import java.util.Random; public class Exercise01 { public static void main(String[] args) { Flux<List<String>> userNamesFlux = Flux.defer(() -> { // TODO Create either a Flux with the result or with an error return null; }); userNamesFlux.subscribe(userNames -> System.out.println("User data: " + userNames), error -> System.out.println("Error fetching user data: " + error.getMessage())); } public static List<String> simulateApiCall() { List<String> userNames = Arrays.asList("Alice", "Bob", "Carol", "David"); Random random = new Random(); if (random.nextBoolean()) { throw new RuntimeException("Remote API error"); } return userNames; } }
- Implement the body of the lambda expression passed to
Flux.defer
to call the methodsimulateApiCall
:- Create a
Flux
that uses theFlux.error
method to emit an error if thesimulateApiCall
method throws an exception. - Otherwise, create a
Flux
with the list returned by the method.
- Create a
- Run the
Exercise01
class and analyze the output.
Exercise 2
In the following exercise, you’ll implement the reactive equivalent of the following imperative try-catch-finally
block.
- Create a class named
Exercise02
with amain
method. - Given the following code:
List<Integer> inputList = Arrays.asList(1, 2, -3, 4); try { for (Integer num : inputList) { if (num < 0) { throw new IllegalArgumentException("Negative numbers are not allowed."); } System.out.println(num * 2); } } catch (IllegalArgumentException e) { System.out.println("Error: " + e.getMessage()); } finally { System.out.println("Finished processing the list."); }
- Create a
Flux
with the values ofinputList
(if you prefer, you can create aFlux
from this list). - Implement the reactive equivalent of the above
try-catch-finally
block using themap
,doOnError
,doFinally
, andsubscribe
operators. - Run the
Exercise02
class and analyze the output.
HINTS:
- Use the
map
operator to double each integer and throw anIllegalArgumentException
if a negative number is found. - Handle the error using the
doOnError
operator, printing the appropriate message. - Use the
doFinally
operator to print the appropriate message regardless of whether an error occurred or not. - Subscribe to the
Flux
to print each doubled value, also passing an empty error consumer to avoid printing the stack trace of the exception.
Exercise 3
In this exercise, you are given a list of URLs, each containing a JSON string. Your task is to create a program that fetches JSON data from the URLs and parses them into strings. Some of the URLs may return invalid JSON responses, causing a exception to be thrown during the parsing process. In case of an invalid JSON string, you should fetch data from a fallback URL, parse it, and return the result.
- Create a class named
Exercise03
with amain
method. - Given the following code were the methods
fetchData
andparseJson
simulate the fetching and parsing of JSON data, respectively:import reactor.core.publisher.Flux; public class Exercise03 { public static void main(String[] args) { String fallbackUrl = "fallbackUrl"; Flux.just("url1", "url2", "url3", "url4") // TODO Use flatMap/map and onErrorResume to implement the functionality .subscribe(System.out::println); } public static String fetchData(String url) { // Simulates fetching JSON data from the URL return "{\"data\": \"" + url + "\"}"; } public static String parseJson(String jsonString) { // Simulates the parsing of jsonString and throwing an exception if it is invalid String str = ""; if(jsonString.contains("url3")) throw new RuntimeException("Invalid JSON"); else str = jsonString.toUpperCase(); return str; } }
- Fetch and parse the JSON data handling any exception using the
onErrorResume
operator. In case of an exception, fetch data from the fallback URL, parse it, and return the result. - Run the
Exercise03
class and analyze the output.
Note: This exercise can be implemented in more than one way. Probably one of the easiest is to get, inside a flatMap
operator, the JSON string as a Mono
, and then, use map to parse it and onErrorResume
to use the fallback URL. Take a look at the solution to review this approach if you’re having a hard time with this exercise.
Exercise 4
In this exercise, you are given a list of integers. Your task is to implement the error handling of a program that processes a number using doOnError
and onErrorResume
.
- Create a class named
Exercise04
with amain
method. - Given the following code:
import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public class Exercise04 { public static void main(String[] args) { Flux.just(1, 2, 3, 4, 5) .flatMap(number -> { // TODO Implement reactive sequence using doOnError and onErrorResume return null; }) .subscribe(System.out::println); } public static Mono<Integer> processNumber(int number) { int doubled = number * 2; if (doubled % 4 == 0) return Mono.error(new IllegalArgumentException("Result is divisible by 4")); else return Mono.just(doubled); } }
- Inside the
flatMap
operator:- Call the method
processNumber
to get aMono<Integer>
. - Use the
doOnError
method to print “Error processing [number]: “ followed by the error message, but only forIllegalArgumentException
. - Use the
onErrorResume
operator to handle any error by returning aMono
that emits-1
as the fallback value.
- Call the method
- Run the
Exercise04
class and analyze the output.
Exercise 5
In this exercise, you’ll use Exceptions.propagate(Throwable) to propagate a checked exception as a RuntimeException
.
- Create a class named
Exercise04
with amain
method. - Given the following code:
import reactor.core.publisher.Flux; public class Exercise05 { public static void main(String[] args) { Flux<String> stringFlux = Flux.just("1", "2", "three", "4", "5"); stringFlux.map(stringNumber -> { // TODO Parse each element and propagate any NumberFormatException return null; }) .subscribe( System.out::println, error -> System.out.println("Error: " + error.getMessage()) ); } }
- Inside the
map
operator, parse the strings into integers. - If a string is not a valid number, throw a
NumberFormatException
and propagate it as aRuntimeException
usingExceptions.propagate(Throwable)
method. - Run the
Exercise05
class and analyze the output.