33

In one to many JPA associations is it considered a best practice to initialize relationships to empty collections? For example.

@Entity
public class Order { 

   @Id
   private Integer id;

   // should the line items be initialized with an empty array list or not?
   @OneToMany(mappedBy="order")
   List<LineItem> lineItems = new ArrayList<>();

}

In the above example is it better to define lineItems with a default value of an empty ArrayList or not? What are the pros and cons?

Abdullah Khan
  • 12,010
  • 6
  • 65
  • 78
ams
  • 60,316
  • 68
  • 200
  • 288

4 Answers4

32

JPA itself doesn't care whether the collection is initialized or not. When retrieving an Order from the database with JPA, JPA will always return an Order with a non-null list of OrderLines.

Why: because an Order can have 0, 1 or N lines, and that is best modeled with an empty, one-sized or N-sized collection. If the collection was null, you would have to check for that everywhere in the code. For example, this simple loop would cause a NullPointerException if the list was null:

for (OrderLine line : order.getLines()) {
    ...
}

So it's best to make that an invariant by always having a non-null collection, even for newly created instances of the entity. That makes the production code creating new orders safer and cleaner. That also makes your unit tests, using Order instances not coming from the database, safer and cleaner.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
2

I would also recommend using Guava's immutable collections, e.g.,

import com.google.common.collect.ImmutableList;
// ...
@OneToMany(mappedBy="order")
List<LineItem> lineItems = ImmutableList.of();

This idiom never creates a new empty list, but reuses a single instance representing an empty list (the type does not matter). This is a very common practice of functional programming languages (Scala does this too) and reduces to zero the overhead of having empty objects instead of null values, making any efficiency argument against the idiom moot.

Giovanni Botta
  • 9,626
  • 5
  • 51
  • 94
  • 4
    In the case of JPA entities, however, I would argue that you should initialize with a _mutable_ list, make the field private and only provide a public getter for your collection. Only this way can you be sure, that `lineItems` is never `null`. I wouldn't worry about creating empty lists, because modern JVMs are quite good with garbage collecting. As a general note: Never pre-optimize your code! – Stefan Haberl May 23 '17 at 14:09
  • I agree with @StefanHaberl, you should not initialize entity field with an immutable collection. This will make creating new instances of entity and testing difficult. If you are worrying about performance you may try something like `new HashSet<>(0)` (https://stackoverflow.com/questions/18076492/why-initialize-hashset0-to-zero). – csharpfolk Jul 29 '17 at 15:37
  • I find `Collections.emptyList()` more understandable than `ImmutableList.of()` – fbastien Jun 19 '23 at 10:09
2

I would rather prefer an utility like this:

public static <T> void forEach(Collection<T> values, Consumer<T> consumer) {
  if (values != null) values.stream().forEach(consumer);
}

and use it in code like:

Utils.forEach(entity.getItems(), item -> {
    // deal with item
});
  • I disagree: looping over the lines is only one of the many possible operations that can be done on that list. Going that way, you would have to write a utility for each possible operation on the list! And even if you reuse existing utilities to avoid reinventing the wheel, nothing guarantees that other devs will use that utility when performing operations on the list. If you don't expose a setter on that list attribute, or if you use `@NonNull` annotations, you can ensure a `NullPointerException` could never be throw. – fbastien Jun 19 '23 at 10:08
2

My suggestion would be to not initialize them.

We ran into a situation where we initialized our collections, then retrieved same entity essentially twice successively. After the second retrieve, a lazy loaded collection that should have had data was empty after calling its getter. If we called the getter after the first retrieve, on the other hand, the collection did load the data. Theory is that the second retrieve got a managed entity from the session that had its collection initialized to empty and appeared to already be loaded or appeared to be modified, and therefore no lazy load took place. Solution was to NOT initialize the collections. This way we could retrieve the entity multiple times in the transaction and have its lazy loaded collections load correctly.

One more item to note: in a different environment, the behavior was different. The collection was lazy loaded just fine when calling the collection's getter on the entity that was retrieved the second time in the same transaction.

Unfortunately I don't have information on what was different between the two environments. It appears - although we didn't prove it 100% and didn't identify the implementations - that different JPA implementations work differently with respect to initialized collections.

We were using hibernate - just don't know which version we were using on each of the two platforms.

Fletcher72
  • 41
  • 5