Use Vavr List and Option with Spring 6 DTOs
In this tutorial, we will override Spring web converters, to support Vavr List and Option in Spring Boot DTOs. The end goal is to be able to write the request DTO code like this:
import com.demo.model.Customer;
import io.vavr.collection.List;
import io.vavr.control.Option;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class CustomerDtoReq {
private Option<String> name;
private Option<Integer> number;
private Option<String> city;
private List<OrderDto> orders;
private Option<MembershipDto> membership;
public Customer toEntity() {
var c = new Customer();
c.setName(name);
c.setNumber(number);
c.setCity(city);
c.setOrders(orders.map(x -> x.toEntity(c)));
c.setMembership(membership.map(MembershipDto::toEntity));
return c;
}
}
Vavr List can be easily mapped (without the whole .stream()
and .collect()
boilerplate code).
Vavr Option is very useful to avoid using Java’s null
and hence avoiding NullPointerException
at runtime.
Compared to Java’s Optional
it has many advantages like the .fold()
method which follows functional programming best practices:
U fold(Supplier<? extends U> ifNone, Function<? super T, ? extends U> f)
To do this, we just need to override the Spring converters in WebMvcConfig
, using the LibCustom
library.
First, add this dependency to your pom.xml
:
<dependency>
<groupId>io.github.jleblanc64</groupId>
<artifactId>lib-custom</artifactId>
<version>1.0.7</version>
</dependency>
Also, if we want the null non-initialized Option
fields to be converted to Option.None
, we need to override some functions from Spring web external libraries,
using the LibCustom
library. In WebMvcConfig
, just call the following code:
import io.github.jleblanc64.libcustom.LibCustom;
import io.github.jleblanc64.libcustom.custom.jackson.VavrJackson2;
import io.github.jleblanc64.libcustom.custom.spring.VavrSpring6;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
VavrJackson2.override(converters);
VavrSpring6.override();
LibCustom.load();
}
}
Then, non initialized null
fields like private List<OrderDto> orders;
are automatically mapped to List.empty()
.
No need to manually specify default value such as: private List<OrderDto> orders = List.empty();
.
Instead, you can forget about it, and just focus on your code logic, and directly map your Entity
fields to your DTO
fields such as:
c.setOrders(orders.map(x -> x.toEntity(c)));
c.setMembership(membership.map(MembershipDto::toEntity));
Also, this whole logic also works for your response DTOs. You can see a full working Spring Boot app that uses these strategies in my Github repo. You may leave a question in the issues if you need.