Jakarta MVC made simple

This article discusses how to get started with Jakarta Model-View-Controller (MVC) to build Web applications using this well-known Web pattern in a Jakarta EE application.

Model-View-Controller in a nutshell

Model-View-Controller (MVC) is a popular pattern in Web frameworks to build HTML applications. In this framework, the model handles to the application’s data, the view handles the data presentation and, finally, the controller is responsible for managing input, updating models and producing output.

You might wonder what is the key difference between MVC and JSF.

  • Jakarta MVC is a good example of action-based framework. In this context, your views will route the requests to controllers. The controller, in turn, turns them into actions by application code.
  • Jakarta Faces, on the other had, is a component-based framework. In this context, the framework groups and handles HTTP requests with little or no interaction from application code. In other words, the framework handles the majority of the controller logic.

The Jakarta MVC API sits on top of Jakarta RESTful Web Services and integrates with existing Jakarta EE technologies like CDI and Bean Validation.

The pillars of Jakarta MVC

We will now show an example of a Jakarta MVC application that performs the standard Read, Update, Delete and List operations using as persistence layer JPA.

Therefore, we will add the following components:

  • A Controller component to orchestrate the interaction with views
  • A Repository component to access the Model
  • A Configuration class for the framework itself
  • A number of Views to provide the UI for our application

Here is a graphical view of our application schema:

jakarta mvc example

Coding the Controller

Firstly, let’s build the core application component: the Controller. A Jakarta MVC Controller is a REST resource method which includes the @Controller annotation. A Controller relies on the standard REST HTTP methods. Therefore, it’s much like any REST Endpoint. The only difference is that it returns the Views, with the cooperation of the jakarta.mvc.Models interface. Here is our Controller:

@Path("customers")
@Controller
@RequestScoped
public class CustomerController {

    @Inject
    private Models models;

    @Inject
    CustomerRepository repository;

    @GET
    public String listCustomers() {
          List<Customer> list = repository.findAllCustomers();
          models.put("customers",list);
          System.out.println("Called form!");
          return "list.xhtml";
    }

    @Path("new")
    @Controller
    @GET
    public String preAdd() {
        return "insert.xhtml";
    }

    @Path("new")
    @POST
    public String createCustomer(@BeanParam Customer customer) {
        repository.createCustomer(customer);
        List<Customer> list = repository.findAllCustomers();
        models.put("customers",list);
        return "list.xhtml";

    }

    @Path("edit/{id}")
    @GET
    public String preEdit(@PathParam("id") Long id) {
        Customer customer = repository.findCustomerById(id);
        models.put("customer",customer);

        return "edit.xhtml";
    }

    @Path("edit/{id}")
    @POST
    public String edit(@PathParam("id") Long id, @BeanParam Customer customer) {
        customer.setId(id);
        repository.updateCustomer(customer);
        List<Customer> list = repository.findAllCustomers();
        models.put("customers",list);
        return "list.xhtml";
    }


    @Path("delete/{id}")
    @GET
    public String delete(@PathParam("id") Long id) {
        repository.deleteCustomer(id);
        List<Customer> list = repository.findAllCustomers();
        models.put("customers",list);
        return "list.xhtml";
    }


}

Coding the Model

In order to persist our data, we will use the following Model, which is an Entity Bean:

@Entity
public class Customer implements Serializable {

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

    @FormParam("name")
    @NotBlank(message = "Name is empty")
    @Column
    private String name;



    @FormParam("address")
    @NotBlank(message = "Address is empty")
    @Size(min = 10, max = 200)
    @Column
    private String address;

    @FormParam("email")
    @NotBlank(message = "Address is empty")
    @Column
    private String email;

    // getters/setters omitted
}

This resource uses a @FormParam annotation to bind the value of the fields with the corresponding form parameters. It also uses Bean Validation annotations to specify some constraints on the values.

Coding the repository Class

The Repository class is a standard transaction component which uses an EntityManager to perform the standard CRUD operations. There is nothing fancy in it, just a standard Jakarta EE Persistence layer:

@Stateless
public class CustomerRepository {
    @PersistenceContext
    EntityManager em;

    @PersistenceUnit
    EntityManagerFactory emf;


    public void createCustomer(Customer customer) {
        em.persist(customer);
        System.out.println("Created Customer");

    }

    public void updateCustomer( Customer customer ) {

        Customer customerToUpdate = findCustomerById(customer.getId());
        customerToUpdate.setName(customer.getName());
        customerToUpdate.setEmail(customer.getEmail());
        customerToUpdate.setAddress(customer.getAddress());
    }

    public void deleteCustomer(Long customerId) {

        Customer c = findCustomerById(customerId);
        em.remove(c);
    }

    public Customer findCustomerById(Long id) {

        Customer customer = em.find(Customer.class, id);

        if (customer == null) {
            throw new WebApplicationException("Customer with id of " + id + " does not exist.", 404);
        }
        return customer;
    }

    public List<Customer> findAllCustomers() {
        Query query = em.createQuery("SELECT c FROM Customer c");
        List<Customer> customerList = query.getResultList();
        System.out.println(customerList);
        return customerList;
    }

    public Customer findCustomerByName(String name) {
        Query query = em
                .createQuery("SELECT c FROM Customer c WHERE c.name = :name");
        query.setParameter("name", name);
        Customer customer = (Customer) query.getSingleResult();
        return customer;
    }


}

Coding the Configuration Class

The controllers and providers that make up an MVC applications can be configured by implementing a subclass of Application from Jakarta RESTful Web Services. Within this class you can specify the URL pace for Jakarta MVC Controllers. For example:

@ApplicationPath("mvc")
public class MvcConfig extends Application {

}

We will not add extra complexity to our configuration, however you can apply further customizations in this Class. For example, here is how to configure a custom location for the view folder other than the /WEB-INF/views:

@ApplicationPath("mvc")
public class MyApplication extends Application {

    @Override
    public Map<String, Object> getProperties() {
        final Map<String, Object> map = new HashMap<>();
        map.put(ViewEngine.VIEW_FOLDER, "/myviews/");
        return map;
    }
}

Coding the Views

In order to provide an UI for a typical CRUD application, we will need a set of XHTML files to cover each use case. For example, we will start with the list.xhtml page which lists the number of Entity objects:

<html
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:c="http://java.sun.com/jsp/jstl/core"
        xmlns:components="http://java.sun.com/jsf/composite/components">
   <head>

      <link href="#{request.contextPath}/resources/css/main.css" rel="stylesheet"/>
   </head>
   <body>
      <h2>Customers list</h2>
      <div>
         <table class="blueTable">
            <thead>
               <tr>
                  <th>Name</th>
                  <th>Address</th>
                  <th>Email</th>
                  <th>Edit</th>
                  <th>Delete</th>
               </tr>
            </thead>
            <c:forEach var="customer" items="${customers}">
               <tr>
                  <td >${customer.name}</td>
                  <td>${customer.address}</td>
                  <td>${customer.email}</td>
                  <td><a href="#{request.contextPath}/mvc/customers/edit/${customer.id}">Edit</a></td>
                  <td><a href="#{request.contextPath}/mvc/customers/delete/${customer.id}">Delete</a></td>
               </tr>
            </c:forEach>
            <c:if test="${empty customers}">
               No Customers in the database.
            </c:if>
         </table>

         <form method="GET" action="#{request.contextPath}/mvc/customers/new">
            <button class="myButton" type="submit">Add new</button>
         </form>
      </div>
   </body>
</html>

Within the insert.xhtml page, there will be an HTML Form to post a new Customer. Here is a snippet of it:

<form method="POST" action="">
    <div class="divTableRow">
        <div class="divTableCell">Name:</div>
        <div class="divTableCell"><input type="text" name="name" id="name"  /></div>
    </div>
    <div class="divTableRow">
        <div class="divTableCell">Address:</div>
        <div class="divTableCell"><input type="text" name="address" id="address"  /></div>
    </div>
    <div class="divTableRow">
        <div class="divTableCell">Email:</div>
        <div class="divTableCell"><input type="text" name="email" id="email"  /></div>
    </div>
    <input type="submit" class="myButton" value="Save"/>
</form>

Finally, the edit.xhtml page is identical to the add.xhtml page, except that it contains the value of each field:

<form method="POST" action="">
    <div class="divTableRow">
        <div class="divTableCell">Name:</div>
        <div class="divTableCell"><input type="text" name="name" id="name" value="${customer.name}" /></div>
    </div>
    <div class="divTableRow">
        <div class="divTableCell">Address:</div>
        <div class="divTableCell"><input type="text" name="address" id="address"  value="${customer.address}" /></div>
    </div>
    <div class="divTableRow">
        <div class="divTableCell">Email:</div>
        <div class="divTableCell"><input type="text" name="email" id="email"  value="${customer.email}" /></div>
    </div>
    <input type="submit" class="myButton" value="Save"/>
</form>

Building the application

To build our example, we will use Eclipse Krazo which is an implementation of action-based MVC framework. Eclipse Krazo builds on top of REST API and currently contains support for RESTEasy, Jersey, and CXF with a well-defined SPI for other implementations.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.eclipse.krazo</groupId>
            <artifactId>krazo-core</artifactId>
            <version>${krazo.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>jakarta.platform</groupId>
        <artifactId>jakarta.jakartaee-api</artifactId>
        <version>${jakartaee.version}</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>jakarta.mvc</groupId>
        <artifactId>jakarta.mvc-api</artifactId>
        <version>2.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.krazo</groupId>
        <artifactId>krazo-core</artifactId>
        <version>${krazo.version}</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.krazo</groupId>
        <artifactId>krazo-resteasy</artifactId>
        <version>${krazo.version}</version>
    </dependency>
</dependencies>

You can deploy the application on WildFly 27 as follows

mvn install wildfly:deploy

And here is our cool MVC application in action:

jakarta mvc example tutorial

Source code
The source code for this article is available here: https://github.com/fmarchioni/mastertheboss/tree/master/web/ee-mvc-demo

Found the article helpful? if so please follow us on Socials