Saturday, December 19, 2020

OmniFaces 3.9 released with new exception handlers and resource handler

OmniFaces 3.9 has been released!

In this version, among others two new exception handlers have been added: the ExceptionSuppressor and ViewExpiredExceptionHandler, as well as a new resource handler: the VersionedResourceHandler. All based on contributions of Lenny Primak.

You can find the complete list of additions, changes and fixes at What's new in OmniFaces 3.9? list in showcase.

Installation

Non-Maven users: download OmniFaces 3.9 JAR and drop it in /WEB-INF/lib the usual way, replacing the older version if any.

Maven users: use <version>3.9</version>.

<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>3.9</version>
</dependency>

If you're already on Jakarta EE 9 (e.g. GlassFish 6), then use 4.0-M4 instead. It's the Jakartified version of 3.9.

Suppressing inevitable exceptions

The ExceptionSuppressor allows you to completely suppress exceptions by simply refreshing the current page without leaving any trace in server logs. This is useful in case of inevitable exceptions such as those caused by an abruptly aborted network connection between client and server.

In order to install it, add it as last exception handler factory in faces-config.xml (because you don't want earlier registered exception handlers from possibly taking over the handling of the to-be-suppressed exceptions; the last registered one basically wraps the previous registered one and thus runs as the first):

<factory>
    ...
    ...
    <exception-handler-factory>org.omnifaces.exceptionhandler.ExceptionSuppressorFactory</exception-handler-factory>
</factory>

And configure the to-be-suppressed exceptions as a commaseparated string of fully qualified names of exception types in web.xml as the value of a context parameter with the name org.omnifaces.EXCEPTION_TYPES_TO_SUPPRESS:

<context-param>
    <param-name>org.omnifaces.EXCEPTION_TYPES_TO_SUPPRESS</param-name>
    <param-value>java.nio.channels.ClosedByInterruptException,java.nio.channels.IllegalSelectorException</param-value>
</context-param>

Suppressing view expired exceptions

The ViewExpiredExceptionHandler is a specialized subclass of ExceptionSuppressor which does basically the same but additionally sets a specific flash scoped attribute so that the view and/or the backing bean can consult whether the currently requested page was the consequence of a refresh after a ViewExpiredException.

In order to install it, add it as exception handler factory in faces-config.xml:

<factory>
    <exception-handler-factory>org.omnifaces.exceptionhandler.ViewExpiredExceptionHandlerFactory</exception-handler-factory>
</factory>

Noted should be that this is essentially a poor practice as this is very likely to cause bad user experience when the view was not expired as the consequence of an expired session. Although this is a rather rare case, if you want to properly deal with this condition, then better use a specific error page in web.xml which can be managed by FullAjaxExceptionHandler. From the error page on, you'll have more control over whether to actually refresh/redirect the request or not.

Adding cache bust query string to resources

The VersionedResourceHandler will automatically add version parameter with query string name v to all resource URLs with a value which can be obtained from a managed bean.

In order to install it, add it as last resource handler in faces-config.xml (because you don't want to disturb any other registered resource handlers which themselves already add a v query string parameter):

<application>
    ...
    ...
    <resource-handler>org.omnifaces.resourcehandler.VersionedResourceHandler</resource-handler>
</application>

The value of the version parameter can in turn be defined as the value of a web.xml context parameter with the name org.omnifaces.VERSIONED_RESOURCE_HANDLER_VERSION which in turn can represent an EL expression referring a managed bean property:

<context-param>
    <param-name>org.omnifaces.VERSIONED_RESOURCE_HANDLER_VERSION</param-name>
    <param-value>#{environment.version}</param-value>
</context-param>

How about OmniFaces 2.x and 1.1x?

The 2.x got the same bugfixes as 3.9 and has been released as 2.7.9. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, use 3.x instead.

The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical bugfix versions will be released. It's currently still at 1.14.1 (May 2017), featuring the same features as OmniFaces 2.4, but without any JSF 2.2 and CDI things and therefore compatible with CDI-less JSF 2.0/2.1.

Friday, November 13, 2020

Using Java 14 Records in JSF via Eclipse

Introduction

Java 14 introduced the record type. It's basically some sort of an immutable JavaBean without the need to write/generate all these accessor/equals/hashCode/toString methods.

Summarized, the following record in its full form:

public record Person(
    Long id,
    String email,
    LocalDate dateOfBirth
) {}

Corresponds less or more to the following class:

public class Person {
    private final Long id;
    private final String email;
    private final LocalDate dateOfBirth;
    
    public Person(
        Long id,
        String email,
        LocalDate dateOfBirth
    ) {
        this.id = id;
        this.email = email;
        this.dateOfBirth = dateOfBirth;
    }
    
    public Long id() {
        return id;
    }
    
    public String email() {
        return email;
    }
    
    public LocalDate dateOfBirth() {
        return dateOfBirth;
    }
    
    @Override
    public boolean equals(Object other) {
        return other == this || other instanceof Person
            && Objects.equals(id, ((Person) other).id)
            && Objects.equals(email, ((Person) other).email)
            && Objects.equals(dateOfBirth, ((Person) other).dateOfBirth);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(
            id,
            email,
            dateOfBirth
        );
    }
    
    @Override
    public String toString() {
        return getClass().getSimpleName() + "["
            + "id=" + id + ", "
            + "email=" + email + ", "
            + "dateOfBirth=" + dateOfBirth
        + "]";
    }
}

But then without all the repeated boilerplate :)

It almost matches the JavaBeans Spec, with the exception that the method names of the accessor methods are not prefixed with get/is.

Preparing Eclipse for Java 14 Records

Being able to develop Java 14 Records in Eclipse requires a minimum of Eclipse 2020-06, because Java 14 support was only introduced in that version for the first time. In Eclipse's preferences, you need to ensure that Java ➤ Compiler ➤ Compiler compliance level is set to at least 14.

If you're using Maven, then you need to ensure that the pom.xml properly instructs Maven to use Java 14 as well. Assuming that you've a Maven project which is created the same way as instructed in JSF 2.3 tutorial with Eclipse, Maven, WildFly and H2, then you just need to adjust the maven.compiler.* properties as below:

<properties>
    ...
    <maven.compiler.source>14</maven.compiler.source>
    <maven.compiler.target>14</maven.compiler.target>
    ...
</properties>

And then right-click the project in Project Explorer view and choose Maven ➤ Update Project. It will automatically sort out the Java version set in Project Facets based on the above setting in pom.xml.

However, this is not sufficient. If you attempt to create a record via the New Java Record wizard as available by right-clicking the project and choosing New ➤ Record, and fill out the fields, then Eclipse will error out as:

Project 'projectname' does not have preview features enabled.

Or when you manually create a class representing a record without using the wizard, then Eclipse will still error out as:

Type record is a preview feature and disabled by default. Use --enable-preview to enable

Indeed, the Java 14 Records feature is only available as a so-called "preview feature". This means that it's by default disabled. It's only available when you explicitly pass --enable-preview argument to the java/javac command.

This can be done in Eclipse via Java Compiler settings in project-specific properties or in window-global properties. The one in project-specific properties will be overridden every time you run Maven Update. It's better to configure it in window-global properties. Open Window ➤ Preferences ➤ Java ➤ Compiler. In the Compiler section, uncheck the Use default compliance settings option and then check the Enable preview features for Java 14 option.

Apply and close.

Creating and using Java Records in JSF

Once having configured the Eclipse project to use Java Records as instructed in previous chapter, then creating a new record will not anymore error out in Eclipse.

public record Person(
    Long id,
    String email,
    LocalDate dateOfBirth
) {}

It will only still show a warning like below:

You are using a preview language feature that may or may not be supported in a future release

It basically boils down that Java Records in its current form is not guaranteed to be backwards compatible in a newer Java version. For example, in a future Java version, the accessor methods might be adjusted to follow JavaBeans Spec to have the get or is method name prefix. If this is going to happen, then any calls to person.id() in existing code will not compile anymore and should be adjusted to person.getId(). Or perhaps the feature will even be completely removed. This potential tech debt has just to be carefully taken into account.

This is okay for now.

Now, let's create an example JSF bean which uses these records:

@Named
@RequestScoped
public class Bean {

    private List<Person> persons;
	
    @PostConstruct
    public void init() { 
        persons = new ArrayList<>();
        persons.add(new Person(1L, "john.doe@example.com", LocalDate.of(1978, 3, 26)));
        persons.add(new Person(2L, "jane.doe@example.com", LocalDate.of(1980, 10, 31)));
        persons.add(new Person(3L, "joe.bloggs@example.com", LocalDate.of(2002, 10, 5)));
    }
	
    public List<Person> getPersons() {
        return persons;
    }
}

And a XHTML file which just iterates over this list and prints each item plain:

<ui:repeat value="#{bean.persons}" var="person">
    #{person}
    <br/>
</ui:repeat>

After deploying it to a Jakarta EE server such as WildFly 21.0.0, you will face the following warning in server logs and the #{bean} will not be available in EL (newlines introduced for readability):

WARN [org.jboss.modules.define] (Weld Thread Pool -- 1)
    Failed to define class com.example.Bean in Module "deployment.playground-wildfly-0.0.1-SNAPSHOT.war" from Service Module Loader:
    java.lang.UnsupportedClassVersionError:
        Failed to link com/example/Bean (Module "deployment.playground-wildfly-0.0.1-SNAPSHOT.war" from Service Module Loader): 
        Preview features are not enabled for com/example/Bean (class file version 58.65535). 
        Try running with '--enable-preview'
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1096)
    at org.jboss.modules.ModuleClassLoader.doDefineOrLoadClass(ModuleClassLoader.java:424)
    at org.jboss.modules.ModuleClassLoader.defineClass(ModuleClassLoader.java:555)
    at org.jboss.modules.ModuleClassLoader.loadClassLocal(ModuleClassLoader.java:339)
    at org.jboss.modules.ModuleClassLoader$1.loadClassLocal(ModuleClassLoader.java:126)
    at org.jboss.modules.Module.loadModuleClass(Module.java:731)
    at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:247)
    at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:410)
    at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:398)
    at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:116)
    at org.jboss.as.weld@21.0.0.Final//org.jboss.as.weld.WeldModuleResourceLoader.classForName(WeldModuleResourceLoader.java:68)
    at org.jboss.weld.core@3.1.5.Final//org.jboss.weld.bootstrap.AnnotatedTypeLoader.loadClass(AnnotatedTypeLoader.java:82)
    at org.jboss.weld.core@3.1.5.Final//org.jboss.weld.bootstrap.FastAnnotatedTypeLoader.createContext(FastAnnotatedTypeLoader.java:114)
    at org.jboss.weld.core@3.1.5.Final//org.jboss.weld.bootstrap.FastAnnotatedTypeLoader.loadAnnotatedType(FastAnnotatedTypeLoader.java:103)
    at org.jboss.weld.core@3.1.5.Final//org.jboss.weld.bootstrap.BeanDeployer.addClass(BeanDeployer.java:87)
    at org.jboss.weld.core@3.1.5.Final//org.jboss.weld.bootstrap.ConcurrentBeanDeployer$1.doWork(ConcurrentBeanDeployer.java:55)
    at org.jboss.weld.core@3.1.5.Final//org.jboss.weld.bootstrap.ConcurrentBeanDeployer$1.doWork(ConcurrentBeanDeployer.java:52)
    at org.jboss.weld.core@3.1.5.Final//org.jboss.weld.executor.IterativeWorkerTaskFactory$1.call(IterativeWorkerTaskFactory.java:62)
    at org.jboss.weld.core@3.1.5.Final//org.jboss.weld.executor.IterativeWorkerTaskFactory$1.call(IterativeWorkerTaskFactory.java:55)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
    at java.base/java.lang.Thread.run(Thread.java:832)
    at org.jboss.threads@2.4.0.Final//org.jboss.threads.JBossThread.run(JBossThread.java:513)

Note specifically the hint Try running with '--enable-preview'. That's indeed what we need to do.

Doubleclick the WildFly server in Servers view and then click the Open launch configuration link. In the VM arguments field, add the new VM argument --enable-preview as highlighted in the below screenshot:

Restart the server. The warning in server logs is now gone. Opening the example XHTML file will produce this result:

Accessing Java Record "properties" in EL

Trouble will start when you adjust the XHTML to access the "properties" of the Java Record in EL as if they were JavaBean properties:

<h:dataTable value="#{bean.persons}" var="person">
    <h:column>#{person.id}</h:column>
    <h:column>#{person.email}</h:column>
    <h:column>#{person.dateOfBirth}</h:column>
</h:dataTable>

It will throw an exception with the following root cause:

javax.el.ELException: /test.xhtml: The class 'com.example.Person' does not have the property 'id'.
    at com.sun.jsf-impl@2.3.14.SP01//com.sun.faces.facelets.compiler.TextInstruction.write(TextInstruction.java:47)
    at com.sun.jsf-impl@2.3.14.SP01//com.sun.faces.facelets.compiler.UIInstructions.encodeBegin(UIInstructions.java:41)
    at com.sun.jsf-impl@2.3.14.SP01//com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeRecursive(HtmlBasicRenderer.java:276)
    at com.sun.jsf-impl@2.3.14.SP01//com.sun.faces.renderkit.html_basic.TableRenderer.renderRow(TableRenderer.java:374)
    at com.sun.jsf-impl@2.3.14.SP01//com.sun.faces.renderkit.html_basic.TableRenderer.encodeChildren(TableRenderer.java:137)
    at javax.faces.api@3.0.0.SP04//javax.faces.component.UIComponentBase.encodeChildren(UIComponentBase.java:566)
    at javax.faces.api@3.0.0.SP04//javax.faces.component.UIComponent.encodeAll(UIComponent.java:1647)
    at javax.faces.api@3.0.0.SP04//javax.faces.component.UIComponent.encodeAll(UIComponent.java:1650)
    at javax.faces.api@3.0.0.SP04//javax.faces.component.UIComponent.encodeAll(UIComponent.java:1650)
    at com.sun.jsf-impl@2.3.14.SP01//com.sun.faces.application.view.FaceletViewHandlingStrategy.renderView(FaceletViewHandlingStrategy.java:468)
    at com.sun.jsf-impl@2.3.14.SP01//com.sun.faces.application.view.MultiViewHandler.renderView(MultiViewHandler.java:170)
    at javax.faces.api@3.0.0.SP04//javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:132)
    at javax.faces.api@3.0.0.SP04//javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:132)
    at com.sun.jsf-impl@2.3.14.SP01//com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:102)
    at com.sun.jsf-impl@2.3.14.SP01//com.sun.faces.lifecycle.Phase.doPhase(Phase.java:76)
    at com.sun.jsf-impl@2.3.14.SP01//com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:199)
    at javax.faces.api@3.0.0.SP04//javax.faces.webapp.FacesServlet.executeLifecyle(FacesServlet.java:708)
    ... 51 more

What is happening here? Well, EL only understands JavaBeans Spec and the Java Records do not exactly comply the JavaBeans Spec. EL is basically trying to find the getId() method on the record, but there's only the id() method. Hence EL thinks it does not exist. Moreover, in case of Java Records we shouldn't really talk about "properties", but about "fields".

Adjusting EL expressions from the #{bean.property} syntax to the #{record.accessor()} syntax as below:

<h:dataTable value="#{bean.persons}" var="person">
    <h:column>#{person.id()}</h:column>
    <h:column>#{person.email()}</h:column>
    <h:column>#{person.dateOfBirth()}</h:column>
</h:dataTable>

Will make it work in some EL implementations.

It worked for me in WildFly 21, but it has been reported to not work in other EL implementations.

Resolving Java Records as JavaBeans in EL

In case you want to be able to keep using the #{bean.property} syntax to access fields of Java Records, then you can always create a custom ELResolver which detects Java Records and resolves the EL expressions accordingly. This will be very useful when you intend to share some XHTML templates among beans and records with the same properties.

Detecting whether a given class is a Java Record can be done by checking Class#isRecord(). Obtaining all the record components associated with the record class can be done by Class#getRecordComponents(). Given this information, the following custom ELResolver which basically converts record fields to bean properties should do it:

public class RecordELResolver extends ELResolver {

    private static final Map<Class<?>, Map<String, PropertyDescriptor>> RECORD_PROPERTY_DESCRIPTOR_CACHE = new ConcurrentHashMap<>();

    private static boolean isRecord(Object base) {
        return base != null && base.getClass().isRecord();
    }
    
    private static Map<String, PropertyDescriptor> getRecordPropertyDescriptors(Object base) {
        return RECORD_PROPERTY_DESCRIPTOR_CACHE
            .computeIfAbsent(base.getClass(), clazz -> Arrays
                .stream(clazz.getRecordComponents())
                .collect(Collectors
                    .toMap(RecordComponent::getName, recordComponent -> {
                        try {
                            return new PropertyDescriptor(recordComponent.getName(), recordComponent.getAccessor(), null);
                        }
                        catch (IntrospectionException e) {
                            throw new IllegalStateException(e);
                        }
                    })));
    }
    
    private static PropertyDescriptor getRecordPropertyDescriptor(Object base, Object property) {
        PropertyDescriptor descriptor = getRecordPropertyDescriptors(base).get(property);
        
        if (descriptor == null) {
            throw new PropertyNotFoundException("The record '" + base.getClass().getName() + "' does not have the field '" + property + "'.");
        }

        return descriptor;
    }

    @Override
    public Object getValue(ELContext context, Object base, Object property) {
        if (!isRecord(base) || property == null) {
            return null;
        }

        PropertyDescriptor descriptor = getRecordPropertyDescriptor(base, property);

        try {
            Object value = descriptor.getReadMethod().invoke(base);
            context.setPropertyResolved(base, property);
            return value;
        }
        catch (Exception e) {
            throw new ELException(e);
        }
    }

    @Override
    public Class<?> getType(ELContext context, Object base, Object property) {
        if (!isRecord(base) || property == null) {
            return null;
        }

        PropertyDescriptor descriptor = getRecordPropertyDescriptor(base, property);
        context.setPropertyResolved(true);
        return descriptor.getPropertyType();
    }

    @Override
    public Class<?> getCommonPropertyType(ELContext context, Object base) {
        if (!isRecord(base)) {
            return null;
        }

        return String.class;
    }

    @Override
    public boolean isReadOnly(ELContext context, Object base, Object property) {
        if (!isRecord(base)) {
            return false;
        }

        getRecordPropertyDescriptor(base, property); // Forces PropertyNotFoundException if necessary.
        context.setPropertyResolved(true);
        return true; // Java Records are per definition immutable.
    }

    @Override
    public void setValue(ELContext context, Object base, Object property, Object value) {
        if (!isRecord(base)) {
            return;
        }

        getRecordPropertyDescriptor(base, property); // Forces PropertyNotFoundException if necessary.
        throw new PropertyNotWritableException("Java Records are immutable");
    }

    @Override
    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
        if (!isRecord(base)) {
            return null;
        }

        Map rawDescriptors = getRecordPropertyDescriptors(base);
        return rawDescriptors.values().iterator();
    }
}

In order to get it to run, register as below in faces-config.xml:

<application>
    <el-resolver>com.example.RecordELResolver</el-resolver>
</application>

Now you can successfully access record fields in EL as if they were bean properties:

<h:dataTable value="#{bean.persons}" var="person">
    <h:column>#{person.id}</h:column>
    <h:column>#{person.email}</h:column>
    <h:column>#{person.dateOfBirth}</h:column>
</h:dataTable>

Using Java Records as JPA Entities

Unfortunately, due to the immutable nature of Java Records, they are not at all useful as JPA entities. But they are definitely useful as DTOs. This has been blogged by Thorben Janssen and Vlad Mihalcea before.

Imagine that you've an entity with "too many" properties wihch would only unnecessarily select every single column when using SELECT p FROM Person p in JPA:

@Entity
public class Person {

    @Id
    private Long id;
    
    @Column
    private String email;
    
    @Column
    private boolean emailVerified;
    
    @Column
    private String firstName;
    
    @Column
    private String lastName;
    
    @Column
    private LocalDate dateOfBirth;
    
    @Column
    private String phoneNumber;
    
    @Enumerated(STRING)
    private Status status;

    @OneToOne(fetch = LAZY)
    private Image profileImage;
    
    @ManyToOne(fetch = LAZY)
    private Address homeAddress;

    @OneToMany(mappedBy = "person", fetch = LAZY)
    private Set<LoginToken> loginTokens;

    @ElementCollection(fetch = EAGER) @Enumerated(STRING)
    private Set<Group> groups;

    // etc ...
}

Then you can use a Java Record as DTO to select only a specific subset of columns, such as ID, first name, last name and profile image in order to present it in some list of cards:

public record PersonCard(
    Long id,
    String firstName,
    String lastName,
    Image profileImage
) {}

Then you can use the constructor expression in JPQL query as demonstrated in the listCards() method below:

@Stateless
public class PersonService {

    @TransactionAttribute(NOT_SUPPORTED)
    public List<Person> listAll() {
        return entityManager.createQuery(
            """
                SELECT 
                    p
                FROM
                    Person p
            """
            , Person.class)
            .getResultList();
    }

    @TransactionAttribute(NOT_SUPPORTED)
    public List<PersonCard> listCards() {
        return entityManager.createQuery(
            """
                SELECT 
                    new com.example.PersonCard(
                        p.id,
                        p.firstName,
                        p.lastName,
                        pi
                    )
                FROM
                    Person p
                        LEFT JOIN FETCH
                    p.profileImage pi
            """
            , PersonCard.class)
            .getResultList();
    }
}

Notice the Text Blocks syntax """ ... """, which is yet another preview feature, but then already introduced in Java 13. This saves you from fiddling with string-concatenating the newlines in JPQL queries which would only produce barely manageable JPQL queries in Java source code.

This shall work just fine in JSF. For example:

@Named
@RequestScoped
public class Bean {

    private List<PersonCard> personCards;

    @Inject
    private PersonService personService;

    @PostConstruct
    public void init() { 
        personCards = personService.listCards();
    }
	
    public List<PersonCard> getPersonCards() {
        return personCards;
    }
}
<ul>
    <ui:repeat value="#{bean.personCards}" var="personCard">
        <li>
            <a href="person.xhtml?id=#{personCard.id}">
                <img src="#{personCard.profileImage.url}" />
                #{personCard.firstName} #{personCard.lastName}
            </a>
        </li>
    </ui:repeat>
</ul>

That's all, folks :) Happy coding!

Saturday, September 12, 2020

OmniFaces 3.8 o:validateBean improvements and a handful new utilities

OmniFaces 3.8 has been released!

In this version, the <o:validateBean> has been improved to support validating nested properties annotated with @Valid. Previously it only supported validating bean's own properties. Further a handful of new utilities have been added: Beans#isProxy(Object), Beans#unwrapIfNecessary(Object), Components#getExpectedType(ValueExpression), Components#getExpectedValueType(UIComponent), Messages#asConverterException(), Messages#asValidatorException() and #{of:encodeBase64(String)}.

You can find the complete list of additions, changes and fixes at What's new in OmniFaces 3.8.1? list in showcase.

Installation

Non-Maven users: download OmniFaces 3.8.1 JAR and drop it in /WEB-INF/lib the usual way, replacing the older version if any.

Maven users: use <version>3.8.1</version>.

<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>3.8.1</version>
</dependency>

If you're already on Jakarta EE 9 (e.g. GlassFish 6), then use 4.0-M3 instead. It's the Jakartified version of 3.8.1.

Validating nested properties

The <o:validateBean> now finally supports validating nested properties which are annotated with the @javax.validation.Valid cascade. Previously this was not supported because nested properties were not automatically copied into the shadow bean, and the violation messages were not associatable with the input components. Both issues have been resolved with help of Andre Wachsmuth.

Here's an example with an imagined Setting model:

@Named
@ViewScoped
public class Bean implements Serializable {

    @Valid
    private List<Setting> settings;

    @Inject
    private SettingService settingService;

    @PostConstruct
    public void init() {
        settings = settingService.list();
    }

    public void save() {
        settingService.save(settings);
    }

    public List<Setting> getSettings() {
        return settings;
    }
}
@ValidSetting
public class Setting {

    public enum Type {
        STRING, BOOLEAN, NUMBER, DECIMAL;
    }

    @NotNull
    private String name;

    @NotNull
    private Type type;

    @NotNull
    private String value;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public Boolean getAsBoolean() {
        return type == Type.BOOLEAN ? Boolean.parseBoolean(getValue()) : null;
    }

    public Long getAsNumber() {
        return type == Type.NUMBER ? Long.valueOf(getValue()) : null;
    }

    public BigDecimal getAsDecimal() {
        return type == Type.DECIMAL ? new BigDecimal(getValue()) : null;
    }
}
@Constraint(validatedBy = SettingValidator.class)
@Documented
@Target({ TYPE, METHOD, FIELD })
@Retention(RUNTIME)
public @interface ValidSetting {
    String message() default "Invalid setting value";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
public class SettingValidator implements ConstraintValidator<ValidSetting, Setting>{

    @Override
    public boolean isValid(Setting setting, ConstraintValidatorContext context) {
        if (setting == null) {
            return true; // Let @NotNull handle it.
        }
        else {
            return isValid(setting);
        }
    }

    public static boolean isValid(Setting setting) {
        switch (setting.getType()) {
            case STRING:
                return true;

            case BOOLEAN:
                return setting.getValue().matches("(true|false)");

            case NUMBER:
                try {
                    setting.getAsNumber();
                    return true;
                }
                catch (NumberFormatException e) {
                    return false;
                }

            case DECIMAL:
                try {
                    setting.getAsDecimal();
                    return true;
                }
                catch (NumberFormatException e) {
                    return false;
                }

            default:
                return false;
        }
    }
}
<h:form>
    <h:dataTable value="#{bean.settings}" var="setting">
        <h:column>
            <f:facet name="header">Name</f:facet>
            #{setting.name}
        </h:column>
        <h:column>
            <f:facet name="header">Type</f:facet>
            #{setting.type}
            <h:inputHidden id="type" value="#{setting.type}" />
        </h:column>
        <h:column>
            <f:facet name="header">Value</f:facet>
            <h:inputText id="value" value="#{setting.value}" />
            <h:message id="value_m" for="value" />
        </h:column>
    </h:dataTable>
    
    <o:validateBean value="#{bean}" showMessageFor="@violating" />
    
    <h:commandButton value="Save" action="#{bean.save}">
        <f:ajax execute="@form" render="@messages" />
    </h:commandButton>
</h:form>

In this use case, the <o:validateBean> will cascade into @Valid and execute SettingValidator for each setting. The showMessageFor="@violating" will associate each message with the violating input, which shall be the <h:inputText id="value">.

Note that the render="@messages" is also specific to OmniFaces, it's handled by the MessagesKeywordResolver, just in case you didn't knew ;)

Generic converter for @Param

The @Param has also been improved to pass the expected type into the UIComponent passed around any associated validators/converters. In case of so-called "generic entity converters" this now allows you to inspect the expected type as well. This has always worked fine for <f|o:viewParam>, but it did previously not work with @Param.

For example, this <f|o:viewParam> approach:

<f:viewParam name="id" value="#{bean.product}" />
private Product product; // +getter +setter
public class Product extends BaseEntity {
    // ...
}
@FacesConverter(forClass = BaseEntity.class)
public class BaseEntityConverter implements Converter<BaseEntity> {

    @Inject
    private BaseEntityService service;
    
    @Override
    public String getAsString(FacesContext context, UIComponent component, BaseEntity modelValue) {
        return modelValue == null ? "" : modelValue.getId().toString();
    }

    @Override
    public BaseEntity getAsObject(FacesContext context, UIComponent component, String submittedValue) {
        if (submittedValue == null) {
            return null;
        }

        try {
            Class<? extends BaseEntity> type = Components.getExpectedValueType(component);
            Long id = Long.valueOf(submittedValue);
            return service.find(type, id);
        }
        catch (Exception e) {
            throw Messages.asConverterException("Cannot convert because it threw " + e);
        }
    }
}

Will now also work fine when using @Param instead of <f|o:viewParam>.

@Param(name = "id")
private Product product; // no getter/setter

How about OmniFaces 2.x and 1.1x?

The 2.x got the same bugfixes as 3.8.1 and has been released as 2.7.8. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, use 3.x instead.

The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical bugfix versions will be released. It's currently still at 1.14.1 (May 2017), featuring the same features as OmniFaces 2.4, but without any JSF 2.2 and CDI things and therefore compatible with CDI-less JSF 2.0/2.1.

Saturday, July 18, 2020

OmniFaces 4.0-M1 ready for testing!

This is the first milestone release of OmniFaces 4.0, which is the first version developed specifically for JSF 3.0 from Jakarta EE 9 which is currently scheduled to be released at September 2020! OmniFaces 4.x is NOT backwards compatible with earlier Java EE / JSF versions because of the migration of the javax.* package to jakarta.* package.

Noted should be that Jakarta EE 9 initially went for Java 11 as minimum Java version, as can be seen in pom.xml of RC1, but this was unfortunately since RC2 switched back to Java 1.8 due to time constraints. Hence OmniFaces 4.0 is also still at Java 1.8.

The integration tests currently run successfully on GlassFish 6.0.0.M1.

Breaking changes:

  • Obviously, the migration from javax.* package to jakarta.* package. E.g. javax.faces.context.FacesContext is now available at jakarta.faces.context.FacesContext. This is however a general change in Jakarta EE itself. Jakarta EE 8 did still use the javax.* package, but Jakarta EE 9 uses now its own jakarta.* package. OmniFaces own package org.omnifaces.* is still the same as it is.
  • The date beans #{now} and #{startup} have been migrated from java.util.Date to java.time.Instant. This means that e.g. #{now.time} does now not work anymore as the java.time.Instant does not have a getTime() method. EL 4.0 will throw the following exception:
    jakarta.el.ELException: /test.xhtml: The class 'java.time.Instant' does not have the property 'time'.
    Sorry about that, but it was high time to migrate to the much better java.time API! You need to modify your JSF files to catch up this. Use e.g. #{now.epochSecond}, #{now.nano}, or #{now.toEpochMilli()} instead.
    UPDATE: as per the upcoming 4.0-M2 these will continue supporting #{now.time} format! And additionally these will offer #{now.instant} and #{now.zonedDateTime} properties to give you concrete instances of java.time.Instant and java.time.ZonedDateTime.
  • The <o:form includeViewParams="true"> which was deprecated since 3.0 has now been removed because this is already the default behavior.
  • The WebXml.INSTANCE and FacesConfigXml.INSTANCE which were deprecated since 3.1 have now been removed. Use WebXml.instance() and FacesConfigXml.instance() instead. This was done because they can then be @Inject'ed in a CDI bean.
  • The script omnifaces:fixviewstate.js which was deprecated since 3.0 has now been removed. This script is unnecessary since JSF 2.2 as it was fixed over there.

No new things are added, so the existing features are basically the same as with the current 3.7.1 version. JSF 3.0 itself is technically also basically the same as JSF 2.3, except for the change of the package from javax.faces.* to jakarta.faces.*. In other words, it has just been 'jakartified' ;)

Also note that the showcase still runs 3.7.1 (for now). Once WildFly comes with a final JEE9 version, it'll be updated over there as well.

Installation

Non-Maven users: download OmniFaces 4.0-M1 JAR and drop it in /WEB-INF/lib the usual way, replacing the older version if any.

Maven users: use <version>4.0-M1</version>.

<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>4.0-M1</version>
</dependency>

If you have found any issues or comments related to OmniFaces 4.0, please by all means report an issue.

Migration of your project from Java EE 8 / Jakarta EE 8 to Jakarta EE 9

Use this dependency (note: keep an eye on newer RC/M versions and bump accordingly):

<dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-api</artifactId>
    <version>9.0.0-RC2</version>
    <scope>provided</scope>
</dependency>

Then it's really basically a matter of performing a find&replace of the literal string "javax." to "jakarta." throughout the entire project! Perhaps a few things will not compile because they are still javax.*, but it's a matter of renaming them back, which you can then do a bit more specific. For example, javax.xml.* and javax.sql.* are still in Java SE. You should then find&replace e.g. the "jakarta.xml." back to "javax.xml.". Oh, don't forget to rename the files in META-INF/services, if any ;)

Monday, July 13, 2020

OmniFaces 3.7 adds autogenerated sw.js, o:inputHidden and of:stripTags()

OmniFaces 3.7 has been released!

In this version, the WebAppManifestResourceHandler got a new feature: auto-generating an offline-aware service worker file sw.js based on welcome files in web.xml and the configuration in your custom WebAppManifest implementation. With this new feature, the resource handler was renamed to PWAResourceHandler. Further a new component <o:inputHidden> component and a new EL function #{of:stripTags(string)} have been added.

You can find the complete list of additions, changes and fixes at What's new in OmniFaces 3.7.1? list in showcase.

Installation

Non-Maven users: download OmniFaces 3.7.1 JAR and drop it in /WEB-INF/lib the usual way, replacing the older version if any.

Maven users: use <version>3.7.1</version>.

<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>3.7.1</version>
</dependency>

PWAResourceHandler

A WebAppManifestResourceHandler was in OmniFaces 3.6 introduced. This PWAResourceHandler takes it a step further. Previously, it generated only a manifest.json file, which is one of the minimum requirements for a Progressive Web Application (PWA), but now it will also generate an offline-aware sw.js along it which should satisfy the PWA test done by Chrome's Lighthouse tool.

Basically, it will collect all welcome files found in web.xml, build them as JSF views, collect all JSF resource dependencies (any x:outputStylesheet, x:outputScript and x:graphicImage components) found in their component trees, and finally register all of them as offline-available. I.e. the home page should now respond 200 to Lighthouse.

All you need to do is to extend from the org.omnifaces.resourcehandler.WebAppManifest class, implement and override the desired getter methods, put a CDI scope annotation on it such as @ApplicationScoped, and finally reference it in your Facelet template exactly as below:

<link rel="manifest" href="#{resource['omnifaces:manifest.json']}" />

Furthermore, you will also be given the opportunity to explicitly register a special fallback/error page for the "You are offline!" condition. You can do this by overridding the WebAppManifest#getOfflineViewId() method to return the desired JSF view ID.

@Override
public String getOfflineViewId() {
    return "/offline.xhtml";
}

See also the showcase.

If you however would like to make use of the manifest.json alone and disable the offline-aware sw.js feature altogether, then simply override the WebAppManifest#getCacheableViewIds() to return an empty collection. By default, it returns the JSF view IDs of the welcome files encountered in web.xml.

@Override
public Collection<String> getCacheableViewIds() {
    return Collections.emptyList();
}

o:inputHidden

The existing <h:inputHidden> is slightly unintuitive. When the JSF form it is enclosed in is submitted, and another input component within the same form fails conversion or validation, then the value of the <h:inputHidden> won't be set in the backing bean. This makes sense for values submitted by the client, but this doesn't make sense for values submitted by server-controlled code. This way the <h:inputHidden> isn't terribly useful in order to retain request scoped bean properties across postbacks. One solution would be to convert the request scoped bean to a view scoped bean, but this isn't always desireable. It would be undesired when the JSF page in question is required to be entirely stateless.

The new <o:inputHidden> solves this by immediately performing the conversion/validation and updating the model values during the apply request values phase. Of course you could also use a plain vanilla HTML <input type="hidden"> for this in combination with manually grabbing the request parameter, but this is cumbersome and it doesn't transparently support JSF converters.

See also the live demo.

of:stripTags

In case you have a bean property representing a string with potentially some HTML tags, which should be used as some preview string, then you can use the new of:stripTags() function to strip out all HTML tags, leaving only plain text.

Usage example:

#{of:stripTags(bean.html)}

See also the showcase.

What happened to OmniFaces 3.7?

Directly after the 3.7 release I noticed that the sw.js was performing horribly on the showcase site :( These performance problems were unfortunately not immediately visible while developing and testing at localhost. I considered these performance issues to be important enough to warrant a quick hotfix release, so 3.7.1 was released the same day with performance fixes.

So, do not use 3.7 if you plan to make use of the PWAResourceHandler for the autogenerated offline-aware sw.js :)

How about OmniFaces 2.x and 1.1x?

The 2.x got the same bugfixes as 3.7.1 and has been released as 2.7.6. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, use 3.x instead.

The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical bugfix versions will be released. It's currently still at 1.14.1 (May 2017), featuring the same features as OmniFaces 2.4, but without any JSF 2.2 and CDI things and therefore compatible with CDI-less JSF 2.0/2.1.

Sunday, May 3, 2020

OmniFaces 3.6 adds manifest.json generator, o:scriptParam, and o:pathParam

OmniFaces 3.6 has been released!

Next to a bunch of utility methods, this version adds a WebAppManifestResourceHandler which autogenerates the manifest.json based on properties of a CDI bean, the <o:scriptParam> which allows you to set evaluated JavaScript results in a managed bean, and a <o:pathParam> which can be used in UIOutcomeTarget components (such as <h:link>) to populate desired path parameters for pages covered by MultiViews.

You can find the complete list of additions, changes and fixes at What's new in OmniFaces 3.6? list in showcase. One of most important changes is that the of:formatDateXxx() functions now also support java.time.Temporal instances.

Installation

Non-Maven users: download OmniFaces 3.6.1 JAR and drop it in /WEB-INF/lib the usual way, replacing the older version if any.

Maven users: use <version>3.6.1</version>.

<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>3.6.1</version>
</dependency>

WebAppManifestResourceHandler

This resource handler takes care of automatically generating a manifest.json file, which is one of the minimum requirements for a Progressive Web Application (PWA). All you need to do is to extend from the org.omnifaces.resourcehandler.WebAppManifest class, implement and override the desired getter methods, put a CDI scope annotation on it such as @ApplicationScoped, and finally reference it in your Facelet template exactly as below:

<link rel="manifest" href="#{resource['omnifaces:manifest.json']}" />

And you're done! See also the showcase.

o:scriptParam

This new little brother of <hashParam> is capable of running some JavaScript code on page load, collect its results and populate the managed bean with them. The @PostScriptParam annotation can be used to invoke a method after the script params have been populated.

For example,

<f:metadata>
    <o:scriptParam script="new Date().timeZoneOffset()" value="#{bean.clientTimeZoneOffset}" />
</f:metadata>
private Integer clientTimeZoneOffset; // +getter +setter

@PostScriptParam
public void initScriptParams() {
    System.out.println("The client time zone offset is: " + clientTimeZoneOffset);
}

See also the live demo.

o:pathParam

In case you're using MultiViews, i.e. when you have for example a /users.xhtml page which can be opened as /users/42/john-doe, then you can use the new <o:pathParam> to safely URI-encode the path parameters into any link to /users.xhtml.

For example,

<ui:repeat value="#{bean.users}" var="user">
    <h:link outcome="/users" value="#{user.name}">
        <o:pathParam value="#{user.id}">
        <o:pathParam value="#{user.slug}">
    </h:link>
</ui:repeat>

See also the showcase.

How about OmniFaces 2.x and 1.1x?

The 2.x got the same bugfixes as 3.6 and has been released as 2.7.5. This version is for JSF 2.2 users with CDI. In case you've already migrated to JSF 2.3, use 3.x instead.

The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical bugfix versions will be released. It's currently still at 1.14.1 (May 2017), featuring the same features as OmniFaces 2.4, but without any JSF 2.2 and CDI things and therefore compatible with CDI-less JSF 2.0/2.1.

Saturday, April 18, 2020

JSF 2.3 tutorial with Eclipse, Maven, WildFly and H2

Introduction

In this tutorial you will learn how to set up a JSF (Jakarta Faces) 2.3 development environment with the Eclipse IDE, the Maven dependency management system, the WildFly application server, and the H2 database from scratch.

Noted should be this tutorial was originally written by me for the “From Zero to Hello World” chapter of the book The Definitive Guide to JSF in Java EE 8, published at May 30, 2018. But as technology changes quickly, that chapter is currently slightly outdated. This tutorial is basically a rewrite of that chapter conform the current state of technology, 2 years later. Moreover, it covered Payara application server only while we’d like to use WildFly application server instead (whose JEE8 version wasn’t yet available at time of writing of the book, around Q4 of 2017).

Update March 2022: Also noted should be that there's already a newer version of the book available which among others helps setting up Jakarta Faces 4.0 from scratch: The Definitive Guide to Jakarta Faces in Jakarta EE 10, published at March 16, 2022.

Back to top

Installing Java SE JDK

You probably already know that Java SE is available as a JRE for end users and as a JDK for software developers. Eclipse itself does not strictly require a JDK as it has its own compiler. JSF being a software library does not require a JDK to run either. WildFly, however, does require a JDK to run, primarily in order to be able to compile JSP files, even though JSP has been deprecated as JSF view technology since JSF 2.0.

Therefore, you need to make sure that you already have a JDK installed as per Oracle’s instructions. The current Java SE version is 14, but as Jakarta EE 8 was designed for Java SE 8, you could get away with a minimum version of Java SE 8. Installation instructions depend on the platform being used (Windows, Linux, or macOS). You can find detailed Java SE 14 JDK installation instructions here.

The most important parts are that the PATH environment variable covers the /bin folder containing the Java executables (e.g., /path/to/jdk/bin), and that the JAVA_HOME environment variable is set to the JDK root folder (e.g., /path/to/jdk). This is not strictly required by JSF, but Eclipse and WildFly need this. Eclipse will need the PATH in order to find the Java executables. WildFly will need the JAVA_HOME in order to find the JDK tools.

Back to top

What About Jakarta EE?

First a small bit of history: “Java EE” has been renamed to “Jakarta EE” in February 2018, and the first Jakarta EE release, the Jakarta EE 8, became available in September 2019. So, anything related to “Java EE” is currently outdated. This includes the currently still available “Java EE SDK” download from Oracle.com. You should ignore that.

JSF itself is part of Jakarta EE. Jakarta EE is basically an abstract specification of which the so-­called application servers represent the concrete implementations. Examples of those application servers are Eclipse Glassfish, JBoss EAP, JEUS, Open Liberty, Payara Server, Primeton Appserver and WildFly. You can find them all at the Jakarta EE Compatible Products page. It are exactly those application servers that actually provide among others JSF, EL (Expression Language), JSTL (Jakarta Server Tag Library), CDI (Contexts and Dependency Injection), EJB (Enterprise JavaBeans), JPA (Java Persistence API), Servlet, WebSocket, and JSON-P APIs out of the box.

There also exist so-called servlet containers which provide basically only the Servlet, JASPIC, JSP, EL, and WebSocket APIs out of the box, such as Tomcat and Jetty. Therefore, it would require some work to manually install and configure among others JSF, JSTL, CDI, EJB, JPA and/or JSON-P on such a servlet container. It is not even trivial in the case of EJB as it basically requires modifying the servlet container’s internals. That is, by the way, exactly why TomEE exists. It’s a Java EE application server built on top of the barebones Tomcat servlet container engine. Note that TomEE is at the time of writing still not Jakarta EE compatible, so it’s not listed in the Jakarta EE Compatible Products page.

Back to top

Installing WildFly

WildFly is an open source Jakarta EE application server from Red Hat. You can download it from wildfly.org. Make sure you choose the “Jakarta EE 8 Full & Web Distribution" download and not, for example, the “WildFly Preview EE 9 Distribution” or “Servlet-Only Distribution”. It's available as WildFly versions 17.x until 26.x. We of course want the latest available one, so let's pick 26.1.3. Here’s a direct link to the ZIP file: 26.1.3.Final.

Installing is basically a matter of unzipping the downloaded file and putting it somewhere in your home folder. We’ll leave it there until we have Eclipse up and running, so that we can then integrate WildFly in Eclipse and let Eclipse manage the WildFly application server.

Back to top

Installing Eclipse

Eclipse is an open source IDE written in Java. You can download it from eclipse.org. It is basically like notepad but then with thousands if not millions of extra features, such as automatically compiling class files, building a WAR (Web Application Archive) file with them, and deploying it to an application server without the need to manually fiddle around with javac in a command console.

Eclipse is available in a lot of flavors, even for C/C++ and PHP. As we’re going to develop with Jakarta EE, we need the one saying “Eclipse IDE for Enterprise Java developers”, importantly the one with “Enterprise” in its name. The one without it doesn’t contain the mandatory plug-ins for developing Jakarta EE web applications. Here’s a direct link to the main download page of currently available latest version: 2023-12. In the section “Download Links” you can find platform-specific versions (Windows, Linux, or macOS).

Also here, installing is basically a matter of unzipping the downloaded file and putting it somewhere in your home folder.

In Windows and Linux you’ll find the eclipse.ini configuration file in the unzipped folder. In Mac OS this configuration file is located in Eclipse.app/Contents/Eclipse. Open this file for editing. We want to increase the allocated memory for Eclipse. At the bottom of eclipse.ini, you’ll find the following lines:

-Xms256m
-Xmx2048m

This sets, respectively, the initial and maximum memory size pool which Eclipse may use. This is a bit too low when you want to develop a bit decent Jakarta EE application. Let’s at least double both the values:

-Xms512m
-Xmx4g

Watch out that you don’t declare more than the available physical memory in the Xmx setting. When the actual memory usage exceeds the available physical memory, it will continue into virtual memory, usually in a swap file on disk. This will greatly decrease performance and result in major hiccups and slowdowns.

Now you can start Eclipse by executing the eclipse executable in the unzipped folder. You will be asked to select a directory as workspace. This is the directory where Eclipse will save all workspace projects and metadata.

After that, Eclipse will show a welcome screen. This is not interesting for now. You can click the Workbench button on the right top to close the welcome screen. Untick if necessary “Always show Welcome at start up” on the bottom right. After that, you will enter the workbench. By default, it looks like this:

Back to top

Configuring Eclipse

Before we can start writing code, we would like to fine-tune Eclipse a bit so that we don’t eventually end up in trouble or with annoyances. Eclipse has an enormous amount of settings, and some of its default values should not have been the default values. You can verify and configure the settings via Window ➤ Preferences.

  • General ➤ Workspace ➤ Text file encoding must be set to UTF-­8. Particularly in Windows this might otherwise default to the proprietary encoding CP-1252 which supports only 255 different characters and does not support any characters beyond the Latin range. When reading and saving Unicode files with CP-1252, you risk seeing unintelligible sequences of characters. This is also called “Mojibake”. For more background information on character encodings, see my old but still very relevant blog article Unicode - How to get the characters right?
  • General ➤ Workspace ➤ New text file line delimiter must be set to Unix. It works just fine on Windows as well. This will particularly keep version control systems happy. Otherwise, developers pulling code on different operating systems might face confusing conflicts or diffs caused by different line endings.
  • General ➤ Editors ➤ Text editors ➤ Spelling should preferably be disabled. This will save you from a potentially big annoyance, because it unnecessarily also spellchecks technical entries in XML configuration files such as faces-config.xml and web.xml for nothing, causing confusing warnings in those files such as “*.xhtml is not correctly spelled”.
  • Java ➤ Installed JREs must be set to the JDK, not to the JRE. This setting will normally also be used to execute the integrated application server which usually requires the JDK.
  • Java ➤ Compiler ➤ Compiler compliance level must be set to at least 1.8. This is the minimum required Java version for Jakarta EE 8. You can of course also pick a higher version. For example 11 will work just fine. We’ll use that version in the remainder of this tutorial. It’s also going to be the minimum required Java version of the upcoming Jakarta EE 9.
Back to top

Integrating New Server in Eclipse

We need to familarize Eclipse with any installed application server so that Eclipse can seamlessly link its Jakarta EE API libraries in the project’s build path (read: the compile time classpath of the project). This is mandatory in order to be able to import classes from the Jakarta EE API in your project. You know, the application server itself represents the concrete implementation of the abstract Jakarta EE API.

In order to integrate a new application server in Eclipse, first check the bottom section of the workbench with several tabs representing several Views (you can add new ones via Window ➤ Show View). Click the Servers tab to open the servers view.

Click the link which literally says “No servers are available. Click this link to create a new server...”. It’ll show the New Server wizard, displaying a list of available server types. From the list, select Red Hat JBoss Middleware ➤ JBoss AS, WildFly, & EAP Server Tools.

After clicking Next, it will download the plug-in in the backgroud and request you to accept the license agreement before installing the plug-in. This plug-in is mandatory in order to manage any JBoss server from inside the workbench. This will allow you to add and remove Eclipse projects to the deployments folder of the server, as well as starting and stopping the server, and running the server in debug mode.

Let it finish downloading and installing the plug-in in the background. Confirm any possible warning of “unsigned software”. This is okay. Finally it will request you to restart Eclipse. Take action accordingly.

Once returned into the workspace, click the same link in the Servers view again. You’ll now see a JBoss Community ➤ WildFly option.

Advance to the next step. Just leave this screen default.

Advance to the next step. Here, you should point the Home Directory field to the folder of the WildFly installation, there where you have unzipped it after downloading. And, you should adjust the Execution Environment to the desired JDK version. Pick JavaSE-11.

Complete the remainder of the New Server wizard with default settings. You don’t need to edit any other fields. The newly added server will now appear in the Servers view.

Back to top

Creating New Project in Eclipse

We’re now ready to create a new project for our JSF application in Eclipse. This can be done via the left section of the workbench which by default shows only one tab representing the Project Explorer view (also here, you can add new views via Window ➤ Show View). By default, when there are no projects in your workspace, then it shows a list of options to create a new project. These options are also available via File ➤ New.

Eclipse, being an IDE for many different project tasks, offers a bewildering amount of different project types from which to choose. For a Jakarta EE-based application which is going to be deployed as a simple WAR file, there are basically two project types that we could choose from: Dynamic Web Project or Maven Project.

The difference is that the first is an Eclipse native project that really only works on Eclipse, while the latter is a universal type of project that can be built by any IDE, as well as easily on the command line and by various CI servers such as Travis and Jenkins. For this reason, the Maven project type is really the only viable choice.

In the next step, make sure that the option Create a simple project (skip archetype selection) is checked.

This will let us start with a really empty Maven project so that we can configure and populate it ourselves. Of course, you could also choose from an archetype, which is basically a template project with several already prepared files and configurations. But we don’t need any for now.

In the next step, we can specify our own Maven coordinates of the project. The Maven coordinates consist of, among others, Group Id, Artifact Id, and Version, also known as GAV in the Maven world. The Group Id usually matches the root package name you’re going to use, such as com.example. The Artifact Id usually represents the project name you’re going to use. For simplicity, we’ll use project. The Version can be kept default at 0.0.1-SNAPSHOT. Finally the Packaging must be set to war (Web Application Archive). This will during the build procude a WAR file instead of a JAR file.

Now click Finish. You don’t need to edit any other fields. Once you’ve finished the wizard, you’ll get to see the project structure in the Project Explorer view.

Unfortunately, the Eclipse-generated pom.xml, which is the main indicator of the project being a Maven project and containing its configuration, is less than ideal. It’s not current any more, even when generated by the latest Eclipse.

You can already see that by the pom.xml file being marked with an alarming red cross and an error message in the Markers view. Any project that has at least one such red cross cannot be built and won’t be deployable.

The error message literally says “web.xml is missing and <failOnMissingWebXml> is set to true.” In other words, Maven somehow thinks that it’s still a pre-Java EE 6 project, when this was indeed disallowed. In order to solve this problem and to catch up the Eclipse-generated pom.xml with the current standards, we need to open pom.xml for editing and adjust it to look exactly like follows:

<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
        http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>project</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-api</artifactId>
            <version>8.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

Once you save this file, Eclipse will automatically sort it out by itself and clear out the alarming red cross. Now that looks much better.

We’ll briefly go through the most important settings here.

  • Packaging war — indicates the project is a “web” project, and that the project’s contents will be assembled into a WAR (Web Application Archive) file.
  • Encoding UTF-8 — sets the encoding that the source files are in and with which the (reporting) output files should be generated. This makes the build repeatable, as it otherwise would default to the system default encoding.
  • Compiler 11 — sets the version of Java SE used in the .java source files as well as the byte code output in the .class files. Without setting this, Maven defaults to the oldest version possible, and sometimes even a lower version than that.
  • failOnMissingWebXml false — older versions of Java EE required the /WEB-INF/web.xml file to be physically present in the WAR project. Even though this has not been required any more since Java EE 6, which was released in 2009, Maven still checks for this file to be present. Setting this to false prevents this unnecessary check.
  • Dependency jakarta.platform:jakarta.jakartaee-api:8.0.0:provided — this declares a dependency on the Jakarta EE 8 API, and makes sure all the Jakarta EE types like FacesServlet, @Named and @Entity are known to the compiler. This dependency is set to provided since those types are in our case already provided by the target runtime, which is WildFly 18, a Jakarta EE 8 compatible application server. This dependency will then only be used to compile the source code against, and the associated JAR files won’t be included in the generated WAR file. You need to make absolutely sure that any compile time dependency which is already provided by the target runtime is set to provided; otherwise it will eventually end up in the generated WAR file and you may run into class loading trouble wherein duplicate different versioned libraries are conflicting with each other. In case you’re actually not targeting a full-fledged Jakarta EE server but a barebones servlet container, such as Tomcat, then you would need to adjust the dependencies as instructed in the installation instructions of Mojarra, one of the available JSF implementations and actually the one used under the covers of WildFly.

Now, in Eclipse’s Markers view, there are two warnings left which say “The compiler compliance specified is 1.5 but a JRE 14 is used” and “Build path specifies execution environment J2SE-1.5. There are no JREs installed in the workspace that are strictly compatible with this environment”. Well, this basically means that Eclipse recognizes this Maven project as a Java SE 1.5-only project while we don’t actually have Java SE 5 installed, and in spite of the compiler version in pom.xml being set to 11. This is an Eclipse+Maven quirk. Eclipse clearly knows how to find the configured compiler version in pom.xml, but unfortunately refuses to automatically adjust its project settings based on that.

In order to tell Eclipse that this is really a Java SE 11 targeted project, we need to right-click the project in Project Explorer view and choose Properties. In the Project Facets section you should change the version of the “Java” entry from 1.5 to 11 (or whatever compiler version specified in pom.xml).

While at it, we also need to update the Servlet API version and add the JSF and JPA facets. The Servlet API is represented by the “Dynamic Web Module” entry. This needs to be set to version 4.0, which matches Jakarta EE 8. Further the “JavaServer Faces” and “JPA” entries need to be selected.

As you can see in the yellow warning bar, Eclipse requires further configuration. This concerns the newly selected JSF and JPA facets. Click the link. We get to see the Modify Faceted Project wizard.

The first step of the Modify Faceted Project wizard allows us to configure the JPA facet. We need to make sure that Eclipse is being instructed that the JPA implementation is already provided by the target runtime and thus Eclipse doesn’t need to include any libraries. This can be achieved by choosing the “Disable Library Configuration” option in the JPA implementation field.

As we’re going to use the WildFly-provided Hibernate as the actual JPA implementation, which supports automatically discovering of @Entity annotated classes, we’d like to instruct Eclipse to do the same. So, choose the “Discover annotated classes automatically” option in the Persistent class management field. Otherwise Eclipse would automatically add entities to the persistence.xml file when going through the entity code generation wizards, or it would show warnings when we manually create one and don’t add it to the persistence.xml.

Note that configuring a database connection (for Eclipse’s built-in data explorer) is not necessary for now as we’re going to use an embedded database.

In the next step of the Modify Faceted Project wizard, we can configure the JSF capabilities. Also here, we need to make sure that Eclipse is being instructed that the JSF implementation is already provided by the target runtime and thus Eclipse doesn’t need to include any libraries. This can be achieved by choosing the “Disable Library Configuration” option in the JSF Implementation Library field.

Further, we need to rename the servlet name of the FacesServlet to match the fictive instance variable name: facesServlet. Last but not least, we absolutely need to change the URL mapping pattern from the jurassic /faces/* to the modern *.xhtml.

Actually, the entire registration of the FacesServlet in web.xml is since JSF 2.2 not strictly necessary any more; you could even uncheck the Configure JSF servlet in deployment descriptor option and rely on the default auto-registered mappings of /faces/*, *.faces, *.jsf and *.xhtml. However, as this allows endusers and even searchbots to open the very same JSF page by different URLs, and thus causes confusion among endusers and duplicate content penalties among searchbots, we’d better restrict to only one explicitly configured URL pattern of *.xhtml. For more background information on JSF URL patterns, see also this Stack Overflow post.

Now, finish and apply all the wizards and dialogs.

Unfortunately, the JPA plug-in of the current Eclipse version only puts the generated persistence.xml in the wrong place. It’s basically not being aware of the project being a Maven-based WAR project and Eclipse’s JPA plug-in blindly puts it in the src/main/java/META-INF folder as if it were a JAR project. This is wrong. You need to manually move it into src/main/resources/META-INF folder conform the rules of a Maven-based WAR project. The workbench must now look like as follows:

Back to top

Adjusting Deployment Descriptors

We only need to verify and adjust all the deployment descriptors to catch up to the Servlet, JSF, JPA, and CDI versions actually used by the target runtime. This is normally done by editing the root element of the deployment descriptor XML file to set the desired version attribute along with version-specific XML schema definitions (XSDs).

You can find all Jakarta EE 8 XSDs at http://xmlns.jcp.org/xml/ns/javaee, which is an actual web page which currently redirects to some landing page at Oracle.com. This may change in the future given that Jakarta EE 8 is currently in the process of being transferred from Oracle to Eclipse, and moreover the javaee part may in the future even be renamed. At this landing page, click the “Java EE 8 Schema Resources” link to see a list of the correct XSD versions. Yes, the Java EE 8 XSDs are still appplicable to Jakarta EE 8.

You can open the deployment descriptor XML file for editing by double-clicking it in the Project Explorer and then selecting the Source tab in the editor. The correct root element declarations for Jakarta EE 8 compatible deployment descriptors are thus as follows:

1. src/main/webapp/WEB-INF/web.xml for Servlet 4.0:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="4.0"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
        http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
>
    <!-- Servlet configuration here. -->
</web-app>

Make sure that the FacesServlet as we created in the Modify Faceted Project wizard is indeed there and that it’s indeed mapped on an URL pattern of *.xhtml.

Unfortunately, the currently available Eclipse version might annoyingly freeze for ~15 seconds during saving the web.xml file when its xsi:schemalocation references XSD file of version 4.0. This is caused by Eclipse Bug 534776. There is so far no clear work around yet. You could only remove the xsi:schemalocation attribute to avoid this from happening. The disadvantage of removing this is that the contents of the web.xml file cannot anymore be autocompleted during typing, nor be validated by Eclipse, and thus e.g. typos may slip through.

2. src/main/webapp/WEB-INF/faces-config.xml for JSF 2.3:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="2.3"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
        http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd"
>
    <!-- JSF configuration here. -->
</faces-config>

As of now it’s indeed empty.

3. src/main/resources/META-INF/persistence.xml for JPA 2.2:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
        http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
>
    <!-- JPA configuration here. -->
</persistence>

As of it indeed only contains the default persistence unit. We’ll reconfigure it later on.

4. src/main/webapp/WEB-INF/beans.xml for CDI 2.0:

For sake of completeness we need to manually create one more deployment descriptor file, the one for CDI 2.0. This isn’t automatically generated as it’s not required. CDI is by default always enabled in any Jakarta EE 8 compatible web application. It’s even mandatory for the functioning of JSF. Among others the new <f:websocket> relies fully on CDI for tracking the opened web sockets.

Right-click the WEB-INF folder of the project and choose New ➤ File. Give it the name beans.xml. Populate the new file as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans version="2.0" bean-discovery-mode="annotated"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
        http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
>
    <!-- CDI configuration here. -->
</beans>

Note that the bean-discovery-mode is set to annotated. This is the default value. The other value is all. This is slightly more intrusive and expensive as it will consider any Java class as a potential CDI bean.

Back to top

Configuring JSF

JSF has a lot of settings available which can be configured via <context-param> entries in web.xml. You can find an overview of them in this Stack Overflow post.

The default configuration is okay to start with, but there’s one peculiar setting which we really want to set right from the beginning on. It’s the setting which instructs JSF whether to interpret empty string submitted values as null. Elaborate background information can be found in my another blog article The empty String madness.

This can be done by adding the following section to top of the web.xml:

<context-param>
    <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
    <param-value>true</param-value>
</context-param>

Back to top

Creating the Backing Bean Class

With the project now correctly configured we can finally start with developing the actual MVC application. The Controller part of MVC is already configured as FacesServlet in web.xml. The Model part of MVC is what we’re going to create now. It’s basically just a simple Java class which is by JSF convention called a Backing Bean since it “backs” a View.

Right-click the src/main/java folder of the project and choose New ➤ Class. The New Java Class wizard will now appear. In this wizard, set the Package to com.example.project.view, set the Name to Bean. The rest of the fields can be kept default or empty.

The class editor will now open with the newly created backing bean class. We’ll modify it to add CDI @Named and @RequestScoped annotations on the class, so that it becomes a CDI managed bean. And we need to add two properties, input and output, and accompany the input property with a getter and setter pair, the output property with only a getter, so that these can be referenced from the view. Finally we’ll add a submit() action method which prepares the output property based on the input property, so that this can be invoked from the view.

As a hint, in Eclipse after entering the properties, you can right-­click anywhere in the class editor and choose Source ➤ Generate Getters and Setters to have the IDE to generate them.

In its entirety, the edited backing bean class should look as follows:

package com.example.project.view;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named @RequestScoped
public class Bean {

    private String input;
    private String output;

    public void submit() {
        output = "Hello World! You have typed: " + input;
    }

    public String getInput() {
        return input;
    }

    public void setInput(String input) {
        this.input = input;
    }

    public String getOutput() {
        return output;
    }
}

We’ll briefly go through the annotations that are used here.

  • @Named — gives the bean a name, which is primarily used to reference it via EL in the view. Without any attributes this name defaults to the simple class name with the first letter in lowercase, thus “bean” here. It will be available by #{bean} in EL. This can be used in JSF pages.
  • @RequestScoped — gives the bean a scope, which means that the same instance of the bean is used throughout the given lifespan. In the case of @RequestScoped that lifespan is the duration of an HTTP request. When the scope ends, then the bean is automatically destroyed. There are more scopes available, such as @ViewScoped, @SessionScoped and @ApplicationScoped. You can read more about scopes in this Stack Overflow post.
Back to top

Creating the Facelets File

Next, we’ll create the View part of MVC. It’s basically just a XHTML file which is by JSF interpreted as a Facelets file or just “Facelet”. When the FacesServlet is invoked with an URL matching the path of this Facelets file, then JSF will ultimately parse it and generate the HTML markup that is sent to the browser as a response to the request. With help of EL, it can reference a bean property and invoke a bean action.

Right-click the webapp folder of the project and choose New ➤ File. Give it the name test.xhtml.

The HTML editor will now open with the newly created Facelets file. It’s initially empty. Fill it with the following content:

<!DOCTYPE html>
<html lang="en"
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
>
    <h:head>
        <title>Hello World</title>
    </h:head>
    <h:body>
        <h1>Hello World</h1>
        <h:form>
            <h:outputLabel for="input" value="Input" />
            <h:inputText id="input" value="#{bean.input}" />
            <h:commandButton value="Submit" action="#{bean.submit}">
                <f:ajax execute="@form" render=":output" />
            </h:commandButton>
        </h:form>
        <h:outputText id="output" value="#{bean.output}" />
    </h:body>
</html>

We’ll briefly go through the JSF-specific XHTML tags that are used here.

  • <h:head> — generates the HTML <head> element. It gives JSF the opportunity to automatically include any necessary JavaScript files, such as the one containing the necessary logic for <f:ajax>.
  • <h:body> — generates the HTML <body> element. You can also use a plain HTML <body> in this specific Facelet example, but then it doesn’t give any other JSF tag the opportunity to automatically include any necessary JavaScript in the end of the HTML <body>.
  • <h:form> — generates the HTML <form> element. JSF will automatically include the so-called view state in a hidden input field within the form.
  • <h:outputLabel> — generates the HTML <label> element. You can also use a plain HTML <label> in this specific Facelet, but then you’d have to manually take care of figuring out the actual ID of the target input element.
  • <h:inputText> — generates the HTML <input type="text"> element. JSF will automatically get and set the value in the bean property specified in the value attribute.
  • <h:commandButton> — generates the HTML <input type="submit"> element. JSF will automatically invoke the bean method specified in the action attribute.
  • <f:ajax> — generates the necessary JavaScript code to enable Ajax behavior of the tag it is being nested in, in this case thus the <h:commandButton>. You can also do as good without it, but then the form submit won’t be performed asynchronously. The execute attribute of @form indicates that the entire <h:form> where it is sitting in must be processed on submit, and the render attribute of :output indicates that the tag identified by id="output" must be automatically updated on complete of the Ajax submit. For more background information on the syntax of the execute and render attributes, see this Stack Overflow post.
  • <h:outputText> — generates the HTML <span> element. This is the one being updated on completion of the Ajax submit. It will merely print the bean property specified in the value attribute.

Those JSF-specific XHTML tags are also called “JSF components”. Note that you can also perfectly embed plain vanilla HTML in a Facelets file. JSF components should only be used when the functionality requires so, or is more easily achievable with them.

Noted should be that a HTML5 doctype is indeed explicitly being used “in spite of” that it’s a XHTML file. HTML5 is these days the standard for web pages. For more background information on this, see this Stack Overflow post.

Back to top

Deploying the Project

In the Servers view, right-click the WildFly server entry and choose Add and Remove. It will show the Add and Remove wizard which gives you the opportunity to add and remove WAR projects to the server. Do so for our newly created project and finish the wizard.

Now start the WildFly server. You can do so by selecting it and then clicking the green arrow icon whose tool tip says “Start the server”. You can, of course, also use the bug icon whose tooltip says “Start the server in debug mode”. The Console view will briefly show the server startup log. Wait until the server starts up and has, in the Servers view, gained the status Started.

Now, open a tab in your favorite web browser and enter the web address http://localhost:8080/project-0.0.1-SNAPSHOT/test.xhtml in order to open the newly created JSF page. Play a bit around with it.

Coming back to the URL, the “localhost:8080” part is by convention the default domain of any Jakarta EE server which is running in development mode. The same address is also used by, among others, Payara and TomEE. The “/project-0.0.1-SNAPSHOT” part is by default the filename of the Maven-generated WAR file. In case of WildFly, you can find it in its /standalone/deployments folder. This part is in Servlet terms called the “context path” and obtainable by HttpServletRequest#getContextPath() and in JSF delegated by ExternalContext#getRequestContextPath().

If you have hard time in figuring out the actually used context path, then you can usually find it in the server logs. The exact logger line is however dependent on the server being used. In case of WildFly, it’ll be the line identified by WFLYUT0021. Open the Console view, press Ctrl+F and search for WFLYUT0021. It’ll show the following line:

INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 42) WFLYUT0021: Registered web context: '/project-0.0.1-SNAPSHOT' for server 'default-server'

The context path part can also be set to “/”. The deployed web application will then end up in the domain root. How to do that depends on the server being used. In case of WildFly, you’ll need to create the JBoss-specific jboss-web.xml as follows:

src/main/webapp/WEB-INF/jboss-web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC
    "-//JBoss//DTD JBOSS 5.0//EN"
    "http://www.jboss.org/j2ee/dtd/jboss-web_5_0.dtd"
>
<jboss-web>
    <context-root>/</context-root>
</jboss-web>

Now it will be deployed to the domain root and after restarting the server you can access the JSF page by http://localhost:8080/test.xhtml.

We can even get a step further by making test.xhtml the default landing file so that this also doesn’t need to be specified in the URL. This can be achieved by adding the following entry to the bottom of web.xml:

<welcome-file-list>
    <welcome-file>test.xhtml</welcome-file>
</welcome-file-list>

This will basically instruct the server to use the specified file within the folder as default resource when any folder is requested. So, if for example / folder is requested, then it’ll search for a /test.xhtml and serve it. Or if for example /foo folder is requested, then it’ll search for a /foo/test.xhtml and serve it. Etcetera. Now, save the web.xml and restart the server.

Coming back to the web browser, you’ll notice that the JSF page is now also accessible by merely http://localhost:8080.

Back to top

Installing H2

H2 is an in-memory SQL database. It’s an embedded database useful for quickly modeling and testing JPA entities, certainly in combination with autogenerated SQL tables based on JPA entities. Adding H2 to your web application project is a matter of adding the following dependency to the <dependencies> section of the pom.xml file:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.200</version>
</dependency>

That’s basically it. Its JDBC (Java Database Connecivity) driver is also provided by this dependency.

Back to top

Configuring DataSource

In order to be able to interact with any SQL database, we need to configure a so-called data source in the web application project. This can be done by adding the following section to the web.xml:

<data-source>
    <name>java:global/DataSourceName</name>
    <class-name>org.h2.jdbcx.JdbcDataSource</class-name>
    <url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</url>
</data-source>

It basically creates a connection pooled data source in the server itself. The data source <name> represents the JNDI (Java Naming and Directory Interface) name. The “java:global/” part is mandatory. The DataSourceName is free to your choice. It’s the one that ultimately needs to be registered in persistence.xml later on. The <class-name> represents the fully qualified name of the javax.sql.DataSource implementation of the JDBC driver being used. The <url> represents the JDBC driver-­specific URL format. The syntax is dependent on the JDBC driver being used. Usually the syntax can be found in the JDBC driver specific documentation. For an in-­memory H2 database with a database name of “test,” that’s thus jdbc:h2:mem:test. The H2-specific DB_CLOSE_DELAY=-1 path parameter basically instructs its JDBC driver not to automatically shut down the database when it hasn’t been accessed for some time, even though the application server is still running.

After configuring the data source, a concrete instance of the javax.sql.DataSource can now be injected in any servlet container managed artifact such as a plain vanilla servlet or filter as follows:

@Resource
private DataSource dataSource;

You could get a SQL connection from it via DataSource#getConnection() for the plain old JDBC work. However, as we’re going to use pure Jakarta EE, it’s better to use Jakarta EE’s own JPA for this instead.

Back to top

Configuring JPA

In order to familiarize JPA with the newly added data source, we need to add a new persistence unit to the persistence.xml which uses the data source as a JTA data source.

<persistence-unit name="PersistenceUnitName" transaction-type="JTA">
    <jta-data-source>java:global/DataSourceName</jta-data-source>
    <properties>
        <property
            name="javax.persistence.schema-generation.database.action"
            value="drop-and-create" />
    </properties>
</persistence-unit>

You see, the data source is identified by its JNDI name which we confgured earlier in web.xml.

You’ll also notice a JPA-­specific javax.persistence.schema-generation.database.action property with a value of “drop-and-create” which basically means that the web application should automatically drop and create all SQL tables based on JPA entities. This is, of course, only useful for prototyping purposes, as we’re basically doing in this tutorial. For real-world applications, you’d better pick either “create” or “none” (which is the default).

The transaction type being set to “JTA” basically means that the application server should automatically manage database transactions. This way every method invocation on an EJB from its client (usually, a JSF backing bean) transparently starts a new transaction and when the EJB method returns to the client (usually, the calling backing bean), the transaction is automatically committed and flushed. And, any runtime exception from an EJB method automatically rolls back the transaction. For more information on usefulness of this, see also this Stack Overflow post.

Back to top

Creating the JPA Entity

Now we’re going to create a JPA entity. Basically, it’s a JavaBean class which represents a single record of a database table. Each bean property is mapped to a particular column of the database table. Normally, JPA entities are modeled against existing database tables. But, as you’ve read in the previous section, “Configuring JPA”, about the persistence.xml, it’s also possible to do it the other way round: database tables are generated based on JPA entities. This is not recommended for production applications, but it’s useful for prototyping as we’re basically doing in this tutorial.

Right-click the src/main/java folder of the project and choose New ➤ JPA Entity. In the wizard, set the Java package to com.example.project.model and set the Class name to Message. The rest of the fields can be kept default or empty.

Modify the new entity class as follows:

package com.example.project.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.validation.constraints.NotNull;

@Entity
public class Message implements Serializable {

    private static final long serialVersionUID = 1L;

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

    @Column(nullable = false) @Lob
    private @NotNull String text;

    // Add/generate getters and setters.
}

As a reminder, you can let Eclipse generate getters and setters by right-clicking anywhere in the class editor and choosing Source ➤ Generate Getters and Setters.

We’ll briefly go through the annotations that are used here.

  • @Entity — marks the bean as a JPA entity, so that the JPA implementation will automatically collect database-related metadata based on all its properties.
  • @Id @GeneratedValue(strategy=IDENTITY) — marks a property to be mapped to a SQL database column of “IDENTITY” type. In MySQL terms, that’s the equivalent of “AUTO_INCREMENT”. In PostgreSQL terms, that’s the equivalent of “BIGSERIAL”.
  • @Column — marks a property to be mapped to a regular SQL database column. The actual SQL database column type depends on the Java type being used. In case of a Java String, without the additional @Lob annotation, that’s by default a VARCHAR(255) whose length can be manipulated by ­ @Column(length=n). With the @Lob annotation present, however, the SQL database column type becomes by default TEXT.
  • @Lob — marks a String property to be mapped to a SQL database column of type TEXT instead of a limited VARCHAR.
  • @NotNull — this is actually not part of JPA but of “Bean Validation”. To the point, it ensures that the bean property is being validated never to be null when submitting a JSF form and when persisting the JPA entity. Also note that this basically replicates the @Column(nullable=false), but that’s only because JPA doesn’t consider any Bean Validation annotations as valid database metadata in order to generate appropriate SQL tables.
Back to top

Creating the EJB Service

Next, we need to create an EJB in order to be able to save an instance of the aforementioned JPA entity in the database, and to obtain a list of them from the database. Right-click the src/main/java folder of the project and choose New ➤ Class. In the wizard, set the Package to com.example.project.service and set the Name to MessageService. The rest of the fields can be kept default or empty.

Modify the new service class as follows:

package com.example.project.service;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import com.example.project.model.Message;

@Stateless
public class MessageService {

    @PersistenceContext
    private EntityManager entityManager;

    public void create(Message message) {
        entityManager.persist(message);
    }

    public List<Message> list() {
        return entityManager
            .createQuery("FROM Message m", Message.class)
            .getResultList();
    }
}

That’s basically it. Let’s briefly go through the annotations.

  • @Stateless — marks the bean as a stateless EJB service, so that the application server knows whether it should pool them and when to start and stop database transactions. The alternative annotations are @Stateful and @Singleton. Note that a @Stateless does not mean that the container will make sure that the class itself is stateless. You as developer are still responsible to ensure that the class doesn’t contain any shared and mutable instance variables. Otherwise, you’d better mark it as either @Stateful or @Singleton, depending on its purpose. See also this Stack Overflow post.
  • @PersistenceContext — basically injects the JPA entity manager from the persistence unit as configured in the project’s persistence.xml. The entity manager is, in turn, responsible for mapping all JPA entities against a SQL database. It will, under the covers, do all the hard JDBC work.
Back to top

Adjusting the Backing Bean and Facelet

Now we’re going to adjust the earlier created backing bean in order to save the messages in the database and display all of them in a table.

package com.example.project.view;

import java.util.List;

import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

import com.example.project.model.Message;
import com.example.project.service.MessageService;

@Named @RequestScoped
public class Bean {

    private Message message = new Message();
    private List<Message> messages;

    @Inject
    private MessageService messageService;

    @PostConstruct
    public void init() {
        messages = messageService.list();
    }
    
    public void submit() {
        messageService.create(message);
        messages.add(message);
        message = new Message();
    }
    
    public Message getMessage() {
        return message;
    }
    
    public List<Message> getMessages() {
        return messages;
    }
}

Note that you don’t need setters for the message and messages properties. We’re going to use the getters and setters of the Message entity itself.

Finally, adjust the <h:body> of test.xhtml Facelet as follows:

<h1>Hello World</h1>
<h:form>
    <h:outputLabel for="input" value="Input" />
    <h:inputText id="input" value="#{bean.message.text}" />
    <h:message id="input_m" for="input" />
    <h:commandButton value="Submit" action="#{bean.submit}">
        <f:ajax execute="@form" render="input input_m :table" />
    </h:commandButton>
</h:form>
<h:dataTable id="table" value="#{bean.messages}" var="message">
    <h:column>#{message.id}</h:column>
    <h:column>#{message.text}</h:column>
</h:dataTable>

Now, restart the server, reload the page in your favorite web browser and create some messages.

As a reminder, the in-memory H2 database will be emptied when you restart the server. If you want to “avoid” this, simply switch to a real database, such as PostgreSQL or MySQL.

That's it! Hopefully it's helpful to get started with JSF 2.3 in Jakarta EE. If you want to explore further, advance to my Stack Overflow answers. You can find a collection of "most valuable" answers at https://jsf.zeef.com/bauke.scholtz. If you already have a moderate knowledge on web application development in general, and want to greatly expand your JSF knowledge, then consider picking the book The Definitive Guide to JSF in Java EE 8. Apart from the “From Zero to Hello World” chapter, the rest of the book is still very up to date these days.

Back to top

Copyright - None of this article may be taken over without explicit authorisation.

(C) April 2020, BalusC