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:
- Eclipse Krazo 2.0.0, which is verified to work with implementations based on Eclipse Jersey and RESTEasy
- Eclipse GlassFish 6.2.x
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).
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).
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).
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.