openxava / documentation / Spring Boot

×News: OpenXava 7.7.3 released - June 8 · Read more

OpenXava applications are standard Java web applications. Traditionally they are packaged as a WAR and run in an external Tomcat. However, you can also add Spring Boot to an existing OpenXava application so it can start with an embedded Tomcat, from a Java main() method, while keeping the usual OpenXava structure.

In this tutorial we are going to start from a regular OpenXava project, for example one created with the OpenXava Maven archetype, and add the minimum configuration needed to run it on Spring Boot.

Use Spring Boot 2 with OpenXava 7

OpenXava 7.x uses the Java EE javax.* APIs. For this reason, use Spring Boot 2.x, not Spring Boot 3.x, because Spring Boot 3 uses Jakarta EE jakarta.* APIs.

The examples below use OpenXava 7.7.2 and Spring Boot 2.7.18. Add these properties to the pom.xml file of your project, replacing the existing <properties> section if necessary:

<properties>
    <openxava.version>7.7.2</openxava.version>
    <spring-boot.version>2.7.18</spring-boot.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
</properties>

Add Spring Boot to pom.xml

First, add the Spring Boot dependency management to the pom.xml file of your project, at the same level as <dependencies> and <build>:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Then add these dependencies to the <dependencies> section of the pom.xml file of your project. The Spring Boot web starter adds the embedded web server, spring-boot-starter-websocket adds support for the OpenXava chat, while tomcat-embed-jasper adds support for JSP pages. OpenXava uses JSPs, so tomcat-embed-jasper is required when running with embedded Tomcat:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
    </dependency>

    <dependency>
        <groupId>org.openxava</groupId>
        <artifactId>openxava</artifactId>
        <version>${openxava.version}</version>
    </dependency>

    <!-- Other dependencies used by your project -->
</dependencies>

Keep Tomcat libraries in the WAR

A regular OpenXava project excludes Tomcat libraries from the generated WAR because it expects to run in an external Tomcat. With Spring Boot embedded Tomcat you need those libraries, so remove this exclusion from the <packagingExcludes> of the maven-war-plugin in the pom.xml file of your project if it is present:

WEB-INF/lib/tomcat-*.jar

Your maven-war-plugin in the pom.xml file of your project can still exclude other libraries, but not the Tomcat ones:

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>3.3.2</version>
    <configuration>
        <packagingExcludes>WEB-INF/lib/ecj-3*.jar,
            WEB-INF/lib/fontbox-*.jar,
            WEB-INF/lib/htmlunit-*.jar,
            WEB-INF/lib/httpclient-*.jar,
            WEB-INF/lib/httpcore-*.jar,
            WEB-INF/lib/httpmime-*.jar,
            WEB-INF/lib/jetty-*.jar,
            WEB-INF/lib/junit-*.jar,
            WEB-INF/lib/neko-htmlunit-*.jar,
            WEB-INF/lib/pdfbox-*.jar,
            WEB-INF/lib/serializer-*.jar,
            WEB-INF/lib/websocket-*.jar,
            WEB-INF/lib/xalan-*.jar,
            WEB-INF/lib/xercesImpl-*.jar,
            WEB-INF/lib/xml-apis-*.jar</packagingExcludes>
    </configuration>
</plugin>

Add the Spring Boot Maven plugin

Add this Spring Boot Maven plugin to the <plugins> section of the pom.xml file of your project, setting the main class that we will create in the next section. In this example the application is called invoicing and the main class is com.yourcompany.invoicing.InvoicingApplication:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>${spring-boot.version}</version>
    <configuration>
        <mainClass>com.yourcompany.invoicing.InvoicingApplication</mainClass>
    </configuration>
</plugin>

Complete pom.xml example

After applying all the changes explained above, the pom.xml file of your project could look like this. Adapt groupId, artifactId, version, finalName and the Spring Boot main class to your own application:

<?xml version="1.0" encoding="UTF-8"?>
<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.yourcompany</groupId>
    <artifactId>invoicing</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <openxava.version>7.7.2</openxava.version>
        <spring-boot.version>2.7.18</spring-boot.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

        <dependency>
            <groupId>org.openxava</groupId>
            <artifactId>openxava</artifactId>
            <version>${openxava.version}</version>
        </dependency>

        <dependency>
            <groupId>org.openxava</groupId>
            <artifactId>openxava-7.7-chat-jdk17</artifactId>
            <version>${openxava.version}</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>invoicing</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>unpack</id>
                        <phase>package</phase>
                        <goals>
                            <goal>unpack</goal>
                        </goals>
                        <configuration>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>org.openxava</groupId>
                                    <artifactId>openxava</artifactId>
                                    <version>${openxava.version}</version>
                                    <outputDirectory>src/main/resources</outputDirectory>
                                    <includes>xava/dtds/*</includes>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
                <configuration>
                    <packagingExcludes>WEB-INF/lib/ecj-3*.jar,
                        WEB-INF/lib/fontbox-*.jar,
                        WEB-INF/lib/htmlunit-*.jar,
                        WEB-INF/lib/httpclient-*.jar,
                        WEB-INF/lib/httpcore-*.jar,
                        WEB-INF/lib/httpmime-*.jar,
                        WEB-INF/lib/jetty-*.jar,
                        WEB-INF/lib/junit-*.jar,
                        WEB-INF/lib/neko-htmlunit-*.jar,
                        WEB-INF/lib/pdfbox-*.jar,
                        WEB-INF/lib/serializer-*.jar,
                        WEB-INF/lib/websocket-*.jar,
                        WEB-INF/lib/xalan-*.jar,
                        WEB-INF/lib/xercesImpl-*.jar,
                        WEB-INF/lib/xml-apis-*.jar</packagingExcludes>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.5</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <mainClass>${project.groupId}.${project.artifactId}.run.${project.artifactId}</mainClass>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.yourcompany.invoicing.InvoicingApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Create application.properties

Create src/main/resources/application.properties. Set the context path to the name of your OpenXava application. This should match the name in xava/application.xml and the final name of the WAR if you want to keep the same URL when using an external Tomcat:

server.servlet.context-path=/invoicing
server.tomcat.additional-tld-skip-patterns=htmlunit-*.jar,jetty-*.jar

The additional-tld-skip-patterns entry avoids scanning some libraries that are not relevant for JSP tag libraries.

Create the Spring Boot application class

Create a class in the root package of your application. For an application with packages under com.yourcompany.invoicing, create src/main/java/com/yourcompany/invoicing/InvoicingApplication.java:

package com.yourcompany.invoicing;

import java.util.EnumSet;

import javax.servlet.DispatcherType;

import org.apache.catalina.Context;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.util.descriptor.web.ContextResource;
import org.openxava.chat.ChatEndpoint;
import org.openxava.util.DBServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

import com.openxava.naviox.web.NaviOXFilter;

@SpringBootApplication
@ServletComponentScan(basePackages = { "org.openxava", "com.openxava" })
public class InvoicingApplication extends SpringBootServletInitializer implements WebMvcConfigurer {

    public static void main(String[] args) throws Exception {
        DBServer.start("invoicing-db");
        SpringApplication.run(InvoicingApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(InvoicingApplication.class);
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("forward:/index.jsp");
    }

    @Bean
    public FilterRegistrationBean<NaviOXFilter> naviOXFilter() {
        FilterRegistrationBean<NaviOXFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new NaviOXFilter());
        registration.setName("naviox");
        registration.addUrlPatterns("*.jsp", "/modules/*", "/phone/index.jsp", "/m/*");
        registration.setDispatcherTypes(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD));
        return registration;
    }

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        ServerEndpointExporter exporter = new ServerEndpointExporter();
        exporter.setAnnotatedEndpointClasses(ChatEndpoint.class);
        return exporter;
    }

    @Bean
    public TomcatServletWebServerFactory tomcatFactory() {
        return new TomcatServletWebServerFactory() {

            @Override
            protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
                tomcat.enableNaming();
                return super.getTomcatWebServer(tomcat);
            }

            @Override
            protected void postProcessContext(Context context) {
                ContextResource resource = new ContextResource();
                resource.setName("jdbc/invoicingDS");
                resource.setType("javax.sql.DataSource");
                resource.setProperty("driverClassName", "org.hsqldb.jdbc.JDBCDriver");
                resource.setProperty("url", "jdbc:hsqldb:hsql://localhost:1666");
                resource.setProperty("username", "sa");
                resource.setProperty("password", "");
                resource.setProperty("maxTotal", "20");
                resource.setProperty("maxIdle", "5");
                resource.setProperty("maxWaitMillis", "10000");
                context.getNamingResources().addResource(resource);
            }
        };
    }

}

There are several important details in this class:

This is important because Spring Boot embedded Tomcat does not use the src/main/webapp/META-INF/context.xml file to create the datasource. So, when running with Spring Boot, the datasource must be defined programmatically in postProcessContext(). If you do not use the embedded HSQLDB server started by OpenXava during development, remove the call to DBServer.start() and configure your own JDBC driver, URL, user and password in the ContextResource created inside postProcessContext().

Run the application

Now you can run the application from your IDE by executing the Spring Boot launcher class. In this tutorial the launcher class is com.yourcompany.invoicing.InvoicingApplication, the class that contains the main() method with SpringApplication.run().

You can also run it from Maven with the Spring Boot plugin, from the root folder of your project:

mvn spring-boot:run

When the application starts, open:

http://localhost:8080/invoicing

Summary

To run OpenXava on Spring Boot you do not have to rewrite your OpenXava application. You keep your entities, controllers, resources, JSPs, xava/application.xml, persistence.xml and web application structure. The essential additions are Spring Boot dependencies, JSP support, a Spring Boot main class, embedded Tomcat JNDI configuration, manual NaviOX filter registration and a small application.properties.

With this setup your OpenXava application can be run as a Spring Boot application, while still being packaged as a WAR.

This integration also allows you to use the tools from the Spring ecosystem in your OpenXava application, for example creating web services using Spring MVC REST.

Download and run a complete working example based on this tutorial