Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:


1. Overview

In this tutorial, we’ll discuss how to persist a property of type List<String> in JPA. We take a look at the possibilities to realize this, how they differ, and explain the advantages with the help of an example.

2. Example

As a model, we use the entity library, which has an automatically generated ID, a name, a List<String> containing addresses, and a List<String> containing book names:

@Entity(name = "library")
public class Library {

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private List<String> addresses = new ArrayList<>();

    private List<String> books = new ArrayList<>();

    // getter, setter, and constructor

For the List<String>, it would be possible to create a second entity with an id and the string and then annotate this with a OneToMany relationship. We’ll look at two other possibilities with JPA that simplify this behavior.

3. @ElementCollection

The first option is to use @ElementCollection. This annotation allows us to specify the target class of the collection. In our case, this is a String. Furthermore, we can specify if the list should be loaded in a lazy or eager way. The default value is lazy. However, for the sake of simplicity for the examples, we set the value to eager:

@ElementCollection(targetClass = String.class, fetch = FetchType.EAGER)
@CollectionTable(name = "books", joinColumns = @JoinColumn(name = "library_id"))
@Column(name = "book", nullable = false)
private List<String> books = new ArrayList<>();

We can also annotate the method that wants to access the list with @Transactional or the repository method with @Query(“SELECT l FROM library l JOIN FETCH l.books WHERE = (:id)”) to avoid a LazyInitializationException.

The annotations result in the following DDL:

    library_id BIGINT       NOT NULL,
    book      VARCHAR(255) NOT NULL

    name      VARCHAR(255),
    addresses VARCHAR(255)                            NOT NULL,
    CONSTRAINT pk_library PRIMARY KEY (id)

    ADD CONSTRAINT fk_books_on_library FOREIGN KEY (library_id) REFERENCES library (id);

We can see that @CollectionTable sets the name of the second table and the column that references our library table. In addition, the Foreign Key is also created appropriately. So by using @ElementCollection in this method, we save the second entity we would normally need for an OneToMany link.

4. Attribute Converter

Another alternative is to use a converter. For this, we have to implement the generic AttributeConverter with our desired object. In our case, this is the List<String>; the desired format could be a String, for example. In the convertToDatabaseColumn(List<String> stringList) method, the return value is the data type that the object should eventually have in the database, and the parameter is our list.

The convertToEntityAttribute(String string) method, on the other hand, defines how the string from the column is converted back to the List<String>. In our example, we use the character “;” to separate the strings:

public class StringListConverter implements AttributeConverter<List<String>, String> {
    private static final String SPLIT_CHAR = ";";

    public String convertToDatabaseColumn(List<String> stringList) {
        return stringList != null ? String.join(SPLIT_CHAR, stringList) : "";

    public List<String> convertToEntityAttribute(String string) {
        return string != null ? Arrays.asList(string.split(SPLIT_CHAR)) : emptyList();

We also have to add our converter to the field with @Convert:

@Convert(converter = StringListConverter.class)
@Column(name = "addresses", nullable = false)
private List<String> addresses = new ArrayList<>();

Alternatively, we could store the list as a JSON string in the column. When we decide to use the AttributeConverter, we have to keep in mind how big our list will grow since it has to fit in the selected size of the column.

In our case, it must fit into the varchar(255) addresses column. In the ElemenCollection approach, we can have an unlimited number of items in our list, each only limited by the varchar(255)  of the column itself.

5. Comparison 

Next, we’ll create the LibraryRepository and test our solutions:

public interface LibraryRepository extends CrudRepository<Library, Long> {

When we now execute the code, we’ll add the list items to the library entity as usual:

Library library = new Library();
library.setAddresses(Arrays.asList("Address 1", "Address 2"));
library.setBooks(Arrays.asList("Book 1", "Book 2"));;
Library lib = libraryRepository.findById(library.getId().longValue());
System.out.println("lib.getAddresses() = " + lib.getAddresses());
System.out.println("lib.getBooks() = " + lib.getBooks());

We’ll get the following output:

lib.getAddresses() = [Address 1, Address 2]
lib.getBooks() = [Book 1, Book 2]

As we can see, both implementation work as expected and have their own advantages:

Element Collection Converter
Default Fetch Type Lazy Eager
Limit of List Unlimited list items Limited by the column length
Limit of each String Limited by the column length Limited by the number of list items and so also limited by the column length
Table Creates an extra table It doesn’t need its own table

6. Conclusion

In this article, we have discussed what kind of possibilities there are in JPA to store a list of strings of an entity. While we showed where there are possible limitations and what differences there are in the respective possibilities.

As always, the example code is available over on GitHub.

Course – LSD (cat=Persistence)

Get started with Spring Data JPA through the reference Learn Spring Data JPA course:

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

res – Persistence (eBook) (cat=Persistence)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.