Ignoring Lombok Code in Jacoco

Update 15. January 2018: Jacoco 0.8.0 has been released. No need to build it from the SNAPSHOT version anymore.

Introduction

Test Coverage is a code metric that indicates how many lines of code, as a percent of the total, your tests execute. It can’t tell you anything about the quality of your tests, but it nevertheless is one of the most important metrics in use. Jacoco is one of the most prominent test coverage tools for Java.

Lombok is a Java library that generates common boilerplate code like getter/setter methods, hashCode, and builder classes during the compilation phase. This improves development speed significantly.

The Problem

Lombok causes problems when your project requires a minimum test coverage rate that is also checked by a CI System such as Jenkins or Travis. Jacoco can’t distinguish between Lombok’s generated code and the normal source code. As a result, the reported coverage rate drops unrealistically low. You the developer are left with two options:

  1. Write unit tests for generated code or
  2. Decrease the required coverage rate.

Neither option makes sense and neither is desirable.

The Solution

Luckily, beginning with version 0.8.0, Jacoco can detect, identify, and ignore Lombok-generated code. The only thing you as the developer have to do is to create a file named lombok.config in your directory’s root and set the following flag:

lombok.addLombokGeneratedAnnotation = true

This adds the annotation lombok.@Generated to the relevant methods, classes and fields. Jacoco is aware of this annotation and will ignore that annotated code.

Please keep in mind that you require at least version 0.8.0 of Jacoco and v1.16.14 of Lombok.

Showcase

Let’s suppose we have a Person class that contains fields for first- and lastname. We are using @Data which will generate the getter/setters, hashCode, toString and equals methods. We also use @Builder which generates - as the name says - a builder pattern for instantiating an object.

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class Person {
  private String firstname;
  private String lastname;
}

Then we have a PersonPrinter class that contains logic for printing a Person. We annotate it with @Log to instantiate a static logger and again @Data:

import lombok.Data;
import lombok.extern.java.Log;

@Log
@Data
public class PersonPrinter {
  private Person person;
  private String separator = " ";
  private String noLastnameLog = "That person has no name";

  public PersonPrinter(Person person) {
    this.person = person;
  }
  public String toString() {
    if ("".equals(person.getLastname())) {
      log.info(noLastnameLog);
      return "";
    }

    return String.format(person.getFirstname() + this.separator + 
      person.getLastname());
  }
}

The only logic which should be tested is PersonPrinter. The following two test cases should actually give us 100% test coverage:

public class PersonPrinterTest {
  @Test
  public void testDefault() {
    Person harrison = Person.builder()
      .firstname("John").lastname("Harrison").build();

    assertEquals("John Harrison",
      new PersonPrinter(harrison).toString());
  }

  @Test
  public void testNoLastname() {
    Person anonymous = Person.builder()
      .firstname("anonymous").lastname("").build();

    assertEquals("", new PersonPrinter(anonymous).toString());
  }
}

Unfortunately, this is not the case since Jacoco also counts the generated code from Lombok:

Jacoco Code Coverage Report including Lombok
Jacoco Code Coverage Report including Lombok

By adding the flag lombok.addLombokGeneratedAnnotation = true before cleaning and running the tests again, we see that Jacoco has completely ignored the class Person and shows us 100% test coverage:

Jacoco Code Coverage Report excluding Lombok
Jacoco Code Coverage Report excluding Lombok

As always the source code is available on GitHub.  It contains support for both Maven and Gradle.

Further Reading

13 Replies to “Ignoring Lombok Code in Jacoco”

      1. Thanks Rainer!

        What kind of nuances go into making this work for a multi-module project [gradle]? I’ve been running into issues related to “Classes in bundle “” do not match with execution data” which I believe might be related to using a different Java version but I’m not sure.

        Please let me know if you intend to update your project to handle multi module project builds (not all subprojects may have tests).

        Again, great page! I’m definitely pointing people here for this issue. Thanks a lot!

        1. Hi Yash,

          if I understand you correctly, you have a “simple” multi-module gradle project, where you get a “classes in bundle – do not match with execution data” error when running Jacoco?

          Have you tried to use the same Java version? If not, please try that and if it is still not working I will look into it.

    1. Unfortunately that’s not possible yet.

      Jacoco’s code removal is done in the report generation part, which comes after creating the raw output in form of an executable (jacoco.exec). Sonarqube and similar take that raw output and create their own reports. So we will have to wait until Jacoco 0.7.10 is released and Sonarqube makes the required adaptions.

      You can find more on: https://github.com/jacoco/jacoco/pull/513#issuecomment-293176354

    1. Hi, this feature is very Lombok specific. I am currently investigating options for a more general approach. Will inform you, when I have something.

  1. The Immutables library adds the ‘javax.annotation.Generated’ annotation to the generated code if that is helpful. It appears the Lombok specific solution was being achieved via an annotation so hopefully something similar can be done here. I am also interested in some sort of generalized support for this. Thanks!

Leave a Reply