3 Libraries Every Java Developer Should Be Using

Estimated reading time: 9 minutes

1873 words

If there was one piece of advice I could give to junior engineers it would be "less is more".

In fact:

The more code you have, the worse off you are. Surprised? Don't be.

Think about it:

More code means more places for bugs to hide.

It means more lines of code to maintain, and understand.

And when you do have a bug, extra code cripples your time to resolution as you pore over all the different codepaths.

Here's the kicker:

In today's fast-paced market, customers have very low tolerance for outages. They will switch providers without a second thought.

So what's a quick win you can employ to keep your codebase refined and elegant, enabling you to ship with high confidence?

Simple. Import a few key libraries to reduce boilerplate and improve your unit test suite.

In today's post I'm going to share 3 high leverage Java libraries that will give you a lot of bang for the buck. They'll help you reduce redundant boilerplate, and help you step up the quality of your unit tests so you can ship value quicker, and more confidently to your users.

My Top Library

As Java guys, we know how verbose the language can be. Sometimes it feels like every other line of code can be boilerplate.

Let's be honest:

How many getters and setters have you written in your career?

How long does it take you to implement the Builder pattern for your classes?

And don't get me wrong - I'm not saying you don't need these. After all, these are patterns that help you write idiomatic Java by following principles like data encapsulation.

But really:

Sometimes it feels like Java just gets in your way even when you're working on small tasks. For example, if you're working on a class with a large number of constructor parameters, the "Java" way to solve it is with a builder.

But now you have to go stand up an inner static class, add the private fields, yada-yada, do some refactoring in the outer class and now you have the Builder set up. Great. Now that all the boilerplate's been written I can finally start working on my task.

Not exactly "moving fast" is it?

Lombok

Luckily enough, there's a library that can generate a lot of this Java boilerplate. It's called Lombok. Here's how you might use it on a DTO:

@Getter
@Setter
public class Foo {
  private String aData;
  private int anotherData;
  private int moreData;
}

With 2 annotations, all your getX and setX methods are generated for you at build time. This is also much more concise and readable.

Moving on:

I love the Builder pattern. The fluent chaining syntax flows incredibly well for Java. It's also useful for writing unit tests when you want to create test objects with certain data.

But it's an immense time-suck to write a Builder from scratch.

Think about it:

You're going to be adding a static class with duplicate fields and methods for every one of your classes? Of course not.

You're going to use the @Builder annotation so you can do this:

@Builder
public class Foo {
  private String aData;
  private int anotherData;
  private int moreData;
}

Because you want to use a builder like this:

Foo f = Foo.builder().aData("foo").anotherData(10).build(); 

without having to do this:

public class Foo {

  private String aData;
  private int anotherData;
  private int moreData;

  private Foo () { }

  public static class Builder {
      private String aData;
    private int anotherData;
      private int moreData;

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

    public Builder anotherData(int anotherData) {
      this.anotherData = anotherData;
      return this;
    }

    public Builder moreData(int moreData) {
      this.moreData = moreData;
      return this;
    }

    public Foo build() {
      Foo f = new Foo();
      f.aData = this.aData;
      f.anotherData = this.anotherData;
      f.moreData = this.moreData;

      return f;
    }
  }

}

You just replaced 25 lines of boilerplate with a single annotation. This is obviously much more readable and elegant than the handwritten solution.

And because you actually spend more time reading code than you do writing it, this has double the benefit of saving you time not only when you first add the builder, but the second time around, when you read the code ("Oh I see the @Builder annotation, great, moving on…")

At any rate:

Here's my top 4 most common annotations that I find myself using in my day-to-day:

  1. @Data
  2. @Value
  3. @Builder
  4. @Slf4j

Now, you'll save a lot of time by using the Lombok library. But you'll save even more time with this next library.

Mindless Mappings

One of the hallmarks of a clean design is n-tier architecture, which means separating your application into logical tiers of shared functionality.

The most common form of n-tier architecture "in the wild" consists of 3 tiers:

  1. The presentation layer
  2. The domain layer
  3. The data layer

Doing this keeps the logic of each layer laser focused on a single responsibility. This is necessary to keep the domain of the application honed in on solving the core problem.

Something to point out is that each layer should know as little as possible about the layer above it. Preferably nothing at all.

But there's the rub: "How do you map data from the presentation down to the data layer, and how do you map data from the data layer back up to the presentation layer?"

A common pattern you'll see here is using Transformers to map a DTO object from the presentation layer to the domain layer and vice-versa.

The problem?

These transformers can grow into some gnarly beasts.

If you expect large payloads from your clients, your transformers start turning into massive, lumbering behemoths. At the beginning of its lifespan, with just a few attributes, it's easy remembering all the attributes. But once they start growing, you'd be surprised at how easy it is to forget one critical attribute with multiple downstream effects.

Not only is this prone to errors with disastrous consequences, but it becomes tedious updating and removing mapping logic.

MapStruct

In order to get around this, I recommend using MapStruct.

It generates transformers for you at build time so you never have to write logic to map objects from one layer to another.

Let's go over a concrete example:

Suppose you have a Person class in your domain layer, and a PersonDto in your presentation layer. Now, Person has some very domain layer specific attributes that have no business belonging in the presentation layer, so PersonDto contains just a subset of Person's attributes.'

In order to have MapStruct generate the mapping logic for you, just create a simple PersonMapper with the @Mapper annotation:

@Mapper
public interface PersonMapper {
  PersonDto personToPersonDto(Person person);
}

And just like that, your mapper is done. Here's what a regular solution might look like:

public class PersonMapper {
  public PersonDto personToPersonDto(Person person) {
    PersonDto dto = new PersonDto();
    dto.setFirstName(person.getFirstName());
    dto.setLastName(person.getLastName());
    dto.setAge(person.getAge());
    dto.setEyeColor(person.getEyeColor());
    dto.setHairColor(person.getHairColor());
    dto.setShoeSize(person.getShoeSize());
    dto.setHeight(person.getHeight());

    return dto;
  }
}

Now, let me ask you:

Which one is more maintainable? What if you add another field to Person that you want to show up in PersonDto? You'd have to add it to 3 classes. With MapStruct, you'd only have to add it to two 2 classes.

What if you're going the reverse way from presentation to domain? From PersonDto to Person? That's yet another method: public Person personDtoToPerson(PersonDto person). Don't forget: Make sure you also add the new field in the reverse direction, or you'll end up with a null value in your persistence layer.

Think it's a over-optimization? Nope.

When you're working on a task, chances are you've loaded quite a few things into your brain's short term memory. The closer you get to The Magic Number 7, Plus Or Minus 2, the more likely you are to sneak a bug in.

So free up some space in your brain's short-term memory. Quit doing it by hand, and just use MapStruct.

Making A Mockery

This last library doesn't do so much in terms of code generation as much as it has to do with helping you unit test your application at the boundaries.

Now, when I talk about the boundaries of your application, I mean one of two things. Either an entry point in your presentation layer (an external client requests some data from your REST endpoints), or an exit point in your domain layer (you make an external network request or database lookup).

This library helps specifically in the use case where you want help testing external network requests.

WireMock

WireMock fulfills a very necessary requirement of thoroughly testing code that makes network requests. Oftentimes it's very easy to test your presentation layer or your domain layer with tools like Mockito. So, if your services depend on a Client interface, you can easily mock it.

But what about when you need to test the concrete implementation? For example, if I have an interface GithubClient, I can easily mock that in my GitHubServiceTest. But how do I actually go about actually testing my GithubClientImpl?

That's where you can use WireMock in your JUnit tests. You configure the URL's, the status code, and the response body, and WireMock will spin up an HTTP server for you.

Here's a concrete example:

@Test
public void myTest() {
  stubFor(get(urlEqualTo("/a/path"))
       .willReturn(
         aResponse()
         .withStatusCode(200)
         .withBody("{\"key\": \"value\"}")));

  int responseCode = myClient.get("/a/path").statusCode();
  assertEquals(responseCode, 200);
}

WireMock is a great library because it allows us to write robust code. Not only does it allow you to test the happy path flow, but also any sad path flows. What happens upstream requests start returning 500's?

Are you handling the failure gracefully and returning an empty response? Well now you can test it.

Or have you adopted a retry with back off strategy? Well now you can ensure it works with WireMock's verify(3, postRequestedFor(urlEqualTo("/a/path")))

Or maybe you really do want to fail with an exception. Is your client throwing an exception with a helpful message? Or is it throwing an unreadable NPE?

WireMock works incredibly for testing your application at the seams. Personally, I have found it to be indispensable over the course of my career.

Check These Libraries Out Today!

Obviously, building robust systems is a multi-faceted problem. Using these 3 libraries won't be a silver bullet that solves all your issues.

But here's the deal:

Building a robust system is a lot of little things done right. And one of those little things is picking the right libraries to speed up development to free up more cycles for iteration and decreasing time to market.

And in today's fast-paced market, why wouldn't you go for some of the low hanging fruit?

Hopefully I've given you enough to whet your appetite. But you'll need to evaluate whether or not these libraries fit your use case. As with all things in life, YMMV depending on your existing tech stack and the prevailing architecture at your company.

All I can say is, I've found these 3 Java libraries incredibly useful in my career.

Did I leave something out? Is there a library that you can't live without? Let me know in the comments below!

back