angularspring-bootmavenwar

How to adapt this Angular-SpringBoot-Maven build to a WAR for Tomcat?


There's a very elegant solution put forth by Marco Molteni for building a web-app with an Angular front end and a Spring Boot backend with one Maven build. Unlike some other solutions, it doesn't depend on third-party Maven plugins, and both halves of the application can be built independently. They are combined by a third Maven subproject called "delivery" which takes the Java from the backend and the static files from the front end and combines them into one JAR.

The code repo is here: https://github.com/marco76/SpringAngularDemo

However, the output is a standalone JAR. I need a WAR that can be deployed to a Tomcat container.

The question: How can I adapt Molteni's design to produce a WAR that can be deployed to Tomcat?

An ideal answer would preserve the option for JAR output (e.g. if I can use a Maven profile to choose between JAR or WAR, or if both can be produced in one build).


Solution

  • The spring-boot-maven-plugin can create a jar or war based on the <packaging> specified in the POM; if you create a war, it can be also used as an executable jar: you can deploy it on Tomcat or run it from the command line with java -jar appname.war. So the first thing to do is to add

    <packaging>war</packaging>
    

    in the delivery POM.

    The second thing to do is to "hide" the embedded Tomcat: you need it only if you run the app from the command line, so it must be present in the war but not in WEB-INF/lib. To get this result you have to declare the embedded Tomcat as "provided": the plugin will put it in WEB-INF/lib-provided so it will be ignored by a "regular" Tomcat.

    So the POM must be changed as follows:

    <?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>
        <parent>
            <groupId>dev.marco.demo</groupId>
            <artifactId>parent</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>
    
        <artifactId>delivery</artifactId>
        <packaging>war</packaging>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>${project.groupId}</groupId>
                <artifactId>frontend</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>${project.groupId}</groupId>
                <artifactId>backend</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
                <scope>provided</scope>
            </dependency>
        </dependencies>
        <build>
            ... (this part has no change)
        </build>
    </project>
    

    The third thing to do is to modify the main class so that it extends SpringBootServletInitializer to initialize Spring Boot when deployed in an application server.

    Finally note that the demo application uses Spring Boot 3.x so you need Tomcat 10.1.x; it also assumes that its context-root will be "/", but this is not true, by default, in Tomcat: you can deploy it in the root context or adapt it further.

    Side note: to build the front-end I prefer to use a specific plugin like frontend-maven-plugin instead of the generic exec-maven-plugin because it can also download/install the right version of Node and NPM if they are not available; they are installed in a project-scoped folder (useful if you have multiple projects with different requirements). In this way you can just git clone the project and mvn install even if you are not a front-end developer and you have not the right front-end tools. This is also useful on a continuous integration server like Jenkins, for example.