Java EE 7 Database Migrations with Liquibase and WildFly

July 31st, 2014 by

I have written about other database migration frameworks before but in this article I’d like to cover the Liquibase framework in combination with WildFly as Java EE 7 compatible application server.

In the following tutorial, we’re going to write a full Java EE 7 book store application with a few steps and with Liquibase on board to create the database structure and insert example data into the database.

Thanks to the WildFly Maven Plug-in we even do not need to download and configure the application server but let Maven and the plug-in do the work for us.

Liquibase Changelog

Liquibase Changelog


Java EE 7 Webproject with Maven

The easiest way to create the project is using the archetype org.codehaus.mojo.archetypes:webapp-javaee7:

mvn archetype:generate -Dfilter=webapp-javaee7
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
Choose archetype:
1: remote -> org.codehaus.mojo.archetypes:webapp-javaee7 (Archetype for a web application using Java EE 7.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1

Or using your IDE of choice with Maven support e.g. Eclipse IDE:

New project using the Java EE 7 Archetype

New project using the Java EE 7 Archetype

In addition, we need the dependencies for Liquibase (liquibase.core and a logging adapter for my logging framework of choice here, SLF4J), so the dependencies node in our final pom.xml should look like this one:


Last but not least we need to add the WildFly Maven Plugin because we’re too lazy to download, install, configure and start the application server by hand:


Creating our Application

We’re going to build a simple book store application with a RESTful webservice to persist new books to the database and to enlist available, persisted books.

The database structure is created using the Liquibase Database Migration framework initialized in an EJB.

Bootstrap Loader

We’re using a singleton session bean annotated with javax.ejb.Startup to initiate our database migrations on application startup.

Since every Java EE Application server must supply a database, we’re free to use the H2 database embedded with our WildFly server.

We’re obtaining a JDBC datasource by @Resource injection and we’re getting a reference to the default resource .. in WildFly it is “jboss/datasources/ExampleDS“.

Now that we’ve got a connection to the database we simply initialize Liquibase, tell the migration framework where to find our migration, we’re setting the context to “development” and finally we’re performing the database migration’s upgrade tasks.

package com.hascode.tutorial.setup;
import java.sql.Connection;
import java.sql.SQLException;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.sql.DataSource;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.ResourceAccessor;
public class InitializerBean {
	private static final String STAGE = "development";
	private static final String CHANGELOG_FILE = "liquibase/db.changelog.xml";
	private DataSource ds;
	protected void bootstrap() {
		ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(getClass().getClassLoader());
		try (Connection connection = ds.getConnection()) {
			JdbcConnection jdbcConnection = new JdbcConnection(connection);
			Database db = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(jdbcConnection);
			Liquibase liquiBase = new Liquibase(CHANGELOG_FILE, resourceAccessor, db);
		} catch (SQLException | LiquibaseException e) {

Persistence Configuration

This is our persistence configuration file persistence.xml in src/main/resources/META-INF.

We’re just adding a persistence unit referencing the existing default database:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
   xmlns="" xmlns:xsi=""
   <persistence-unit name="default">

Database Migration

The following database migration creates the structure for or book table and adds two books to the database.

I won’t go into the details of the Liquibase syntax but there exists an excellent documentation on the Liquibase website here.

The referenced XML schema allows us to use auto-completion in most IDE’s (I like that!).

This is our db.changelog.xml in src/main/resources/liquibase:

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns=""
	<changeSet author="micha_kops" id="2014-07-29_1440-create-book-table"
		<createTable tableName="book">
			<column name="id" type="CHAR(36)">
				<constraints nullable="false" />
			<column name="title" type="VARCHAR(255)" />
			<column name="published" type="DATETIME" />
	<changeSet author="micha_kops" id="2014-07-29_1450-add-a-book"
		<insert tableName="book">
			<column name="id" value="3B79F749-5B96-46CD-AAF8-4E75D663E6D4"/>
			<column name="title" value="Some Book"/>
			<column name="published" value="2014-01-11 12:00:01"/>
		<changeSet author="micha_kops" id="2014-07-29_1455-add-another-book"
		<insert tableName="book">
			<column name="id" value="6FE7A1E5-B172-4D7C-97C7-88667862B264"/>
			<column name="title" value="Another book"/>
			<column name="published" value="2014-01-11 12:20:00"/>

Book Entity

Our book entity simply has and id and a title and in addition we’re adding a named query to return all existing books from the database ordered by their title in ascending order.

package com.hascode.tutorial.entity;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@NamedQueries(@NamedQuery(name = Book.Query.FIND_ALL, query = "SELECT b FROM Book b ORDER BY b.title"))
public class Book {
	public static class Query {
		public static final String FIND_ALL = "Book.findAll";
	private String id;
	private String title;
	private Date published;
	// getter, setter, toString omitted

Stateless Book Bean

The responsibility of our stateless book bean is to persist and fetch book entities to/from the database within the stateless session bean’s transaction context.

package com.hascode.tutorial.service;
import java.util.List;
import java.util.UUID;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import com.hascode.tutorial.entity.Book;
public class BookBean {
	private EntityManager em;
	public List<Book> findAll() {
		return em.createNamedQuery(Book.Query.FIND_ALL, Book.class).getResultList();
	public void create(final Book book) {

RESTful Book Webservice

Our simple webservice supports two operations: fetching available books and storing a new book.

Both tasks are delegated to the BookBean injected into the webservice, nothing special here..

package com.hascode.tutorial.service;
import javax.ejb.Stateless;
import javax.inject.Inject;
import com.hascode.tutorial.entity.Book;
public class BookWebservice {
	private BookBean bookBean;
	public Response getBooks() {
		return Response.ok(bookBean.findAll()).build();
	public Response create(final Book book) {
		return Response.ok().build();

Project Structure

Our project structure should look similar to this one by now so that Maven is able to package our application as a war-file.

$ tree
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── hascode
        │           └── tutorial
        │               ├── entity
        │               │   └──
        │               ├── service
        │               │   ├──
        │               │   ├──
        │               │   └──
        │               └── setup
        │                   └──
        ├── resources
        │   ├── liquibase
        │   │   └── db.changelog.xml
        │   └── META-INF
        │       └── persistence.xml
        └── webapp

Running WildFly and the Liquibase Migrations

Now that we’ve put together everything we need, the last step is to get a WildFly application server running, build, package and deploy our application and verify that the database migrations are applied on deployment without an error.

Luckily, the WildFly Maven Plugin does all this work for us: Running the Maven goal wildfly:run downloads the application server, copies it into the project’s target directory and deploys our application.

This is the excerpt from the Maven output that displays the database migrations applied and the success of these operations:

$  mvn clean package wildfly:run
19:43:03,944 INFO  [liquibase] (ServerService Thread Pool -- 11) Successfully acquired change log lock
19:43:04,489 INFO  [liquibase] (ServerService Thread Pool -- 11) Creating database history table with name: PUBLIC.DATABASECHANGELOG
19:43:04,493 INFO  [liquibase] (ServerService Thread Pool -- 11) Reading from PUBLIC.DATABASECHANGELOG
19:43:04,498 INFO  [liquibase] (ServerService Thread Pool -- 11) liquibase/db.changelog.xml: 2014-07-29_1440-create-book-table::micha_kops: Table book created
19:43:04,499 INFO  [liquibase] (ServerService Thread Pool -- 11) liquibase/db.changelog.xml: 2014-07-29_1440-create-book-table::micha_kops: ChangeSet liquibase/db.changelog.xml::2014-07-29_1440-create-book-table::micha_kops ran successfully in 1ms
19:43:04,507 INFO  [liquibase] (ServerService Thread Pool -- 11) liquibase/db.changelog.xml: 2014-07-29_1450-add-a-book::micha_kops: New row inserted into book
19:43:04,507 INFO  [liquibase] (ServerService Thread Pool -- 11) liquibase/db.changelog.xml: 2014-07-29_1450-add-a-book::micha_kops: ChangeSet liquibase/db.changelog.xml::2014-07-29_1450-add-a-book::micha_kops ran successfully in 1ms
19:43:04,511 INFO  [liquibase] (ServerService Thread Pool -- 11) liquibase/db.changelog.xml: 2014-07-29_1455-add-another-book::micha_kops: New row inserted into book
19:43:04,511 INFO  [liquibase] (ServerService Thread Pool -- 11) liquibase/db.changelog.xml: 2014-07-29_1455-add-another-book::micha_kops: ChangeSet liquibase/db.changelog.xml::2014-07-29_1455-add-another-book::micha_kops ran successfully in 1ms
19:43:04,517 INFO  [liquibase] (ServerService Thread Pool -- 11) Successfully released change log lock

Using the RESTful Webservice with cURL

This is nearly offtopic, but if we’d wish to test our persistence solution using our RESTful webservice, this may be achieved with a view steps using cURL (or a similar tool of choice).

Fetching available books

We’re fetching available books from the server and we’re able to see that the books added by the database migration are reflected in the server’s JSON response.

$ curl -XGET http://localhost:8080/javaee7-wildfly-liquibase-tutorial-1.0.0/rs/book
[{"id":"6FE7A1E5-B172-4D7C-97C7-88667862B264","title":"Another book","published":1389439200000},{"id":"3B79F749-5B96-46CD-AAF8-4E75D663E6D4","title":"Some Book","published":1389438001000}]

Persisting a new book

Since we’ve specified that our webservice accepts the content-type application/json, we need to add this header to our request.

$ curl -H "Content-Type: application/json" -XPOST -d '{"id":null, "title":"New book", "published":"2009-11-15T14:12:12"}' http://localhost:8080/javaee7-wildfly-liquibase-tutorial-1.0.0/rs/book

Fetching again to verify

We’re able to see that now there’s one more book in the list of books fetched from the server:

$ curl -XGET http://localhost:8080/javaee7-wildfly-liquibase-tutorial-1.0.0/rs/book
[{"id":"6FE7A1E5-B172-4D7C-97C7-88667862B264","title":"Another book","published":1389439200000},{"id":"5F939EC7-549A-4D37-809C-BB605269E955","title":"New book","published":1258294332000},{"id":"3B79F749-5B96-46CD-AAF8-4E75D663E6D4","title":"Some Book","published":1389438001000}]

Tutorial Sources

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

git clone



Though using Liquibase is really easy-to-use and straightforward, you might encounter one of the following situations:

Classloader Issues

Using the latest (to the moment of writing) version of liquibase-core may produce the following warning that might be irritating when viewing the log files – but besides the classloader errors here the migrations are handled without a problem

22:00:50,381 WARN  [org.jboss.modules] (ServerService Thread Pool -- 23) Failed to define class$DynamicContentHandler in Module "deployment.javaee7-wildfly-liquibase-tutorial-1.0.0.war:main" from Service Module Loader: java.lang.LinkageError: Failed to link liquibase/sdk/watch/WatchCommand$DynamicContentHandler (Module "deployment.javaee7-wildfly-liquibase-tutorial-1.0.0.war:main" from Service Module Loader)
        at java.util.concurrent.Executors$ [rt.jar:1.7.0_40-ea]
        at java.util.concurrent.FutureTask$Sync.innerRun( [rt.jar:1.7.0_40-ea]
        at [rt.jar:1.7.0_40-ea]
        at java.util.concurrent.ThreadPoolExecutor.runWorker( [rt.jar:1.7.0_40-ea]
        at java.util.concurrent.ThreadPoolExecutor$ [rt.jar:1.7.0_40-ea]
        at [rt.jar:1.7.0_40-ea]
Caused by: java.lang.NoClassDefFoundError: org/eclipse/jetty/server/handler/AbstractHandler
        at java.lang.ClassLoader.defineClass1(Native Method) [rt.jar:1.7.0_40-ea]
        at java.lang.ClassLoader.defineClass( [rt.jar:1.7.0_40-ea]
        at org.jboss.modules.ModuleClassLoader.doDefineOrLoadClass( [jboss-modules.jar:1.3.3.Final]
        at org.jboss.modules.ModuleClassLoader.defineClass( [jboss-modules.jar:1.3.3.Final]
        ... 75 more
Caused by: java.lang.ClassNotFoundException: org.eclipse.jetty.server.handler.AbstractHandler from [Module "deployment.javaee7-wildfly-liquibase-tutorial-1.0.0.war:main" from Service Module Loader]
        at org.jboss.modules.ModuleClassLoader.findClass( [jboss-modules.jar:1.3.3.Final]
        at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked( [jboss-modules.jar:1.3.3.Final]
        at org.jboss.modules.ConcurrentClassLoader.performLoadClassChecked( [jboss-modules.jar:1.3.3.Final]
        at org.jboss.modules.ConcurrentClassLoader.performLoadClass( [jboss-modules.jar:1.3.3.Final]
        at org.jboss.modules.ConcurrentClassLoader.loadClass( [jboss-modules.jar:1.3.3.Final]
        ... 79 more

Switching to an older version helps to avoid this output, e.g. by using version 3.1.1:


Liquibase logging to STDERR

Running on JBoss AS or WildFly and without a logger configured, Liquibase logs to STDERR and the Application Server logs such output as an error – the output might look like this one:

20:59:55,188 ERROR [stderr] (ServerService Thread Pool -- 14) INFO 7/29/14 8:59 PM: liquibase: Successfully acquired change log lock

The solution is to add a logging adapter for Liquibase e.g. for SLF4J:


Alternatives: GlassFish and Flyway

I have written an article about database migrations in a Java EE enviroment using an alterenative setup with a GlassFish as application server and Flyway as database migration library: “Easy Database Migrations using Flyway, Java EE 6 and GlassFish“.

Article Updates

2014-08-01: Unnecessary JDNI references to JBoss/WildFly resources removed, thanks @john_waterwood for the remark!

Tags: , , , , , , , , , , , , , , , , , , , ,

3 Responses to “Java EE 7 Database Migrations with Liquibase and WildFly”

  1. John Waterwood Says:

    Great article, but why use the JBoss specific name for the default data source? There’s a standard name in Java EE 7 for that.

    And if you omit the data source in persistence.xml you’ll get the default one as well.

  2. micha kops Says:

    Hi John,

    thanks for your remarks! I’ve updated the article.



  3. sanjeev Says:


    Thanks for this great article.
    I have trying to integrate the Liquibase with my application at my work place.
    We are using Tomcat with openEjb. In the openejb.xml we specifiy multiple data sources.
    I am not able to automate this scenario. Can you please provide any help in this case.

    Thanks in advance,

Leave a Reply

Please note, that no personal information like your IP address is stored and you're not required to enter you real name.

Comments must be approved before they are published, so please be patient after having posted a comment - it might take a short while.

Please leave these two fields as-is: