Adding multiple EntityListeners to an Entity in JPA 2

February 25th, 2013 by

The ability to attach lifecycle events to an entity using simple annotations sometimes is a neat feature in the Java Persistence API.

The following short snippets demonstrate how to bind and trigger the different available lifecycle events using an embedded derby database and a bunch of annotations.



I’m using Hibernate as persistence manager here and Derby as an easy to setup database. In the last step we’ll be writing a test that’s why we’ve added JUnit and Hamcrest.


Creating an Entity

First of all we’re creating the classical JPA entity .. a book entit that has and numeric ID, a title and a date field.

Please notice that we’ve registered two entity listeners for this entity using the @EntityListeners annotation for the class.

package com.hascode.tutorial.entity;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import com.hascode.tutorial.entity.listener.BookEntityListener;
import com.hascode.tutorial.entity.listener.TitleValidator;
@EntityListeners({ BookEntityListener.class, TitleValidator.class })
public class Book implements TitleEntity {
	private Long id;
	private String title;
	private Date published;
	public final Long getId() {
		return id;
	public final void setId(final Long id) { = id;
	public final String getTitle() {
		return title;
	public final void setTitle(final String title) {
		this.title = title;
	public final Date getPublished() {
		return published;
	public final void setPublished(final Date published) {
		this.published = published;
	public String toString() {
		return "Book [id=" + id + ", title=" + title + ", published="
				+ published + "]";

Afterwards we should reference the entity in the persistence.xml in src/test/resources/META-INF

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns=""
	<persistence-unit name="hascode-tutorial"
			<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" />
			<property name="javax.persistence.jdbc.url" value="jdbc:derby:hascode_testdb;create=true" />
			<property name="hibernate.show_sql" value="true" />
			<property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />
			<property name="" value="create-drop" />

You might have noticed that our entity implements an interface so this is the TitleEntity interface .. it just got one getter method, getTitle()

package com.hascode.tutorial.entity;
public interface TitleEntity {
	String getTitle();


Now the final part – our entity listeners: The first entity listener responds to every possible entity lifecycle listener and prints out a message.

The following lifecycle events are supported:

  • @PrePersist: triggered before a new entity is persisted (being added to the EntityManager)
  • @PostPersist: – triggered after storing a new entity in the database (during commit or flush).
  • @PostLoad: – triggered after an entity has been retrieved from the database.
  • @PreUpdate: – triggered when an entity is identified as modified by the EntityManager.
  • @PostUpdate: – triggered after updating an entity in the database (during commit or flush).
  • @PreRemove: – triggered when an entity is marked for removal in the EntityManager.
  • @PostRemove: – triggered after deleting an entity from the database (during commit or flush).
package com.hascode.tutorial.entity.listener;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import com.hascode.tutorial.entity.Book;
public class BookEntityListener {
	public void prePersist(final Book book) {
		System.out.println("prePersist: " + book.toString());
	public void postPersist(final Book book) {
		System.out.println("postPersist: " + book.toString());
	public void preUpdate(final Book book) {
		System.out.println("preUpdate: " + book.toString());
	public void postUpdate(final Book book) {
		System.out.println("postUpdate: " + book.toString());
	public void postLoad(final Book book) {
		System.out.println("postLoad: " + book.toString());
	public void preRemove(final Book book) {
		System.out.println("preRemove: " + book.toString());
	public void postRemove(final Book book) {
		System.out.println("postRemove: " + book.toString());

The second entity listener is activated before an entity gets persisted and validates the title of the book against a list of bad words.

Please notice that you really should not validate your entities this way – its just for demonstration purpose – if you’re interesting in an efficient way to validate your entities, please feel free to take a look at my tutorial “Bean Validation with JSR-303 and Hibernate Validator“.

package com.hascode.tutorial.entity.listener;
import javax.persistence.PersistenceException;
import javax.persistence.PrePersist;
import com.hascode.tutorial.entity.TitleEntity;
public class TitleValidator {
	private final String[] badWords = { "shitty", "xxx", "..." };
	public void checkBadWords(final TitleEntity titleEntity) {
		for (String badWord : badWords) {
			if (titleEntity.getTitle().contains(badWord)) {
				System.err.println("bad word in title detected: " + badWord);
				throw new PersistenceException("bad word in title detected: "
						+ badWord);
				// should use ValidationException if bean validation is present


Now that we’ve got everything we need – the last thing is to put it all together and persist some data in our embedded database.

You might be wondering why I’m flushing the entitymanager all the time – this is solely to demonstrate all possible life-cycle events.

package it;
import static org.hamcrest.MatcherAssert.assertThat;
import static;
import static org.hamcrest.Matchers.notNullValue;
import java.sql.SQLException;
import java.util.Date;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import com.hascode.tutorial.entity.Book;
public class BookIT {
	static EntityManagerFactory emf;
	static EntityManager em;
	static EntityTransaction tx;
	public static void setupClass() throws Exception {
		emf = Persistence.createEntityManagerFactory("hascode-tutorial");
		em = emf.createEntityManager();
		tx = em.getTransaction();
	public void setUp() {
	public static void teardownClass() throws SQLException {
	public void tearDown() {
	@Test(timeout = 10000)
	public void shouldTriggerBookLifecycles() throws Exception {
		Book book = new Book();
		book.setPublished(new Date());
		book.setTitle("Some book");
		assertThat(book.getId(), is(notNullValue()));
		assertThat(book.getId(), is(1L));
		book.setPublished(new Date());
		em.refresh(book); // we just want to trigger postLoad
		Book book2 = em.find(Book.class, 1L);
		assertThat(book2, is(notNullValue()));
		assertThat(book2.getId(), is(book.getId()));
		assertThat(book2.getTitle(), is(book.getTitle()));
	@Test(timeout = 10000, expected = PersistenceException.class)
	public void testShouldValidateTitle() throws Exception {
		Book book = new Book();
		book.setPublished(new Date());
		book.setTitle("The shitty boook");

The first test produces some similar output:

prePersist: Book [id=null, title=Some book, published=Mon Feb 25 21:55:12 CET 2013]
postPersist: Book [id=1, title=Some book, published=Mon Feb 25 21:55:12 CET 2013]
postLoad: Book [id=1, title=Some book, published=2013-02-25]
preRemove: Book [id=1, title=Some book, published=2013-02-25]
postRemove: Book [id=1, title=Some book, published=2013-02-25]

The second test yields the following output:

bad word in title detected: shitty

Tutorial Sources

Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:

git clone

Other JPA Articles

Please feel free to have a look at my other articles about the Java Persistence API:


Article Updates

  • 2015-03-03: Links to my other JPA articles added.

    Tags: , , , , , ,

    Leave a Reply

    Please leave these two fields as-is:

    Protected by Invisible Defender. Showed 403 to 113,220 bad guys.