Two difficult Java Error/Exception

When I was maintaining the company’s products recently, I encountered two Java anomalies that were a headache. I might forget it later, so I wrote a blog to record these problems and solutions.

Entity definition

Since the company’s code cannot be displayed, I will use objects such as bookstores, books, and authors to illustrate. The relationship between the bookstore and the author is m:n, and the relationship between the author and the book is 1:n. Each object is defined as follows:

import jakarta.persistence.*;
import java.io.Serializable;
import java.util.*;
import org.hibernate.annotations.*;
import org.hibernate.envers.Audited;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Audited
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Entity
@EntityListeners({AuditingEntityListener.class, AuditingEntityListener.class})
@MappedSuperclass
@Table(name = "book_store")
public class BookStore implements Serializable {
  private static final long serialVersionUID = 1L;

  @Id
  @org.springframework.data.annotation.Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(name = "name") private String name;

  @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
  @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
  @JoinTable(name = "book_store_author",
      joinColumns =
          @JoinColumn(name = "book_stores_id", referencedColumnName = "id"),
      inverseJoinColumns =
          @JoinColumn(name = "authors_id", referencedColumnName = "id"))
  private Set<Author> authors = new HashSet<>();

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Set<Author> getAuthors() {
    return authors;
  }

  public Author addAuthor(Author author) {
    this.authors.add(author);
    return this;
  }

  public Author removeAuthor(Author author) {
    this.authors.remove(author);
    return this;
  }

  public void setAuthors(Set<Author> authors) {
    this.authors = authors;
  }
}

@Audited
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Entity
@EntityListeners({AuditingEntityListener.class, AuditingEntityListener.class})
@MappedSuperclass
@Table(name = "author")
public class Author implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @org.springframework.data.annotation.Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(name = "name") private String name;

  @OneToMany(
      cascade = {CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE},
      fetch = FetchType.LAZY, orphanRemoval = true)
  @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
  @JoinColumn(name = "author_id")
  private Set<Book> books = new HashSet<>();

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Set<Book> getBooks() {
    return books;
  }

  public Book addBook(Book book) {
    this.books.add(book);
    book.setAuthor(this);
    return this;
  }

  public Book removeBook(Book book) {
    this.books.remove(book);
    book.setAuthor(null);
    return this;
  }

  public void setBooks(Set<Book> books) {
    this.books = books
  }
}

@Audited
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Entity
@EntityListeners({AuditingEntityListener.class, AuditingEntityListener.class})
@MappedSuperclass
@Table(name = "book")
public class Book implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @org.springframework.data.annotation.Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(name = "name") private String name;

  @ManyToOne(optional = false)
  @JoinColumn(nullable = false)
  private Author author;

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Author getAuthor() {
    return author;
  }

  public void setAuthor(Author author) {
    this.author = author;
  }
}

Errors and their corrections

When trying to create a bookstore with two authors and three books each, the program throws the first error:

A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance …

After Googling for a long time, many people encountered the same problem, but no one could explain clearly what the root cause was. They just suggested that the method setAuthors() should be modified as follows:

public void setAuthors(Set<Author> authors) {
  this.authors.clear();
  this.authors.addAll(authors);
}

I modified it accordingly and it worked. But why is it an error to directly assign a new instance to member authors, but not to modify its content? I have no idea what Spring Boot is checking behind the scenes. I will have to dig deeper into the Spring Boot code when I have time in the future.

However, during subsequent testing, a second error was encountered:

JSON parse error: Cannot invoke “java.util.Collection.iterator()” because “c” is null

After searching Google for another day and a half, I finally found the reason: in the setAuthors method, the parameter authors was not checked. If it is null, this error will be raised. Then continue to modify setAuthors():

public void setAuthors(Set<Author> authors) {
  this.authors.clear();
  if (authors != null) {
    this.authors.addAll(authors);
  }
}

Just in case, modify Author.setBooks() the same way:

public void setBooks(Set<Book> books) {
  this.books.clear();
  if (books != null) {
    this.books.addAll(books);
  }
}

At this point, the above two errors are finally solved.

The difficulty in solving these two errors is that even if I adjust the log level to the lowest level, the program only outputs a line of error information without any related stacktrace, which makes it impossible for me to quickly locate the relevant code. Moreover, these two errors are triggered before executing the RESTful API callback function. They can only be broken in the Spring Boot code, which is very inconvenient to debug.

Follow-up

When debugging the code later, I finally found that it was not impossible to assign a new instance to the member authors. The problem still lies in the parameters passed in. The parameters themselves may be null, or they may contain null elements inside. So ultimately, setAuthors() and setBooks() should be modified to:

public void setAuthors(Set<Author> authors) {
  this.authors =
    Optional.ofNullable(authors)
      .map(s -> s.stream().filter(Objects::nonNull).collect(Collectors.toSet()))
      .orElse(new HashSet<>());
}

public void setBooks(Set<Book> books) {
  this.books =
    Optional.ofNullable(books)
      .map(s -> s.stream().filter(Objects::nonNull).collect(Collectors.toSet()))
      .orElse(new HashSet<>());
}

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Java Skill TreeHomepageOverview 138262 people are learning the system