How using Lombok can create bugs in your code

Recently my colleague Eyal Levy and I were investigating a bug, a strange one.

Our code was very simple:

ComponentInfo value = getComponenet();
List<ComponentSubRule> wSubRules = value.getSubRules();
wSubRules.removeAll(removeSubRules);

In most cases, it works perfect, however, we have one component type that uses a sub-class of ComponentSubRule and in that case, after Collection.removeAll was called, the list was empty!

Collections revisited

Let us remind ourselves how removeAll works: It all boils down to iterating over the list and if the current list item is contained by the other list, it is removed from this list.

So, we can deduce that the Collection.contains always returns true.

Well that’s not good.

Contains is based on the object equals, so this is pretty big indicator to a mistake in the equals implementation.

Back to our code

Let’s look at our ComponentSubRule sub-class:

@Data
@NoArgsConstructor
public class CounterComponentSubRule extends ComponentSubRule {
   private Boolean someBoolean = false;
   private Boolean someOtherBoolean = false;

   public CounterComponentSubRule(String subRuleName, Long subRuleId, Map<Long, String> subRulesColorMap, Boolean someOtherBoolean, Boolean someBoolean) {
      super(subRuleName, subRuleId, subRulesColorMap);
      this.someOtherBoolean = someOtherBoolean;
      this.someBoolean = someBoolean;
   }
}

Okay, we’re using Lombok’s Data annotation, which is in fact a grouping of several annotations to produce:

  • Getters
  • Setters
  • Constructors
  • toString
  • Equals and HashCode
  • Value

Digging into the EqualsAndHashCode annotation reveals that by default calling the super-class’s implementation is turned off, causing our equals implementation to only compare the CounterComponentSubRule’s members.

Changing the class as seen below, fixed the bug.

@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class CounterComponentSubRule extends ComponentSubRule {
   private Boolean someBoolean = false;
   private Boolean someOtherBoolean = false;

   public CounterComponentSubRule(String subRuleName, Long subRuleId, Map<Long, String> subRulesColorMap, Boolean someOtherBoolean, Boolean someBoolean) {
      super(subRuleName, subRuleId, subRulesColorMap);
      this.someOtherBoolean = someOtherBoolean;
      this.someBoolean = someBoolean;
   }
}

So, what we can learn here is that using Lombok for classes using inheritance should be done with proper consideration to all annotation parameters. Lombok is definitely fun, nifty and relieves us from constantly taking care of these method, but understanding how it works will help us avoid this kind of bugs.

But our biggest take here is that unit tests are extremely important as simply testing the original code would have caught this issue before it leaked into the testing environment.

P.S.

Though it is not as critical, be advised that the ToString annotation, which is also included in the Data annotation, also has the option to call the super class, and is also set to false by default.

So I would change the code to:

@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@NoArgsConstructor
public class CounterComponentSubRule extends ComponentSubRule {
   private Boolean someBoolean = false;
   private Boolean someOtherBoolean = false;

   public CounterComponentSubRule(String subRuleName, Long subRuleId, Map<Long, String> subRulesColorMap, Boolean someOtherBoolean, Boolean someBoolean) {
      super(subRuleName, subRuleId, subRulesColorMap);
      this.someOtherBoolean = someOtherBoolean;
      this.someBoolean = someBoolean;
   }
}
Senior Java Developer

Backend Group
Thank you for your interest!

We will contact you as soon as possible.

Send us a message

Oops, something went wrong
Please try again or contact us by email at info@tikalk.com