eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Partner – LambdaTest – NPI EA (cat=Testing)
announcement - icon

Regression testing is an important step in the release process, to ensure that new code doesn't break the existing functionality. As the codebase evolves, we want to run these tests frequently to help catch any issues early on.

The best way to ensure these tests run frequently on an automated basis is, of course, to include them in the CI/CD pipeline. This way, the regression tests will execute automatically whenever we commit code to the repository.

In this tutorial, we'll see how to create regression tests using Selenium, and then include them in our pipeline using GitHub Actions:, to be run on the LambdaTest cloud grid:

>> How to Run Selenium Regression Tests With GitHub Actions

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Overview

In this article, we are going to explore exactly what Vavr is, why we need it and how to use it in our projects.

Vavr is a functional library for Java 8+ that provides immutable data types and functional control structures.

1.1. Maven Dependency

In order to use Vavr, you need to add the dependency:

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.9.0</version>
</dependency>

It is recommended to always use the latest version. You can get it by following this link.

2. Option

The main goal of Option is to eliminate null checks in our code by leveraging the Java type system.

Option is an object container in Vavr with a similar end goal like Optional in Java 8. Vavr’s Option implements Serializable, Iterable, and has a richer API.

Since any object reference in Java can have a null value, we usually have to check for nullity with if statements before using it. These checks make the code robust and stable:

@Test
public void givenValue_whenNullCheckNeeded_thenCorrect() {
    Object possibleNullObj = null;
    if (possibleNullObj == null) {
        possibleNullObj = "someDefaultValue";
    }
    assertNotNull(possibleNullObj);
}

Without checks, the application can crash due to a simple NPE:

@Test(expected = NullPointerException.class)
public void givenValue_whenNullCheckNeeded_thenCorrect2() {
    Object possibleNullObj = null;
    assertEquals("somevalue", possibleNullObj.toString());
}

However, the checks make the code verbose and not so readable, especially when the if statements end up being nested multiple times.

Option solves this problem by totally eliminating nulls and replacing them with a valid object reference for each possible scenario.

With Option a null value will evaluate to an instance of None, while a non-null value will evaluate to an instance of Some:

@Test
public void givenValue_whenCreatesOption_thenCorrect() {
    Option<Object> noneOption = Option.of(null);
    Option<Object> someOption = Option.of("val");

    assertEquals("None", noneOption.toString());
    assertEquals("Some(val)", someOption.toString());
}

Therefore, instead of using object values directly, it’s advisable to wrap them inside an Option instance as shown above.

Notice, that we did not have to do a check before calling toString yet we did not have to deal with a NullPointerException as we had done before. Option’s toString returns us meaningful values in each call.

In the second snippet of this section, we needed a null check, in which we would assign a default value to the variable, before attempting to use it. Option can deal with this in a single line, even if there is a null:

@Test
public void givenNull_whenCreatesOption_thenCorrect() {
    String name = null;
    Option<String> nameOption = Option.of(name);
   
    assertEquals("baeldung", nameOption.getOrElse("baeldung"));
}

Or a non-null:

@Test
public void givenNonNull_whenCreatesOption_thenCorrect() {
    String name = "baeldung";
    Option<String> nameOption = Option.of(name);

    assertEquals("baeldung", nameOption.getOrElse("notbaeldung"));
}

Notice how, without null checks, we can get a value or return a default in a single line.

3. Tuple

There is no direct equivalent of a tuple data structure in Java. A tuple is a common concept in functional programming languages. Tuples are immutable and can hold multiple objects of different types in a type-safe manner.

Vavr brings tuples to Java 8. Tuples are of type Tuple1, Tuple2 to Tuple8 depending on the number of elements they are to take.

There is currently an upper limit of eight elements. We access elements of a tuple like tuple._n where n is similar to the notion of an index in arrays:

public void whenCreatesTuple_thenCorrect1() {
    Tuple2<String, Integer> java8 = Tuple.of("Java", 8);
    String element1 = java8._1;
    int element2 = java8._2();

    assertEquals("Java", element1);
    assertEquals(8, element2);
}

Notice that the first element is retrieved with n==1. So a tuple does not use a zero base like an array. The types of the elements that will be stored in the tuple must be declared in its type declaration as shown above and below:

@Test
public void whenCreatesTuple_thenCorrect2() {
    Tuple3<String, Integer, Double> java8 = Tuple.of("Java", 8, 1.8);
    String element1 = java8._1;
    int element2 = java8._2();
    double element3 = java8._3();
        
    assertEquals("Java", element1);
    assertEquals(8, element2);
    assertEquals(1.8, element3, 0.1);
}

A tuple’s place is in storing a fixed group of objects of any type that are better processed as a unit and can be passed around. A more obvious use case is returning more than one object from a function or a method in Java.

4. Try

In Vavr, Try is a container for a computation which may result in an exception.

As Option wraps a nullable object so that we don’t have to explicitly take care of nulls with if checks, Try wraps a computation so that we don’t have to explicitly take care of exceptions with try-catch blocks.

Take the following code for example:

@Test(expected = ArithmeticException.class)
public void givenBadCode_whenThrowsException_thenCorrect() {
    int i = 1 / 0;
}

Without try-catch blocks, the application would crash. In order to avoid this, you would need to wrap the statement in a try-catch block. With Vavr, we can wrap the same code in a Try instance and get a result:

@Test
public void givenBadCode_whenTryHandles_thenCorrect() {
    Try<Integer> result = Try.of(() -> 1 / 0);

    assertTrue(result.isFailure());
}

Whether the computation was successful or not can then be inspected by choice at any point in the code.

In the above snippet, we have chosen to simply check for success or failure. We can also choose to return a default value:

@Test
public void givenBadCode_whenTryHandles_thenCorrect2() {
    Try<Integer> computation = Try.of(() -> 1 / 0);
    int errorSentinel = result.getOrElse(-1);

    assertEquals(-1, errorSentinel);
}

Or even to explicitly throw an exception of our choice:

@Test(expected = ArithmeticException.class)
public void givenBadCode_whenTryHandles_thenCorrect3() {
    Try<Integer> result = Try.of(() -> 1 / 0);
    result.getOrElseThrow(ArithmeticException::new);
}

In all the above cases, we have control over what happens after the computation, thanks to Vavr’s Try.

5. Functional Interfaces

With the arrival of Java 8, functional interfaces are inbuilt and easier to use, especially when combined with lambdas.

However, Java 8 provides only two basic functions. One takes only a single parameter and produces a result:

@Test
public void givenJava8Function_whenWorks_thenCorrect() {
    Function<Integer, Integer> square = (num) -> num * num;
    int result = square.apply(2);

    assertEquals(4, result);
}

The second only takes two parameters and produces a result:

@Test
public void givenJava8BiFunction_whenWorks_thenCorrect() {
    BiFunction<Integer, Integer, Integer> sum = 
      (num1, num2) -> num1 + num2;
    int result = sum.apply(5, 7);

    assertEquals(12, result);
}

On the flip side, Vavr extends the idea of functional interfaces in Java further by supporting up to a maximum of eight parameters and spicing up the API with methods for memoization, composition, and currying.

Just like tuples, these functional interfaces are named according to the number of parameters they take: Function0, Function1, Function2 etc. With Vavr, we would have written the above two functions like this:

@Test
public void givenVavrFunction_whenWorks_thenCorrect() {
    Function1<Integer, Integer> square = (num) -> num * num;
    int result = square.apply(2);

    assertEquals(4, result);
}

and this:

@Test
public void givenVavrBiFunction_whenWorks_thenCorrect() {
    Function2<Integer, Integer, Integer> sum = 
      (num1, num2) -> num1 + num2;
    int result = sum.apply(5, 7);

    assertEquals(12, result);
}

When there is no parameter but we still need an output, in Java 8 we would need to use a Supplier type, in Vavr Function0 is there to help:

@Test
public void whenCreatesFunction_thenCorrect0() {
    Function0<String> getClazzName = () -> this.getClass().getName();
    String clazzName = getClazzName.apply();

    assertEquals("com.baeldung.vavr.VavrTest", clazzName);
}

How about a five parameter function, it’s just a matter of using Function5:

@Test
public void whenCreatesFunction_thenCorrect5() {
    Function5<String, String, String, String, String, String> concat = 
      (a, b, c, d, e) -> a + b + c + d + e;
    String finalString = concat.apply(
      "Hello ", "world", "! ", "Learn ", "Vavr");

    assertEquals("Hello world! Learn Vavr", finalString);
}

We can also combine the static factory method FunctionN.of for any of the functions to create a Vavr function from a method reference. Like if we have the following sum method:

public int sum(int a, int b) {
    return a + b;
}

We can create a function out of it like this:

@Test
public void whenCreatesFunctionFromMethodRef_thenCorrect() {
    Function2<Integer, Integer, Integer> sum = Function2.of(this::sum);
    int summed = sum.apply(5, 6);

    assertEquals(11, summed);
}

6. Collections

The Vavr team has put a lot of effort in designing a new collections API that meets the requirements of functional programming i.e. persistence, immutability.

Java collections are mutable, making them a great source of program failure, especially in the presence of concurrency. The Collection interface provides methods such as this:

interface Collection<E> {
    void clear();
}

This method removes all elements in a collection(producing a side-effect) and returns nothing. Classes such as ConcurrentHashMap were created to deal with the already created problems.

Such a class does not only add zero marginal benefits but also degrades the performance of the class whose loopholes it is trying to fill.

With immutability, we get thread-safety for free: no need to write new classes to deal with a problem that should not be there in the first place.

Other existing tactics to add immutability to collections in Java still create more problems, namely, exceptions:

@Test(expected = UnsupportedOperationException.class)
public void whenImmutableCollectionThrows_thenCorrect() {
    java.util.List<String> wordList = Arrays.asList("abracadabra");
    java.util.List<String> list = Collections.unmodifiableList(wordList);
    list.add("boom");
}

All the above problems are non-existent in Vavr collections.

To create a list in Vavr:

@Test
public void whenCreatesVavrList_thenCorrect() {
    List<Integer> intList = List.of(1, 2, 3);

    assertEquals(3, intList.length());
    assertEquals(new Integer(1), intList.get(0));
    assertEquals(new Integer(2), intList.get(1));
    assertEquals(new Integer(3), intList.get(2));
}

APIs are also available to perform computations on the list in place:

@Test
public void whenSumsVavrList_thenCorrect() {
    int sum = List.of(1, 2, 3).sum().intValue();

    assertEquals(6, sum);
}

Vavr collections offer most of the common classes found in the Java Collections Framework and actually, all features are implemented.

The takeaway is immutability, removal of void return types and side-effect producing APIs, a richer set of functions to operate on the underlying elements, very short, robust and compact code compared to Java’s collection operations.

A full coverage of Vavr collections is beyond the scope of this article.

7. Validation

Vavr brings the concept of Applicative Functor to Java from the functional programming world. In the simplest of terms, an Applicative Functor enables us to perform a sequence of actions while accumulating the results.

The class vavr.control.Validation facilitates the accumulation of errors. Remember that, usually, a program terminates as soon as an error is encountered.

However, Validation continues processing and accumulating the errors for the program to act on them as a batch.

Consider that we are registering users by name and age and we want to take all input first and decide whether to create a Person instance or return a list of errors. Here is our Person class:

public class Person {
    private String name;
    private int age;

    // standard constructors, setters and getters, toString
}

Next, we create a class called PersonValidator. Each field will be validated by one method and another method can be used to combine all the results into one Validation instance:

class PersonValidator {
    String NAME_ERR = "Invalid characters in name: ";
    String AGE_ERR = "Age must be at least 0";

    public Validation<Seq<String>, Person> validatePerson(
      String name, int age) {
        return Validation.combine(
          validateName(name), validateAge(age)).ap(Person::new);
    }

    private Validation<String, String> validateName(String name) {
        String invalidChars = name.replaceAll("[a-zA-Z ]", "");
        return invalidChars.isEmpty() ? 
          Validation.valid(name) 
            : Validation.invalid(NAME_ERR + invalidChars);
    }

    private Validation<String, Integer> validateAge(int age) {
        return age < 0 ? Validation.invalid(AGE_ERR)
          : Validation.valid(age);
    }
}

The rule for age is that it should be an integer greater than 0 and the rule for name is that it should contain no special characters:

@Test
public void whenValidationWorks_thenCorrect() {
    PersonValidator personValidator = new PersonValidator();

    Validation<List<String>, Person> valid = 
      personValidator.validatePerson("John Doe", 30);

    Validation<List<String>, Person> invalid = 
      personValidator.validatePerson("John? Doe!4", -1);

    assertEquals(
      "Valid(Person [name=John Doe, age=30])", 
        valid.toString());

    assertEquals(
      "Invalid(List(Invalid characters in name: ?!4, 
        Age must be at least 0))", 
          invalid.toString());
}

A valid value is contained in a Validation.Valid instance, a list of validation errors is contained in a Validation.Invalid instance. So any validation method must return one of the two.

Inside Validation.Valid is an instance of Person while inside Validation.Invalid is a list of errors.

8. Lazy

Lazy is a container which represents a value computed lazily i.e. computation is deferred until the result is required. Furthermore, the evaluated value is cached or memoized and returned again and again each time it is needed without repeating the computation:

@Test
public void givenFunction_whenEvaluatesWithLazy_thenCorrect() {
    Lazy<Double> lazy = Lazy.of(Math::random);
    assertFalse(lazy.isEvaluated());
        
    double val1 = lazy.get();
    assertTrue(lazy.isEvaluated());
        
    double val2 = lazy.get();
    assertEquals(val1, val2, 0.1);
}

In the above example, the function we are evaluating is Math.random. Notice that, in the second line, we check the value and realize that the function has not yet been executed. This is because we still haven’t shown interest in the return value.

In the third line of code, we show interest in the computation value by calling Lazy.get. At this point, the function executes and Lazy.evaluated returns true.

We also go ahead and confirm the memoization bit of Lazy by attempting to get the value again. If the function we provided was executed again, we would definitely receive a different random number.

However, Lazy again lazily returns the initially computed value as the final assertion confirms.

9. Pattern Matching

Pattern matching is a native concept in almost all functional programming languages. There is no such thing in Java for now.

Instead, whenever we want to perform a computation or return a value based on the input we receive, we use multiple if statements to resolve the right code to execute:

@Test
public void whenIfWorksAsMatcher_thenCorrect() {
    int input = 3;
    String output;
    if (input == 0) {
        output = "zero";
    }
    if (input == 1) {
        output = "one";
    }
    if (input == 2) {
        output = "two";
    }
    if (input == 3) {
        output = "three";
    }
    else {
        output = "unknown";
    }

    assertEquals("three", output);
}

We can suddenly see the code spanning multiple lines while just checking three cases. Each check is taking up three lines of code. What if we had to check up to a hundred cases, those would be about 300 lines, not nice!

Another alternative is using a switch statement:

@Test
public void whenSwitchWorksAsMatcher_thenCorrect() {
    int input = 2;
    String output;
    switch (input) {
    case 0:
        output = "zero";
        break;
    case 1:
        output = "one";
        break;
    case 2:
        output = "two";
        break;
    case 3:
        output = "three";
        break;
    default:
        output = "unknown";
        break;
    }

    assertEquals("two", output);
}

Not any better. We are still averaging 3 lines per check. A lot of confusion and potential for bugs. Forgetting a break clause is not an issue at compile time but can result in hard-to-detect bugs later on.

In Vavr, we replace the entire switch block with a Match method. Each case or if statement is replaced by a Case method invocation.

Finally, atomic patterns like $() replace the condition which then evaluates an expression or value. We also provide this as the second parameter to Case:

@Test
public void whenMatchworks_thenCorrect() {
    int input = 2;
    String output = Match(input).of(
      Case($(1), "one"), 
      Case($(2), "two"), 
      Case($(3), "three"),
      Case($(), "?"));
 
    assertEquals("two", output);
}

Notice how compact the code is, averaging only one line per check. The pattern matching API is way more powerful than this and can do more complex stuff.

For example, we can replace the atomic expressions with a predicate. Imagine we are parsing a console command for help and version flags:

Match(arg).of(
    Case($(isIn("-h", "--help")), o -> run(this::displayHelp)),
    Case($(isIn("-v", "--version")), o -> run(this::displayVersion)),
    Case($(), o -> run(() -> {
        throw new IllegalArgumentException(arg);
    }))
);

Some users may be more familiar with the shorthand version (-v) while others, with the full version (–version). A good designer must consider all these cases.

Without the need for several if statements, we have taken care of multiple conditions. We will learn more about predicates, multiple conditions, and side-effects in pattern matching in a separate article.

10. Conclusion

In this article, we have introduced Vavr, the popular functional programming library for Java 8. We have tackled the major features that we can quickly adapt to improve our code.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – LS – NPI (cat=Java)
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)