Here is how to adopt EJB in JakartaEE with Hibernate and JSP

There is a big change coming in the Java web world soon. It is the renaming of the javax packages to jakarta packages. You can find details about it here. After this renaming, our beloved old JavaEE platform will become JakartaEE (version 9 and after), Spring framework will rename the packages with the version 6 and libraries like Hibernate or Jersey will be updated to jakarta.persistence or jakarta.ws.rs packages. I feel like this transformation is a clue for the old EJB (enterprise java beans) projects. In this post, i will show you how to bring JakartaEE 9, EJB, Hibernate and JSP technologies together without using spring framework with a sample project. And of course i will use maven. The demo project is writing a "book" object into a database and listing them on a JSP page.

I must clearify something first. We can already create multi-functional web applications with spring boot and thymeleaf. We will see the jakarta namespaces in that world with hibernate or jersey or other library integrations soon. So why am i building a sample app with EJB instead of spring framework? Because we still have web applications that use complex java tehchnologies without even using maven. If transforming these applications would involve transitioning to another technology like spring framework, you would have a lot of work to do. So instead of changing the whole structure, i think it makes more sense to stay in the EJB world and use the existing knowledge base. My demo project is using the up-to-date libraries already. It doesn't create any confusion and it provides you the minimum configuration necessary. Long story short, do you have an old Java web application? Do you want to be a necromencer and revive it? You can start with this blog post. Otherwise, you may not be able to find many results in google if you search for "jakarta, ejb, hibernate, jsp" quartet :)

You can find the source code of this project on github as usual. I will mostly explain the key points of the code. In order to understand and run this project, you need to have some knowledge about some technologies and have installed some applications on your computer. Our shopping list is like below.

  • JavaEE, Servlet, Maven and JSP knowledge
  • java 13 or Java 17 installed on your computer
  • Eclipse or Intellij or any other Java IDE
  • If you are using eclipse you might need Lombok plugin
  • Postgresql or any other RDBMS of your choice

Project creation

If you want to create this project from scratch yourself, you can use maven-archetype-webapp archetype like the image below. This archetype will create the necessary folders like webapp and WEB-INF and a web.xml file. This is a good starting point since we are not using spring boot and we are unable to find a clean and up-to-date archetype with jakartaee 9, hibernate and jsp dependencies.

After you create the project, you can simply follow these steps.

  • Adding the necessary dependencies for jakartaee, hibernate and JSP in the pom.xml file
  • Adding the necessary servlet and mapping settings in the web.xml file
  • Creating a jakarta servlet extension class for this servlet
  • This archetype may not create java folder under src/main folder, so you should create it yourself if none exists
  • Creating a SessionFactory bean for Hibernate
  • Creating a hibernate.cfg.xml file under resources folder for Hibernate

Let me show you the important bits of code for you to follow this path to awesomeness.

pom.xml file and minimum requirements

We have JakartaEE 9 for javaee, hibernate core 6 for jakarta version of Hibernate, jakarta.servlet.jstl 2.0.0 for JSP and these are the most important ones. We also have postgresql for hibernate to use to connect to the database. I have also added jakarta.servlet dependency to shut up the errors popping up in JSP files in eclipse. Notice it is "provided" scope and already exists in the server. Eclipse doesn't know that :)

			
<dependencies>
	<!-- this is the new jakarta EE runtime, renamed from javax to jakarta in the version 9 -->
	<dependency>
		<groupId>jakarta.platform</groupId>
		<artifactId>jakarta.jakartaee-api</artifactId>
		<version>9.1.0</version>
		<scope>provided</scope>
	</dependency>
	<!-- This is here to shut up the eclipse complaining about no "javax.servlet implementation found on classpath" in JSP files -->
	<!-- This is already in TomEE, so it is provided and not included in the output war file -->
	<dependency>
		<groupId>jakarta.servlet</groupId>
		<artifactId>jakarta.servlet-api</artifactId>
		<version>5.0.0</version>
		<scope>provided</scope>
	</dependency>
	<!-- This is important !! You need the java tag library processing in your JSP files -->
	<!-- This is also not javax, it is jakarta from now on -->
	<dependency>
		<groupId>jakarta.servlet.jsp.jstl</groupId>
		<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
		<version>2.0.0</version>
	</dependency>
	<!-- This one prevents the JSP files complaining about JSTL library not found, not sure it is mandatory though -->
	<dependency>
		<groupId>org.apache.taglibs</groupId>
		<artifactId>taglibs-standard-impl</artifactId>
		<version>1.2.5</version>
		<scope>runtime</scope>
	</dependency>
	<!-- Hibernate 6 is the jakarta package version, not javax.persistance anymore, it is jakarta.persistance -->
	<dependency>
		<groupId>org.hibernate.orm</groupId>
		<artifactId>hibernate-core</artifactId>
		<version>6.0.0.Beta3</version>
	</dependency>
	<dependency>
		<groupId>org.postgresql</groupId>
		<artifactId>postgresql</artifactId>
		<version>42.3.1</version>
	</dependency>
</dependencies>

<build>
	<pluginManagement>
		<plugins>
			<plugin>
				<!-- You can use EAR plugin to create a modular project but it would becoma a monolithic project -->
				<!-- Why create a monolithic project in 2022 ? -->
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>3.3.1</version>
			</plugin>
		</plugins>
	</pluginManagement>
</build>
			
		

We can run this application on an EJB container server like TomEE or Jboss or Glassfish. Because these servers meet the EJB specification requirements. In other words, we are using CDI (dependency injection) with enterprise java beans, therefore we need an EJB container, not just tomcat.

We also have maven-war-plugin in the pom file. Since we are not using a parent pom file, we are indicating the output file format of the compiled form of our project. You can create an enterprise archive (EAR) that contains small EJB projects, or war files, in one package. But why would you create a monolithic application in 2022? Next stop is the xml configurations.

Web.xml and hibernate.cfg.xml files

Naturally, we have the mandatory servlet definition and the mappings to reach that servlet in our web.xml file. If you are already familiar with web apps, there isn't any extraordinary info here. Only one weird thing is that i have put all the web pages about the "book" object (entity or model) inside the "book" folder. That is why i had to indicate all the mappings one by one here. If i try a mapping like "/book/*" the servlet will go into an infinite loop. The "BookServlet" class will be explained below.

			
<web-app>
	<display-name>Archetype Created Web Application</display-name>
	<!-- I don't have BookServlet-servlet.xml in this project like in spring framework -->
	<!-- It is possible to create an ejb-jar.xml file, but i think it is old school -->
	<servlet>
		<servlet-name>BookServlet</servlet-name>
		<display-name>BookServlet</display-name>
		<description>Book servlet</description>
		<servlet-class>com.numankaraaslan.jakartaejb.servlet.BookServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>BookServlet</servlet-name>
		<url-pattern>/book/addbook</url-pattern>
	</servlet-mapping>
	<servlet-mapping>
		<servlet-name>BookServlet</servlet-name>
		<url-pattern>/book/books</url-pattern>
	</servlet-mapping>
</web-app>
			
		

You might be asking now where will we create the beans. We used to have applicationcontext.xml or beans.xml or servlet.xml in spring framework applications. You can create an ejb-jar.xml file here for the same functionality as far as i know. But i will create the beans via a beanfactory class. You can find an example ejb-jar.xml file here.

We have one more xml configuration in this project and that is hibernate.cfg.xml file. We will set the settings for hibernate in this file. hibernate.dialect and hibernate.hbm2ddl.auto are two important properties here. Dialect must be compatible with the postgresql version you are using. The ddlauto will automatically create tables in the postgresql for you while the application is starting. We will handle the database transactions. Because we are not using JTA implementations or Spring Data here. Therefore we need hibernate.current_session_context_class property set here. Otherwise you will get "No CurrentSessionContext configured" exception. The namespaces on top of the file are also important.

			
<!-- check out the hibernate-configuration-3.0.dtd and hibernate-configuration-4.0.xsd file for possible tags and structures -->
<p:hibernate-configuration xmlns:p="http://www.hibernate.org/xsd/orm/cfg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.hibernate.org/xsd/orm/cfg hibernate-configuration-4.0.xsd ">
	<p:session-factory name="Session Factory">
		<!-- Of course you can create a Bean for session factoey and set the properties in java side -->
		<p:property name="hibernate.connection.url">jdbc:postgresql://localhost:5433/EJB</p:property>
		<p:property name="hibernate.connection.driver_class">org.postgresql.Driver</p:property>
		<p:property name="hibernate.connection.username">postgres</p:property>
		<!-- change the password -->
		<p:property name="hibernate.connection.password">yourpassword</p:property>
		<p:property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</p:property>
		<!-- create, update, validate, create-drop -->
		<p:property name="hibernate.hbm2ddl.auto">update</p:property>
		<p:property name="hibernate.show_sql">true</p:property>
		<!-- hibernate.current_session_context_class is either jta or thread -->
		<!-- we are not using any java transaction api manager, we are using hibernate session transactions, hence the thread -->
		<p:property name="hibernate.current_session_context_class">thread</p:property>
		<!-- i couldn't set the package name here so i had to indicate all the model classes manually -->
		<!-- <p:mapping package="com.numankaraaslan.jakartaejb.model" /> -->
		<!-- there are no autoscanning of entity clases here, this is not spring framework -->
		<p:mapping class="com.numankaraaslan.jakartaejb.model.Book" />
	</p:session-factory>
</p:hibernate-configuration>
			
		

BeanFactory class and the necessary beans

Right now, the only necessary bean for this application to read and write values from a database is the org.hibernate.SessionFactory bean. This session factory will be configured according to hibernate.cfg.xml file. I wasn't able to create a SessionFactory bean directly beucase i don't know if i can write an annotation on top of a method, like @Bean in spring framework. That is why i have configured the BeanFactory as a singleton bean and injected it into the necessary classes and reached the sessionfactory. The code below will read the hibernate.cfg.xml file from the resources folder. It will be placed inside the WEB-INF folder inside the war file of the project. Don't forget to add the resources folder in the classpath if it is not there. You can check out the build path settings. And of course you can set the settings manually inside the code yourself, without an xml file. Notice the database name is "EJB", don't forget to create it.

			
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import jakarta.annotation.PostConstruct;

@jakarta.ejb.Singleton
public class BeanFactory
{
	// Instead of using entitymanager, we are using session factory of hibernate
	private SessionFactory sessionFactory;

	// Initialize the session factory only once
	@PostConstruct
	public void initialize()
	{
		sessionFactory = new Configuration().configure("hibernate.cfg.xml").buildSessionFactory();
		// you can also hardcode properties like below
		// sessionFactory = new Configuration().setProperty("hibernate.connection.url", "jdbc:postgresql://localhost:5433/EJB");
	}

	// This is not a bean, i am calling manually
	public SessionFactory getSessionFactory()
	{
		return sessionFactory;
	}
}
			
		

A simple repository

This application has the necessary connection information in the hibernate.cfg.xml file now and we have a postgresql driver in the dependencies. Our repository class must inject the sessionfactory with CDI. We have declared this class inside the beanfactory. We can inject the beanfactory with @jakarta.inject.Inject here. Notice session.getTransaction().begin(); and session.getTransaction().commit(); methods inside the get and add methods. We are handling the transactions ourselves. This repository is a singleton too, so that we can inject it somewhere else. It could have been "Stateless" but i wanted a single instance in all the application context.

			
import java.util.List;

import org.hibernate.Session;
import org.hibernate.query.criteria.JpaCriteriaQuery;

import com.numankaraaslan.jakartaejb.config.BeanFactory;
import com.numankaraaslan.jakartaejb.model.Book;

@jakarta.ejb.Singleton
public class BookRepo
{
	private BeanFactory factory;
	
	// @Inject is actually new and improved @jakarta.ejb.EJB
	@jakarta.inject.Inject
	public void setFactory(BeanFactory factory)
	{
		this.factory = factory;
	}

	public List<Book> getBooks()
	{
		// Criteria API :@
		Session session = this.factory.getSessionFactory().getCurrentSession();
		session.getTransaction().begin();
		JpaCriteriaQuery<Book> query = session.getCriteriaBuilder().createQuery(Book.class);
		query.from(Book.class);
		List<Book> list = session.createQuery(query).getResultList();
		session.getTransaction().commit();
		return list;
	}

	public void save(Book book)
	{
		Session session = this.factory.getSessionFactory().getCurrentSession();
		session.getTransaction().begin();
		session.save(book);
		session.getTransaction().commit();
	}
}
			
		

The model (or you can call entity) in this project is very basic and it uses the basic hibernate functions. You can find the code below but it you want to use your own schema rather than the default "public" schema, remember to create a schema first. Notice the package names, jakarta.persistence, and annotations here. You might need double quotes (") in the schema and table names in postgresql.

			
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
// this is a hibernate managed entity
// \"JakartaEE\" is the schema name and \"Book\" is the table name
// if you are using postgresql, you need the queries like create table "jakartaee"."book" with double quotes
// that's why i have to use \" on schema and table names
@Table(schema = "\"JakartaEE\"", name = "\"Book\"")
//Remember to create "JakartaEE" schema in posgtresql database
//CREATE SCHEMA JakartaEE AUTHORIZATION postgres; GRANT ALL ON SCHEMA JakartaEE TO postgres;
public class Book
{
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;

	public Book(String name, int year, String author)
	{
		this.name = name;
		this.year = year;
		this.author = author;
	}

	@Column
	private String name;

	@Column
	private int year;

	@Column
	private String author;
}
			
		

BookServlet class

This part of the application is a bit different. Nowadays, if you are using a web service implementation like Jersey or jakarta web services or spring framework, you don't have to implement your own servlet. They have their own servlets to operate on requests and reponses. Unfortunately we don't have a preset servlet and MVC classes that are integrated with java here. We are designing our own servlet and mvc structures in these JSP servlet applications. You can also find an example of this here. Correct me if i'm wrong please :)

My custom servlet extends the jakarta.servlet.http.HttpServlet class and overrides the get and post methods. Even though it looks spaghettified, it is doing simple redirections. I have injected the BookRepo repository class here with the @Inject annotation. The post method invokes when we are adding books and the get method invokes when we list the books. The get method is sending the books list to the JSP page with request.setAttribute method. Post method acquires and saves the book information from the form inside the JSP page with element names. I guess there would be another way to read the object from the form inside JSP files (like th:object in thymeleaf) but i have no experience in that field. You can easily change the frontend part of this project.

We are redirecting the requests in the get and post methods. My demo project has a folder called "/book" and i am redirecting the requests to these jsp files. For example request.getRequestDispatcher("addbook.jsp").forward(request, response); is doing this forwarding. As your project grow, these mappings will get more and more complicated, be aware. This servlet is Stateless. If you want to know more about Stateless, Stateful and Singleton, you can check out this.

			
import java.io.IOException;

import com.numankaraaslan.jakartaejb.model.Book;
import com.numankaraaslan.jakartaejb.repo.BookRepo;

import jakarta.ejb.Stateless;
import jakarta.inject.Inject;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Stateless
// The alternative approach without web.xml definition would be like below
// @WebServlet(urlPatterns = { "/book/addbook", "/book/books" })
public class BookServlet extends HttpServlet
{
	private static final long serialVersionUID = -4755981094229859819L;

	@Inject
	public BookRepo bookRepo;

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		if (request.getRequestURI().endsWith("/addbook"))
		{
			// this is http://localhost:8080/JakartaEJB/book/addbook
			// i can directly redirect to "addbook.jsp", not "book/addbook.jsp" even though it is in the "book" folder (see webapp folder)
			// because i am already inside the /book mappings (see web.xml)
			request.getRequestDispatcher("addbook.jsp").forward(request, response);
		}
		else if (request.getRequestURI().endsWith("/books"))
		{
			// this is http://localhost:8080/JakartaEJB/book/books
			request.setAttribute("books", bookRepo.getBooks());
			// i can directly redirect to "list.jsp", not "book/list.jsp" even though it is in the "book" folder (see webapp folder)
			// because i am already inside the /book mappings (see web.xml)
			request.getRequestDispatcher("list.jsp").forward(request, response);
		}
		// here "request.setAttribute" is actually "modelandview.addobject" method in spring framework
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		if (request.getRequestURI().endsWith("/addbook"))
		{
			bookRepo.save(new Book(request.getParameter("name"), Integer.parseInt(request.getParameter("year")), request.getParameter("author")));
			response.sendRedirect("addbook.jsp");
		}
	}
}
			
		

A simple JSP page

We have prepared the servlet class in order to establish the MVC structure. The next step is designing the frontend pages. You will be reaching these pages via links like http://localhost:8080/JakartaEJB/book/books and the servlet will redirect it to the list.jsp file. We have sent the data from the servlet as attributes from the servlet class.

Down below is the list.jsp page example. The top 2 lines of this file is pretty important. isELIgnored="false" language="java" notation denotes that this is page is created using java. I had to include isELIgnored too because somehow the jsp files were not being rendered and i was seeing $ signs. The second line is the tag library that we will use in this file. Just like thymeleaf, this is a namespace. We have printed the data in the body section of the page with <c:forEach items="${books}" var="book"> command. This line is defining a variable called Book and iterates over the list. We have reached the name of the book with "book.name" thanks to reflection looking for the "getName" method and invoking it. This page will be rendered on the server side and the user will be receiving a pure html file. The table will have the same number of rows as the number of the books in the database.

			
<%@ page isELIgnored="false" language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Spring JSP demo</title>
</head>
<body>
	<!-- going one level up in the folder structure ../index.jsp -->
	<a href="../index.jsp">home</a>
	<a href="books">books</a>
	<a href="addbook">add</a>
	<br />
	<table>
		<thead>
			<tr>
				<th>Name</th>
				<th>Year</th>
				<th>Author</th>
			</tr>
		</thead>
		<tbody>
			<c:forEach items="${books}" var="book">
				<tr>
					<td>${book.name}</td>
					<td>${book.year}</td>
					<td>${book.author}</td>
				</tr>
			</c:forEach>
		</tbody>
	</table>
</body>
</html>
			
		

Finally you might have a project structure like this.

You may not be able to run this application directly inside Eclipse or Intellij with run as -> run on server command. Because you need the servers with the integration of the new jakarta packages. I was able to create a server in the eclipse like i was creating a tomcat 10 server and pointing it to the newest TomEE 9 server. But i suggest you to use Payara or a Jboss server. They have enterprise versions too. If you can run the application, you must be able to reach it via localhost:8080/JakartaEJB/index.jsp.

Clear for take off with the minimal required configuration

We have used JakartaEE as the most up-to-date JavaEE version in this project. A note here, you can easily use JakartaEE web services with jakarta.ws.rs classes, without using jersey. We have created beans and put them inside the context and injected them, that means we have used EJB. This context is different from spring from spring framework context. It is directly tied to the servers context. We also have Hibernate (you can call JPA or persistence) to manage and connect to the database. And the version 6 provided us the jakarta packages. As of 2022 January, the version 6 is in beta stage but it will eventually become stable release. We have used JSP in the frontend. I think you can easily turn this into a higher level frontend structure like JSF. And so we have an up-to-date project archetype that uses JakartaEE, EJB, Hibernate and JSP. Your project will get more and more complex as it grows, so i recommend you to check out which bean types should i use at this point. See you at the next post :)


Leave a comment