• Share this article:

Get to Know Jakarta MVC

Thursday, December 9, 2021 - 05:59 by Ivar Grimstad

To help you get to know the Jakarta MVC specification, here’s a recap of its history and status, and a brief introduction to the technology.

Jakarta MVC History and Status

The story of Jakarta MVC started back in 2014 when Java Specification Request (JSR) 371 was proposed in the Java Community Process. The work progressed very well, and the specification became popular in the community as a frequently requested addition to Java EE. When the specification was dropped for Java EE 8, the community took over and released MVC 1.0 as an independent specification in January 2020. After this release, it was transferred to the Eclipse Foundation and renamed to Jakarta MVC. 

Jakarta MVC 1.1 was released in September 2020 under the Eclipse Foundation Specification License. Just three months later, in December 2020, Jakarta MVC 2.0 was released with the jakarta.mvc.* namespace and aligned with Jakarta EE 9.

Jakarta MVC 2.0 is the most recent version of the specification, and there are currently two compatible implementations: 

Work on Jakarta MVC 2.1 is ongoing, and it is expected to be released in the Jakarta EE 10 timeframe. The Jakarta MVC project will continue to seek inclusion in the Jakarta EE Web Profile

MVC Styles 

In the Model-View-Controller (MVC) design pattern, the controller responds to a request by updating the model and selecting which view to display. The view then retrieves the data to display from the updated model (Figure 1).

MVC-1
Figure 1: Model-View-Controller Relationships

This widely used design pattern can be used in two ways: component-based and action-based. 

Component-Based MVC

Component-based MVC is made popular by component frameworks, such as Jakarta Server Faces. In this style of MVC, the framework provides the controller. This allows application developers to focus on implementing models and views, leaving controller logic to be handled by the framework (Figure 2).

MVC-2
igure 2: Component-Based MVC


Action-Based MVC

In the action-based style of MVC, the application defines the controller, giving application developers a little more fine-grained control (Figure 3).

MVC-3
Figure 3: Action-Based MVC


Jakarta MVC is an action-based MVC framework, which makes it complementary to the component-based MVC framework provided by Jakarta Server Faces. Other examples of action-based MVC include Spring MVC and Apache Struts.

Jakarta MVC Basics

Jakarta MVC is built on top of Jakarta RESTful Web Services. That means everything you know about Jakarta RESTful Web Services can be applied to your Jakarta MVC application as well.

Let’s take a closer look at what you can do with Jakarta MVC. 

The Controller

The @Controller annotation defined by Jakarta MVC marks a resource as the controller. If the annotation is applied to the resource class, all resource methods in the class become controllers. 

@Controller
@Path("hello")
public class Hello {
 
    @Path("one")

    public String oneController() {
    }

    @Path("another")
    public String anotherController() {
    }
}

The annotation can also be applied to a specific resource method. This approach is useful if you want to combine MVC controllers with REST resources in the same class.

@Path("hello")
public class Hello {

    @Controller
    @Path("one")
    public String oneController() {
    }

    @Controller
    @Path("another")
    public String anotherController() {
    }
 
    @Path("not-a-controller")
    public String notAController() {
    }
}

There are three ways to define which view a controller should select in a Jakarta MVC application:

  • First, if a controller returns void, it must be decorated with an @View annotation 
  • Second, a String returned is interpreted as a view path
  • Third, a Jakarta RESTful Web Services Response object where the entity is one of the first two 

The example below illustrates all three approaches.

@Controller
@Path("hello")
public class HelloController {

    @GET @Path("void")
    @View("hello.jsp")
    public void helloVoid() {
    }

    @GET @Path("string")
    public String helloString() {
        return "hello.jsp";
    }

    @GET @Path("response")
    public Response helloResponse() {
        return Response.status(Response.Status.OK)
            .entity("hello.jsp")
            .build();
    }
}


That’s all there is to the controller in Jakarta MVC. The rest is exactly as you know from Jakarta RESTful Web Services, such as how to handle and validate path parameters, query parameters, and bean parameters.

The Model

Jakarta MVC supports two ways of handling models:

  • Use any CDI @Named beans as your model
  • Use the provided Models interface as your model

For the CDI approach, you simply inject the CDI @Named bean into the controller, update it as needed, and return the view, as shown in the example below.

@Named("greeting")
@RequestScoped
public class Greeting {
    private String message;
    // getters and setters
}

@Path("hello")
public class HelloController {

    @Inject
    private Greeting greeting;

    @GET
    @Controller
    public String hello() {
        greeting.setMessage("Hello there!");
        return "hello.jsp";
    }
}

If the view model does not support CDI, or you want to use the provided Models interface for a different reason, you can inject the Models map and update it as shown below.

@Path("hello")
public class HelloController {

    @Inject
    private Models models;

    @GET
    @Controller
    public String hello() {
        models.put("string_greeting", "Howdy!");
        return "hello.jsp";
    }
}

The View

Views in Jakarta MVC applications are processed by a mechanism called view engines. View engines for Jakarta Server Pages and Facelets must be supported by all implementations, although the requirement to support Facelets is likely to be removed in future versions of Jakarta MVC. Additional view engines can be added using a well-defined CDI extension mechanism.

In Jakarta Server Pages views, the model is available using the Jakarta Expression Language, as shown in the example below.

<!DOCTYPE html>
<html>
    <head>
        <title>Hello</title>
    </head>
    <body>
        <h1>${greeting.message}</h1>
        <h1>${string_greeting}</h1>
    </body>
</html>

The rendered view would look something like this:

Hello   
Hello there!
Howdy!

Advanced Jakarta MVC Topics

The Jakarta MVC specification document provides a very good overview of what’s included in Jakarta MVC. I introduce some of the items here, but please refer to the specification document for details.

Data Binding

Jakarta MVC extends the data binding provided by Jakarta RESTful Web Services with support for internationalization and handling of binding errors within the controller. The Jakarta MVC-specific data binding is enabled by adding the @MvcBinding annotation to the relevant field or method parameter. Binding errors are handled in the controller by injecting BindingResult and using it to handle the error before the next view is rendered. 

@Controller
@Path("form")
public class FormController {

    @MvcBinding
    @FormParam("age")
    @Min(18)
    private int age;

    @Inject
    private BindingResult bindingResult;

    @POST
    public String processForm() {

        if( bindingResult.isFailed() ) {

            // handle the failed request
        }
 

        // process the form request
    }
}

Security

Jakarta MVC provides support to protect applications from Cross-Site Request Forgery (CSRF). To provide this support, the Jakarta MVC implementation generates a CSRF token that is available via the MvcContext object. To verify a request, simply add the @CsrfProtected annotation to the controller, as shown below.

@Path("csrf")
@Controller
public class CsrfController {

    @GET
    public String getForm() {
        return "csrf.jsp"; // Injects CSRF token
    }

    @POST
    @CsrfProtected // Required for CsrfOptions.EXPLICIT
    public void postForm(@FormParam("greeting") String greeting) {
        // Process greeting
    }
}

Events

Jakarta MVC specifies a number of events that occur while processing requests. The event mechanism is based on Jakarta Contexts and Dependency Injection (CDI), and can be observed using the @Observer annotation defined by Jakarta CDI.

Internationalization

Jakarta MVC uses the term “request locale,” which can be used for locale-dependent operations. Example use cases for locale-dependent operations include data binding, data formatting, and language-specific validation error messages. The request locale is available through the MvcContext object.

@Controller
@Path("/foobar")
public class MyController {

    @Inject
    private MvcContext mvc;

    @GET
    public String get() {
        Locale locale = mvc.getLocale();
        NumberFormat format = NumberFormat.getInstance(locale);
    }
}

For more insight into Jakarta MVC:

Learn More About Jakarta MVC and Get Involved

We welcome everyone who would like to get involved in Jakarta MVC. To discover the many different ways you can contribute to the project, click here.

This article was first published in the Eclipse Newsletter, November 29, 2021.