Lombok addresses the main ergonomic issue with Java, which is writing code that is necessary, but not necessarily worth the time to write it. I’m referring to setters, getters, builders, and logging utilities. Lombok reduces the number of lines you have to write and lets you focus on better problems. Lombok is like the poor man’s Kotlin.
The next big target is validation. It’s one thing to have a POJO structure and populate it with data, but you still need to guarantee that data is in the correct format. Traditionally, we would write a util method with a lot of ifs and elses, making sure no strings are null and all dates fall within this century. Just like with getters and setters, this is a crucial aspect of any project, but one that is routine - almost standardised - and again we lose time by having to write these rules by hand.
Before we get to the code, let me stress that this is not the best way to do things. In fact, there is no best way to do anything. There’s only what works for you, your team, and your project. These techniques will get you 90% of the way, but no library saves us from dealing with special cases.
Constraints and Validation
Validation is easy to add to existing projects, so we will start with that.
Let’s write an Article
. Our requirements are simple: We need the text
body, creation date, author, and tags.
public class Article {
private final String body;
private final String author;
private final LocalDateTime createdAt;
private final List<String> tags;
}
Our validation rules are:
- Fields must not be null
- String fields must not be empty
- Creation date must not be in the future
Simple, right? Let’s introduce javax.utils.validation
:
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PastOrPresent;
public class Article {
@NotBlank
private final String body;
@NotBlank
private final String author;
@PastOrPresent
private final LocalDateTime createdAt;
@NotNull
private final List<String> tags;
}
We’re almost there. There’s one detail we haven’t addressed:
@NotNull
private final List<String> tags;
The @NotNull
constraint applies to the tags
object, but not its contents.
We can fix this like so:
@NotNull
private final List< @NotBlank String> tags;
All good. We’ve written our POJO, added the necessary constraints, and now it’s time to validate it.
We will do this in a separate Utils
class.
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
public class Utils {
public static final boolean validate(Article obj) {
final Validator validator = Validation
.buildDefaultValidatorFactory()
.getValidator();
// Validate object and print errors to stderr
return obj != null && !validator.validate(obj)
.stream()
// in the code repo the error message is properly formatted
.map(ConstraintViolation::getMessage)
.peek(System.err::println)
.findAny()
.isPresent(); // No constraint violations equals a happy object
}
}
The method Validator#validate
returns a Set<ConstraintViolation<T>>
.
This is populated with error messages if any constraints are broken. So,
if the returned set is empty, validation was successful.
Built-In Constraints
@NotNull
Annotated property value is not null@NotEmpty
Annotated property value is not null or empty@NotBlank
Annotated String property value is not null or whitespace@Email
Annotated property is a valid email address@AssertTrue
,@AssertFalse
Annotated property value is true or false@Size(min="0", max="10")
Annotated property value has a size between min and max; can be applied to String, Collection, Map, and arrays@Min
Annotated property has a value no smaller than min@Max
Annotated property has a value no larger than max@Positive
,@PositiveOrZero
Used with numeric properties@Negative
,@NegativeOrZero
Used with numeric properties@Past
,@PastOrPresent
Used with date properties@Future
,@FutureOrPresent
Used with date properties
There are many more constraints available, for example, @DurationMin
,
@Currency
, @CreditCardNumber
to name a few. You can create custom
constraints to deal with special cases, or if you’re feeling particularly
lazy, just do Javascript:
public class Car {
@ParameterScriptAssert(lang = "javascript", script = "luggage.size() <= passengers.size() * 2")
public void load(List<Person> passengers, List<PieceOfLuggage> luggage) {
//...
}
}
That covers the validation aspect of POJOS. Let’s have a look at Lombok and how it can help us.
Lombok
Our Article
has all the necessary fields, but no setters, getters or constructors. Let’s fix that.
import lombok.EqualsAndHashCode;
import lombok.Getter;
@Getter //All fields now have getters
@EqualsAndHashCode //Let Lombok generate an equals() method
public class Article {
// ...
}
Now we can get the data, but we can’t set them. All Article
fields
are final
so we can’t use @Setter
here. We have to either write
a constructor, or write a builder, or both. Lombok can do this for us.
import lombok.Builder;
import lombok.RequiredArgsConstructor;
@Getter //All fields now have getters
@Builder
@RequiredArgsConstructor
@EqualsAndHashCode //Let Lombok generate an equals() method
public class Article {
// ...
}
If we use @AllArgsConstructor
or @RequiredArgsConstructor
, we have to
pay attention to the order in which we declared our fields. Arguments
in the generated constructor will be in the same order. For example:
@AllArgsConstructor // Generate constructor that populates all fields
public class Foo {
private int max;
private Bar bar;
private int min;
//Constructor generated by Lombok. Argument order is the same as declaration order.
public Foo(int max, Bar bar, int min);
}
@Builder // Generate a Builder for this class
@RequiredArgsConstructor // Generate constructor that populates required fields only
public class Bar {
private String name;
private int age;
// Generated by Lombok. No fields are final, so we have no "required" arguments.
public Bar();
}
Note that Foo
uses @AllArgsConstructor
and Bar
uses @RequiredArgsConstructor
.
For that reason, since Bar
has no final
fields, the generated constructor will
have no arguments.
Choosing between constructors and builders is largely a matter of preference. We can even have both. For small objects I don’t bother with builders at all.
Getting the Code Generated by Lombok
To see the de-lomboked output, you can either use the command line or have Maven do it for you.