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.
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>
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>
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 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>
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 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 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:
@ServletComponentScan scans OpenXava and NaviOX annotated web components.SpringBootServletInitializer keeps the application deployable as a WAR.NaviOXFilter is registered manually because it is defined by OpenXava in a web fragment,
not as a Spring bean.ServerEndpointExporter registers the OpenXava chat WebSocket endpoint
ChatEndpoint in the embedded Tomcat.tomcat.enableNaming() enables JNDI in the embedded Tomcat.postProcessContext() method is where the datasource is defined for Spring Boot.
It creates the JNDI datasource that OpenXava expects from persistence.xml.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().
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
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