Enter Project Lombok

Happy new year one and all!

We know we’re a little bit later than we promised and for this we are truly sorry. We have, however, got a corker of a blog post for you!

So, yes, we had a great Christmas and a wonderful new year’s eve, we hope you did too. But without further ado we’ll jump straight into our first post of 2017.

We write quite a bit of Java here and as we all know, Java has a tendency to be somewhat, er, verbose…Consider the following situation – A server which accepts and stores and returns a news post. For simplicity’s sake this server only stores one (the latest) news post at any given time. The interface to the server looks like this:

public interface Server {

    JsonNode get();

    void post(JsonNode data);
}

This news posts stored on the server can be accessed by using Sender:

public interface Sender {

    void send(JsonNode data) throws Exception;
}

and Receiver interfaces:

public interface Receiver {

    JsonNode receive() throws Exception;
}

The ‘TransmissionService’ is where the serialising/deserialising of our model (POJO) class actually happens. Our model class is as follows:

@JsonDeserialize(builder=NewsPost.Builder.class)
public class NewsPost {

    private final UUID creatorId;
    private final List<UUID> recipientIds;
    private final String text;

    private NewsPost(UUID creatorId, List<UUID> recipientIds, String text) {
        this.creatorId = creatorId;
        this.recipientIds = recipientIds;
        this.text = text;
    }

    public UUID getCreatorId() {
        return creatorId;
    }

    public List<UUID> getRecipientIds() {
        return recipientIds;
    }

    public String getText() {
        return text;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("creatorId", creatorId)
                .append("recipientIds", recipientIds)
                .append("text", text)
                .toString();
    }

    public static class Builder {

        private UUID creatorId;
        private List<UUID> recipientIds;
        private String text;

        public Builder() {}

        public Builder(UUID creatorId, List<UUID> recipientIds, String text) {
            this.creatorId = creatorId;
            this.recipientIds = recipientIds;
            this.text = text;
        }

        public Builder withCreatorId(UUID creatorId) {
            this.creatorId = creatorId;
            return this;
        }

        public Builder withRecipientIds(List<UUID> recipientIds) {
            this.recipientIds = recipientIds;
            return this;
        }

        public Builder withText(String text) {
            this.text = text;
            return this;
        }

        public NewsPost build() {
            return new NewsPost(creatorId, recipientIds, text);
        }
    }
}

And the transmission service:

public class TranmissionService {

    private static final Logger LOGGER = LoggerFactory.getLogger(TranmissionService.class);

    private final Sender sender;
    private final Receiver receiver;
    private final ObjectMapper mapper;

    public TranmissionService(Sender sender, Receiver receiver, ObjectMapper mapper) {
        this.sender = sender;
        this.receiver = receiver;
        this.mapper = mapper;
    }

    public void run() throws Exception {
        LOGGER.info("Starting {}", TranmissionService.class);

        NewsPost newsPost = new NewsPost.Builder()
                .withCreatorId(UUID.randomUUID())
                .withRecipientIds(Collections.singletonList(UUID.randomUUID()))
                .withText("This is an exciting news post!")
                .build();

        sender.send(serialize(newsPost));

        NewsPost receivedNewsPost = deserialize(receiver.receive());

        LOGGER.info(receivedNewsPost.toString());
        LOGGER.info("Creator Id: {}", receivedNewsPost.getCreatorId());
        LOGGER.info("Text: {}", receivedNewsPost.getText());
    }

    private JsonNode serialize(NewsPost newsPost) {
        return mapper.valueToTree(newsPost);
    }

    private NewsPost deserialize(JsonNode jsonNode) throws JsonProcessingException {
        return mapper.treeToValue(jsonNode, NewsPost.class);
    }
}

This is a pretty contrived example we agree. What we’re trying to show here is the amount of boilerplate code that has to be written just to send three fields to a server to inform it of a news post being created, almost 80 lines in fact!

So, surely we need all this to achieve the required outcome of data transfer we hear you shout..isn’t this ‘Just Java’ goes the incantation. Yes, it sure used to be, but enter Project Lombok. Lombok is a fantastic library that generates all that boiler plate for you.

So, how does it work? Well, first of all you need to add the dependency, we’re using Maven which looks like this:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.12</version>
    <scope>provided</scope>
</dependency>

If you’re using Gradle or other build tools, have a look here.

Hold up there cowboy. It’s not quite as simple as just installing the dependency, you need to tell your IDE what the hell is going on too…We’re using IntelliJ but the instructions should be similar for Eclipse and others.

First enable annotation processing…

Preferences (Ctrl + Alt + S)

  • Build, Execution, Deployment
    • Compiler
      • Annotation Processors
        • Enable annotation processing

And then install the Lombok plugin…

  • ‘Preferences’ and then ‘Plugins’
  • Search for “Lombok Plugin”
  • Click Browse repositories…
  • Choose Lombok Plugin
  • Install
  • Restart IntelliJ

Once your IDE has restarted you’re good to go!

So, back to the story…remember that NewsPost POJO that was almost 80 lines long, well, delete it. Delete it all. Well maybe not all of it, just leave this much:

public class NewsPost {

    private final UUID creatorId;
    private final List<UUID> recipientIds;
    private final String text;
}

Great! but with some compile errors…So how do we fix them? Simple! Change the class definition from:

public class NewsPost {

To:

public @Data class NewsPost {

And import lombok.data.

Voila! If you look at the class members now in the IDE panel you should see that Lombok has very kindly generate our getters, setters, constructor and toString methods, among other things! How cool is that?

To complete the class we still need a builder for it though, but fortunately that’s just as easy…

@Builder
@JsonDeserialize(builder=NewsPost.NewsPostBuilder.class)
public @Data class NewsPost {

Lombok generates a slightly different implementation of the builder pattern so we need to change our creation of our NewsPost object in the TransmissionService to this:

NewsPost newsPost = NewsPost.builder()
        .creatorId(UUID.randomUUID())
        .recipientIds(Collections.singletonList(UUID.randomUUID()))
        .text("This is an exciting news post!")
        .build();

And we’re just about there! So, the full NewsPost class now looks like this:

@Builder
@JsonDeserialize(builder=NewsPost.NewsPostBuilder.class)
public @Data class NewsPost {

    private final UUID creatorId;
    private final List<UUID> recipientIds;
    private final String text;

    @JsonPOJOBuilder(withPrefix = "")
    public static final class NewsPostBuilder {}
}

Note the NewsPostBuilder inner class – this isn’t necessary for Lombok to generate our builder but without it and its @JsonPOJOBuilder annotation the Jackson object mapper won’t be able to deserialise the JsonNode back into the NewsPost POJO.

There’s a load more to Lombok, some really cool features and some we aren’t entirely convinced about; which ones you use are up to you! But remember…with great power comes great responsibility…

~23Squared