1. Introduction

Eclipse Krazo is an implementation of action-based MVC specified by Jakarta MVC. It builds on top of Jakarta RESTful Web Services (former JAX-RS; abbreviated with Jakarta REST inside this documentation) and currently contains support for RESTEasy and Jersey with a well-defined SPI for other implementations.

This is the user guide for Eclipse Krazo 3.0.0-SNAPSHOT. We are trying to keep it up to date with the current features. Please note, that this guide assumes that the examples are, if not specified otherwise, targeting a Jakarta EE 10 environment.

2. Quickstart

This section describes how to set up Eclipse Krazo.

2.1. Basic setup

The required steps to set up Eclipse Krazo depends on your environment. The easiest way is to use a Jakarta EE environment, because in this case the server already provides all the prerequisites.

2.1.1. Jakarta EE

The easiest way getting started with Eclipse Krazo is to generate a Jakarta EE project with our archetype. This archetype generates a simple project including the Jakarta EE Web Profile and the selected, server-specific Krazo implementation.

This archetype comes with the following setups:

  • Java EE 8 / Jakarta EE 8: MVC 1.0, Krazo 1.1.0

  • Jakarta EE 9: MVC 2.0, Krazo 2.0.x

  • Jakarta EE 10: MVC 2.1, Krazo 3.0.0

Please note that since Krazo 3.0.0 the name of the archetype changed. Before this release, all archetypes contained the target Jakarta EE release in their names which led to confusing versioning. Since Krazo 3.0.0 the archetype targets the same Jakarta EE release than Krazo and the corresponding Jakarta MVC API.

How to use the archetype

The usage of the archetype is really easy. Depending on your application server, just run one of these commands in your command line.

Glassfish / Payara (Jersey) (since Krazo 3.0.0)
mvn archetype:generate \
-DarchetypeGroupId=org.eclipse.krazo \
-DarchetypeArtifactId=krazo-jakartaee-archetype \
-DarchetypeVersion=KRAZO VERSION  \
-DgroupId=YOUR GROUP ID\
-DartifactId=YOUR ARTIFACT ID
Wildfly (RESTEasy) / OpenLiberty >= 21.x (since Krazo 3.0.0)
mvn archetype:generate \
-DarchetypeGroupId=org.eclipse.krazo \
-DarchetypeArtifactId=krazo-jakartaee-archetype \
-DarchetypeVersion=KRAZO VERSION \
-DgroupId=YOUR GROUP ID \
-DartifactId=YOUR ARTIFACT ID \
-DkrazoImpl=resteasy
Glassfish / Payara (Jersey) (before Krazo 3.0.0)
mvn archetype:generate \
-DarchetypeGroupId=org.eclipse.krazo \
-DarchetypeArtifactId=krazo-jakartaee[8|9]-archetype \
-DarchetypeVersion=[1.1.0 | 2.0.x]  \
-DgroupId=YOUR GROUP ID\
-DartifactId=YOUR ARTIFACT ID
Wildfly (RESTEasy) / OpenLiberty >= 21.x (before Krazo 3.0.0)
mvn archetype:generate \
-DarchetypeGroupId=org.eclipse.krazo \
-DarchetypeArtifactId=krazo-jakartaee[8|9]-archetype \
-DarchetypeVersion=[1.1.0 | 2.0.x] \
-DgroupId=YOUR GROUP ID \
-DartifactId=YOUR ARTIFACT ID \
-DkrazoImpl=resteasy
TomEE / OpenLiberty (CXF)

Krazo won’t support CXF anymore. As OpenLiberty is going to switch to RESTEasy until it supports Jakarta REST Web Services 3.0, this change is only relevant for TomEE in general and older versions of OpenLiberty.

To use Krazo on those servers anyway, you can add Jersey or RESTEasy as a compile time dependency to your artifact/

Glassfish/Payara

Glassfish comes with Jersey as its Jakarta REST implementation. Please add the following dependencies to your application:

<dependencies>
    <dependency>
        <groupId>jakarta.mvc</groupId>
        <artifactId>jakarta.mvc-api</artifactId>
        <version>{{versions.spec.latest}}</version>
    </dependency>

    <dependency>
        <groupId>org.eclipse.krazo</groupId>
        <artifactId>krazo-jersey</artifactId>
        <version>{{versions.krazo.latest}}</version>
    </dependency>

    <!-- other dependencies ... -->
</dependencies>
Wildfly, JBoss EAP and OpenLiberty >= 21.x

Wildfly is using RESTEasy for Jakarta REST. So you need the Eclipse Krazo RESTEasy integration module:

<dependencies>
    <dependency>
        <groupId>jakarta.mvc</groupId>
        <artifactId>jakarta.mvc-api</artifactId>
        <version>{{versions.spec.latest}}</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.krazo</groupId>
        <artifactId>krazo-resteasy</artifactId>
        <version>{{versions.krazo.latest}}</version>
    </dependency>

    <!-- other dependencies ... -->
</dependencies>
Thorntail

Since version 2.7.0, Thorntail provides a stable fraction for MVC with Krazo. For older versions of Thorntail you can use the Jakarta REST fraction and add the same dependencies as required for WildFly.

Please note, that Thorntail reached end of life and therefore the MVC fraction won’t receive updates anymore.

2.1.2. Servlet Containers

The simplest way to get started with MVC is to deploy your app to a JavaEE 8 application server. In this setup the application server will provide Jakarta REST, CDI and Bean Validation implementations for you. However, there is a large number of users who prefer to run their applications on plain Servlet containers like Apache Tomcat and Jetty. In this setup you will have to provide Jakarta REST, CDI and Bean Validations yourself.

The following steps will show you how to run Eclipse Krazo on Apache Tomcat using Weld, Jersey and Hibernate Validator (used by Jersey Bean Validation).

2.2. Required dependencies

The following pom.xml example shows the dependency configuration for your application. Please use the corresponding versions according to the Jakarta EE platform you target.

<dependencies>
    <dependency>
        <groupId>jakarta.ws.rs</groupId>
        <artifactId>jakarta.ws.rs-api</artifactId>
        <scope>compile</scope>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-server</artifactId>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-common</artifactId>
        <scope>compile</scope>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-servlet</artifactId>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.inject</groupId>
        <artifactId>jersey-hk2</artifactId>
    </dependency>

    <dependency>
        <groupId>jakarta.validation</groupId>
        <artifactId>jakarta.validation-api</artifactId>
        <scope>compile</scope>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.ext</groupId>
        <artifactId>jersey-bean-validation</artifactId>
    </dependency>

    <dependency>
        <groupId>jakarta.enterprise</groupId>
        <artifactId>jakarta.enterprise.cdi-api</artifactId>
        <scope>compile</scope>
    </dependency>

    <dependency>
        <groupId>org.jboss.weld.servlet</groupId>
        <artifactId>weld-servlet-core</artifactId>
        <scope>compile</scope>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.ext.cdi</groupId>
        <artifactId>jersey-cdi1x-servlet</artifactId>
    </dependency>

    <dependency>
        <groupId>org.eclipse.krazo</groupId>
        <artifactId>krazo-jersey</artifactId>
        <version>${krazo.version}</version>
    </dependency>
</dependencies>

2.3. Configuration files

Make sure to add an empty beans.xml file in your /src/main/webapp/WEB-INF folder:

<?xml version="1.0"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
       version="4.0">

</beans>

Please note, that the default bean-discovery-mode changed to annotated in CDI 4.0. In case you want to use the old behavior, set the bean-discovery-mode to all in the beans.xml

The next file to create is called web.xml and should be placed in the src/main/webapp/WEB-INF directory:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0" metadata-complete="true">

</web-app>

The attribute metadata-complete isn’t madantory, but helps to prevent Krazo starting twice on RESTEasy

2.3.1. Mixed configuration with Application#getClasses and Jakarta REST auto discovery

As an alternative to the resource auto-discovery of Jakarta REST, someone can use Application#getClasses to configure endpoints or other providers. Unfortunately, this disables the auto-discovery of Jakarta REST completely, which leads to not loaded Krazo classes and errors or misbehavior during runtime. In case you want to use the manual approach together with Eclipse Krazo, you have to consider following configurations.

RESTEasy and Jersey

You don’t need additional configuration, as Krazo is auto-loaded by implementation-specific SPIs.

2.4. Creating you first controller

The following steps will show you how to create your first Controller using Eclipse Krazo. It assumes, that you’ve set up a project like we described before.

The first step is to create an Application class, which serves as root resource for our Controllers. The Application class extends from Jakarta REST Application and provides the base path of our application.

import jakarta.ws.rs.Application;
import jakarta.ws.rs.core.Application;

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

}

Please note that, according to the MVC specification, it is not recommended to use an empty application path, as this can lead to problems during request handling when using servlets and MVC resources in parallel or whenyou try to access a stylesheet served by the servlet container via src/main/webapp.

After we created the Application class, we need to add our Controller. Therefore, you need to add a simple Jakarta REST resource and decorate it with the jakarta.mvc.Controller annotation.

@Path("hello")
public class HelloController {

    @GET
    @Controller
    public String hello() {
        return "hello.jsp";
    }
}

Now you should see the content of hello.jsp when you access the URI /mvc/hello in your browser.

3. Configuration

This section describes how you can configure your MVC application by overwriting properties inside the Jakarta REST Application class. For example, you can overwrite an exemplary property foo inside the Application like this:

public class MyApplication extends Application {

    @Override
    public Map<String, Object> getProperties() {
        final Map<String, Object> props = new HashSet<>();

        props.put("foo", "bar");

        return props;
    }
}

3.1. CSRF

For the usage of CSRF, the MVC API and Krazo provide different properties to customize its behavior.

3.1.1. Csrf.CSRF_PROTECTION (jakarta.mvc.security.CsrfProtection)

By default, the CSRF protection for MVC resources is set to Csrf.CsrfOptions#EXPLICIT, which means, that you’re required to set the @CsrfProtected annotation over a Controller method annotated with @POST, @PUT, @PATCH or @DELETE. In case you want to use CSRF protection in all methods annotated with one of these HTTP verbs, you can set the property jakarta.mvc.security.CsrfProtection to CsrfOptions#IMPLICIT.

import jakarta.mvc.security.Csrf;
import jakarta.mvc.security.Csrf.CsrfOptions;

public class MyApplication extends Application {

    @Override
    public Map<String, Object> getProperties() {
        final Map<String, Object> props = new HashSet<>();

        props.put(Csrf.CSRF_PROTECTION, CsrfOptions.IMPLICIT);

        return props;
    }
}

Although, you might want to disable CSRF protection completely for you application. Therefore exists the CsrfOptions#OFF value, which can be set like the CsrfOptions#IMPLICIT value in the example above.

3.1.2. Csrf.CSRF_HEADER_NAME (jakarta.mvc.security.CsrfHeaderName)

Sometimes you may want to set a custom value for the header transporting the CSRF token. For this task you can use the property jakarta.mvc.security.CsrfHeaderName or its constant Csrf.CSRF_PROTECTION. Overwriting this property will change the default name of the HTTP header from X-CSRF-TOKEN to any valid HTTP header name. The property can be changed like this:

import jakarta.mvc.security.Csrf;
import jakarta.mvc.security.Csrf.CsrfOptions;

public class MyApplication extends Application {

    @Override
    public Map<String, Object> getProperties() {
        final Map<String, Object> props = new HashSet<>();

        props.put(Csrf.CSRF_HEADER_NAME, "X-CUSTOM-CSRF-HEADER");

        return props;
    }
}

3.1.3. Properties.CSRF_TOKEN_STRATEGY (org.eclipse.krazo.csrfTokenStrategy)

To generate CSRF tokens, Krazo provides two mechanisms by default: a cookie-based and a session-based algorithm, where the session-based is set as default. In case you want to change the strategy or even want to use some custom one, you can change the used strategy with the org.eclipse.krazo.csrfTokenStrategy property. For example, you can switch to the cookie-based token strategy by using this setting:

import org.eclipse.krazo.Properties;
import org.eclipse.krazo.security.CsrfTokenStrategy;

public class MyApplication extends Application {

    @Override
    public Map<String, Object> getProperties() {
        final Map<String, Object> props = new HashSet<>();

        props.put(Properties.CSRF_TOKEN_STRATEGY, CookieCsrfTokenStrategy.Builder.build());

        return props;
    }
}

The org.eclipse.krazo.security.CookieCsrfTokenStrategy can be customized via its Builder, but that isn’t in the scope of this documentation. Please have a look into the JavaDoc for this.

3.2. Views

Eclipse Krazo provides some configuration properties, where someone can customize the behavior of the view handling. These configurations will be described in the following sections.

3.2.1. Properties.REDIRECT_SCOPE_COOKIES (org.eclipse.krazo.redirectScopeCookies)

By default, Krazo uses a URL re-write mechanism to store the information about @RedirectScoped beans. In case someone doesn’t want this behavior, Krazo also supports a cookie-based strategy for this task. To enable this feature, the property org.eclipse.krazo.redirectScopeCookies needs to be overwritten inside the Jakarta REST Application class:

import org.eclipse.krazo.Properties;

public class MyApplication extends Application {

    @Override
    public Map<String, Object> getProperties() {
        final Map<String, Object> props = new HashSet<>();

        props.put(Properties.REDIRECT_SCOPE_COOKIES, true);

        return props;
    }
}

When you use the cookie based approach to handle the redirect scope, the default cookie name is org.eclipse.krazo.redirect.Cookie. In case this name doesn’t match your requirements, there is a property called org.eclipse.krazo.redirectScopeCookieName to overwrite it. The example shows how this can be achieved.

import org.eclipse.krazo.Properties;

public class MyApplication extends Application {

    @Override
    public Map<String, Object> getProperties() {
        final Map<String, Object> props = new HashSet<>();

        props.put(Properties.REDIRECT_SCOPE_COOKIE_NAME, "my_redirect_cookie");

        return props;
    }
}

This example would let Krazo create a Cookie called my_redirect_cookie containing the redirect scope token.

3.2.3. Properties.REDIRECT_SCOPE_QUERY_PARAM_NAME (org.eclipse.krazo.redirectScopeQueryParamName)

On the other hand, when you use the query param based approach, which is the default setting, you can change the query param’s name from org.eclipse.krazo.redirect.param.ScopeId to something more concise by using the property org.eclipse.krazo.redirectScopeQueryParamName. The example shows to how this can be done inside the Jakarta REST application.

import org.eclipse.krazo.Properties;

public class MyApplication extends Application {

    @Override
    public Map<String, Object> getProperties() {
        final Map<String, Object> props = new HashSet<>();

        props.put(Properties.REDIRECT_SCOPE_QUERY_PARAM_NAME, "redirect_token");

        return props;
    }
}

Now the query param would be something like /foo?redirect_token=[some UUID] instead of /foo?org.eclipse.krazo.redirect.param.ScopeId=[some UUID].

3.2.4. Properties.DEFAULT_VIEW_FILE_EXTENSION (org.eclipse.krazo.defaultViewFileExtension)

While developing a completely new MVC application, someone normally just uses one template engine for all of its views. This means, that every view file ends with the same extension and this leads to less readable code. To set this extension as a default for all view files, Krazo provides the property org.eclipse.krazo.defaultViewFileExtension, which takes a string containing the name of the file extension:

import org.eclipse.krazo.Properties;

public class MyApplication extends Application {

    @Override
    public Map<String, Object> getProperties() {
        final Map<String, Object> props = new HashSet<>();

        props.put(Properties.DEFAULT_VIEW_FILE_EXTENSION, "jsp");

        return props;
    }
}

In this example, Krazo will assume that every view is a JSP file. So in case you return the view edit from your Controller, Krazo will search for a file edit.jsp.

4. View Engines

Eclipse Krazo contains two view engine implementation out of the box: JSP and JSF. Those are required by the specification and will be shortly be introduced in this chapter.

In case you search for view engines not covered by the specification, please have a look into the Krazo Extensions repository. There you’ll find implementations for Thymeleaf, Freemarker and many more.

Anyway, if you want to implement your own custom view engine, please refer to the chapter Writing extensions covered later in this document.

4.1. JSP view engine

The MVC specification requires implementations to provide a view engine using JSP as template language. To use this view engine you’ve nothing more to do as adding a JSP ending on .jsp or .jspx into the configured view directory and call it from the controller.

4.2. JSF view engine

The JSF view engine is, as well as the JSP engine, required to be part of an MVC specification compatible implementation. This view engine enables you to use Facelets as template language. To use this language, your server needs to provide a JSF implementation and your template needs to end on .xhtml.

Please be aware of the fact, that some features known from JSF won’t work. This is due to the fact, that JSF and Jakarta MVC follow different architectural approaches. Also this view engine is discussed to be removed, or at least going to be optional, in Jakarta MVC 3.0.

5. Advanced Features

This section describes features which are built for advanced use-cases.

5.1. Overwrite HTTP method in form submissions

There are architectural approaches which use the power of HTTP verbs for processing views.

For example, the update of a resource can be performed by a PUT or PATCH request. Unfortunately, the HTML specification doesn’t support PUT, PATCH or DELETE as valid methods for forms. To bypass the lack of this possibility, Krazo provides the org.eclipse.krazo.forms.FormMethodOverwriteFilter, whose functionality got specified in Jakarta MVC 2.1. This filter enables users to override the POST method for form actions by setting an hidden input field, so they can target resources mapped to the supported HTTP verbs PUT, PATCH or DELETE.

The form method overwrite is enabled per default, which means overwriting a form’s method is supported out of the box.

The following example shows the code of a form, which performs some DELETE request on a resource:

<form action="/foobar/123" method="POST">
  <!-- Use the hidden '_method' input for overriding the 'POST'  -->
  <input name="_method" type="hidden" value="DELETE">

  <input type="submit" name="submit" value="Submit"/>
</form>

5.1.1. Migrating from legacy org.eclipse.krazo.forms.HiddenMethodFilter to org.eclipse.krazo.forms.FormMethodOverwriteFilter

The migration from the legacy org.eclipse.krazo.forms.HiddenMethodFilter is simple.

If the form method overwrite was activated by setting Properties.HIDDEN_METHOD_FILTER_ACTIVE to true, nothing but removing this line has to be done. The form method overwrite functionality is enabled per default since Jakarta MVC 2.1.

If the form method overwrite has to be disabled to achieve the legacy default behavior, the property jakarta.mvc.form.FormMethodOverwrite has to be set to jakarta.mvc.form.FormMethodOverwriter.Options#DISABLED inside the Jakarta REST application.

6. Writing Extensions

This chapter explains, what steps need to be taken to extend Krazo’s functionality. This may be additional configurations, type converters or whole view engines.

Most of those SPIs (Service Provider Interfaces) are handled by Java’s ServiceLoader mechanism. Please read its documentation before you start to implement Krazo extensions.

6.1. Add type converters

Eclipse Krazo provides its own implementation of Jakarta RESTs ParamConverterProvider, so the BindingResult can be filled in case of an incompatible mapping between the request’s and target’s type. You can add custom implementations of org.eclipse.krazo.binding.convert.MvcConverter and register them as a service, so it can be picked up by the org.eclipse.krazo.binding.convert.ConverterRegistry. Converts can be prioritized by the @Priority annotation. The default priority of all embedded MvcConverter implementations is 0 (zero).

You can find an example for adding a new MvcConverter in the ConverterPriorityIT. Please note, that the service registration is handled by Arquillian in this case.

6.2. Add features to Jakarta RESTful Web Services

To be usable on top of Jakarta REST, Eclipse Krazo uses a lot of Jakarta REST APIs. One of them is the FeatureContext which is used to register providers, filters etc. The core classes of Krazo are registered there too, which is made possible by the org.eclipse.krazo.bootstrap.ConfigProvider interface. This interface needs to be implemented in case an extension wants to add additional behavior to Krazo and registered as a service. Then, on application startup, all ConfigProvider implementations are fetched by org.eclipse.krazo.bootstrap.Initializer and registered in Jakarta REST.

The ConfigProvider implementations for krazo-jersey and krazo-resteasy are good example for the usage of this SPI.

6.3. Unwrap HTTP communication for different servers

As there is more than one implementation of Jakarta REST, there may be different behaviors when it comes to request and response handling. Some implementations wrap the original HttpServletRequest and / or HttpServletResponse which makes it hard to work on the specified API. Eclipse Krazo provides an SPI which makes it possible to unwrap such non-standard behavior, namely the org.eclipse.krazo.core.HttpCommunicationUnwrapper. This interface can be used to unwrap incoming requests an responses so Krazo can handle them.

One real-world example on how this interface can be used is the LibertyHttpCommunicationUnwrapper used to normalize the request coming from OpenLiberty.

6.4. Custom behavior for form handling during CSRF validation

To be able to receive the CSRF token from HTML forms, an implementation of the org.eclipse.krazo.security.FormEntityProvider is required. At the moment, only the content type application/x-www-form-urlencoded is supported. Anyway, sometimes there is different behavior between Jakarta REST implementations or their integration into application servers, so a custom FormEntityProvider is required. Those custom implementations need to be registered as service.

To give you an example implementation, you can have a look into org.eclipse.krazo.resteasy.security.RestEasyFormEntityProvider to see how this provider can be used to handle the behavior of RESTEasy forms.

6.5. Creating your own view engine

The last extension point is more or less one single interface you need to know when you want to add a custom view engine. To have a real-world example the Krazo Freemarker extension will be described. This one is chosen because it requires the minimum amount of configuration. More advanced example can be found e. g. in the Krazo Thymeleaf extension.

A view engine is in most of the cases just an implementation of the jakarta.mvc.engine.ViewEngine interface which contains the following two methods:

  • boolean supports(String view)

  • void processView(ViewEngineContext context)

So a view engine requires to return which views are supported, which is done by checking them for specific file suffixes. The FreemarkerViewEngine for example is triggered, when a view ends on .ftl.

@ApplicationScoped
@Priority(ViewEngine.PRIORITY_FRAMEWORK)
public class FreemarkerViewEngine extends ViewEngineBase {

    @Override
    public boolean supports(String view) {
        return view.endsWith(".ftl");
    }

    //...
}

And in case the view engine is identified as engine-to-use for the current view, it needs to get processed. This is done inside the processView method:

@ApplicationScoped
@Priority(ViewEngine.PRIORITY_FRAMEWORK)
public class FreemarkerViewEngine extends ViewEngineBase {

    @Inject
    @ViewEngineConfig
    private Configuration configuration;

	//...

    @Override
    public void processView(ViewEngineContext context) throws ViewEngineException {

        Charset charset = resolveCharsetAndSetContentType(context);

        try (Writer writer = new OutputStreamWriter(context.getOutputStream(), charset)) {

            Template template = configuration.getTemplate(resolveView(context));

            Map<String, Object> model = new HashMap<>(context.getModels().asMap());
            model.put("request", context.getRequest(HttpServletRequest.class));

            template.process(model, writer);

        } catch (TemplateException | IOException e) {
            throw new ViewEngineException(e);
        }
    }
}

As you can see, there’s no magic inside. The charset of the view is resolved, which is done by the abstract ViewEngineBase class that implements the ViewEngine interface and the template loaded via the Configuration class. The Configuration class is a FreeMarker API that is produced in the bean DefaultConfigurationProducer and injected by CDI. Afterwards, the model is migrated into the type FreeMarker expects as its model and the template gets processed.

Concluding this topic, as you can see creating an own Krazo view engine is not hard from Krazo’s point of view and the ViewEngine interface provides everything you need to get started.