How to deploy a Java and Angular webapp in one JAR/WAR


Deploy your Angular application in a Java artifact

In your project you can deploy your Angular and Java application in the same artifact (.jar or .war) or in separate artifacts.

There is no best solution. You should adopt the architecture chosen by your team.

If frontend and backend teams are working independently, and the frontend team is using a monorepo, separate artifacts could have some advantages.

A quick video tutorial is on Vimeo:

One JAR with Spring Boot or Java EE / Jakarta EE

Your project contains 3 modules, the parent and 2 children (frontend and backend).

In this example we use Spring Boot, the same apply for a Java EE / Jakarta EE project.

We are building a Spring fat jar that contains the backend and the frontend.

You can find the project on GitHub here: https://github.com/marco76/java-angular-basic

Here you can see how the project is structured:

The backend project artifact will contain the Java compiled classes and the Angular application:

To build and start the project locally:

 
git clone https://github.com/marco76/java-angular-basic.git 
 
cd ./java-angular-basic.git 
 
mvn clean package 
 
cd ./backend/target 
 
java -jar backend-0.1-SNAPSHOT.jar 

parent pom.xml

The parent pom.xml declares the 2 modules to build. frontend for Angular and backend for Java.

The 2 modules can be started separately during the development. The Angular app will use the port 4200, the Java application will use the port 8080.

<?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>dev.marco</groupId> 
    <artifactId>java-angular-example</artifactId> 
    <packaging>pom</packaging> 
    <version>0.1-SNAPSHOT</version> 
    <parent> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-parent</artifactId> 
        <version>2.4.3</version> 
    </parent> 
    <modules> 
        <module>frontend</module> 
        <module>backend</module> 
    </modules> 
 
    <dependencies> 
        <dependency> 
            <groupId>org.springframework</groupId> 
            <artifactId>spring-context-support</artifactId> 
            <version>5.3.4</version> 
        </dependency> 
        <dependency> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-starter-web</artifactId> 
            <version>2.4.3</version> 
        </dependency> 
    </dependencies> 
</project> 

Frontend pom.xml

The frontend module contains a standard Angular CLI application.

During the development you can start the application with a standard ng serve

The heavy lifting is done by the plugin https://github.com/eirslett/frontend-maven-plugin. This plugin download node, install the libraries and build the project.

The result is a jar file that contains the Angular application compiled.

<?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"> 
    <parent> 
        <groupId>dev.marco</groupId> 
        <artifactId>java-angular-example</artifactId> 
        <version>0.1-SNAPSHOT</version> 
    </parent> 
    <modelVersion>4.0.0</modelVersion> 
    <artifactId>frontend</artifactId> 
    <packaging>jar</packaging> 
    <build> 
        <plugins> 
            <!-- clean the dist directory used by Angular --> 
            <plugin> 
                <artifactId>maven-clean-plugin</artifactId> 
                <version>3.1.0</version> 
                <configuration> 
                    <filesets> 
                        <fileset> 
                            <directory>dist</directory> 
                        </fileset> 
                    </filesets> 
                </configuration> 
            </plugin> 
 
            <plugin> 
                <groupId>com.github.eirslett</groupId> 
                <artifactId>frontend-maven-plugin</artifactId> 
                <version>1.11.2</version> 
 
                <executions> 
                    <!-- Install node and npm --> 
                    <execution> 
                        <id>Install Node and NPM</id> 
                        <goals> 
                            <goal>install-node-and-npm</goal> 
                        </goals> 
                        <configuration> 
                            <nodeVersion>v14.16.0</nodeVersion> 
                        </configuration> 
                    </execution> 
 
                    <!-- clean install --> 
                    <execution> 
                        <id>npm install</id> 
                        <goals> 
                            <goal>npm</goal> 
                        </goals> 
                    </execution> 
 
                    <!-- build app --> 
                    <execution> 
                        <id>npm run build</id> 
                        <goals> 
                            <goal>npm</goal> 
                        </goals> 
                        <configuration> 
                            <arguments>run build --prod</arguments> 
                        </configuration> 
                    </execution> 
 
                    <!-- code validation --> 
                    <execution> 
                        <id>npm run lint</id> 
                        <goals> 
                            <goal>npm</goal> 
                        </goals> 
                        <phase>test</phase> 
                        <configuration> 
                            <arguments>run lint</arguments> 
                        </configuration> 
                    </execution> 
                </executions> 
            </plugin> 
        </plugins> 
 
        <resources> 
            <resource> 
                <!-- we copy the content of the frontend directory in the final artifact --> 
                <directory>dist/frontend</directory> 
            </resource> 
        </resources> 
    </build> 
</project> 

The Java Backend pom.xml

You have to import the frontend module, the frontend code will be included in your final package.

There a dependency with the frontend module. This solution is not ideal because the backend module is not completely independent.

We use the maven-dependency-plugin to unpack the content of the frontend and copy the content in our backend artifact.

The Angular application is copied in the folder /classes/static, this folder is used to serve static content in Spring Boot.

<?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"> 
    <parent> 
        <groupId>dev.marco</groupId> 
        <artifactId>java-angular-example</artifactId> 
        <version>0.1-SNAPSHOT</version> 
        <relativePath>../pom.xml</relativePath> 
    </parent> 
 
    <modelVersion>4.0.0</modelVersion> 
 
    <properties> 
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
        <project.build.outputEncoding>UTF-8</project.build.outputEncoding> 
    </properties> 
 
    <artifactId>backend</artifactId> 
 
    <dependencies> 
        <dependency> 
            <groupId>dev.marco</groupId> 
            <artifactId>frontend</artifactId> 
            <version>0.1-SNAPSHOT</version> 
            <type>jar</type> 
        </dependency> 
    </dependencies> 
 
    <build> 
        <plugins> 
            <plugin> 
                <groupId>org.springframework.boot</groupId> 
                <artifactId>spring-boot-maven-plugin</artifactId> 
                <version>2.4.3</version> 
                <configuration> 
                    <mainClass>dev.marco.example.springboot.Application</mainClass> 
                </configuration> 
                <executions> 
                    <execution> 
                        <goals> 
                            <goal>repackage</goal> 
                        </goals> 
                    </execution> 
                </executions> 
            </plugin> 
 
            <plugin> 
                <groupId>org.apache.maven.plugins</groupId> 
                <artifactId>maven-dependency-plugin</artifactId> 
                <version>3.1.2</version> 
                <executions> 
                    <execution> 
                        <id>merge</id> 
                        <phase>initialize</phase> 
                        <goals> 
                            <goal>unpack</goal> 
                        </goals> 
                        <configuration> 
                            <artifactItems> 
                                <artifactItem> 
                                    <groupId>dev.marco</groupId> 
                                    <artifactId>frontend</artifactId> 
                                    <type>jar</type> 
                                    <overWrite>true</overWrite> 
                                    <outputDirectory>${project.build.directory}/classes/static</outputDirectory> 
                                </artifactItem> 
                            </artifactItems> 
                        </configuration> 
                    </execution> 
                </executions> 
            </plugin> 
        </plugins> 
    </build> 
</project> 

Spring

In our example we added a REST controller to deploy and execute the basic application.

During the development phase we are starting the Spring backend application and the Angular application in separated instances.

To allow the communication between the HttpClient of Angular and the RestController of Spring we have to enable a Cross Origin Resource Sharing in Spring Boot or our requests will be refused.

@RestController 
// we allow localhost:4200 for testing purposes 
@CrossOrigin(origins = "http://localhost:4200") 
public class HelloController { 
 
    @RequestMapping(value = "/message", produces = MediaType.APPLICATION_JSON_VALUE) 
    public Map<String, String> index() { 
        return Collections.singletonMap("message", "Greetings from Spring Boot!"); 
    } 
 
} 

Angular

Our Angular application doesn't do anything special, we simply call the backend service and we show the message received:

export class AppComponent implements OnInit{ 
  title = 'frontend'; 
  message = ''; 
 
  constructor(private http: HttpClient) { } 
 
  ngOnInit(): void { 
    this.http.get('http://localhost:8080/message').pipe( 
      first(), 
      tap(result => console.log('Message received from the server: ', result)), 
      map(result => this.message = (result as any).message) 
    ).subscribe(); 
  } 
 
} 

Deploy a WAR with Tomcat

If you need to deploy a WAR using Tomcat you can use the following branch

https://github.com/marco76/java-angular-basic/tree/feature/war-for-tomcat

You need to

  1. mvn clean package

  2. In backend there will be a file: angular-spring-example.war

  3. Deploy the file, it should be visible under: localhost:8080/angular-spring-example, if you want to run it as / you have to rename it in ROOT before the deployment.

Major changes in relation to the JAR version:

backend/pom.xml

We change the type of packaging and we add a dependency to spring-boot-starter-tomcat

<packaging>war</packaging> 
 
<dependency> 
<groupId>org.springframework.boot</groupId> 
<artifactId>spring-boot-starter-tomcat</artifactId> 
<scope>provided</scope> 
</dependency> 

backend/src/main/java/dev/marco/example/springboot/Application.java

@SpringBootApplication 
// we extend SpringBootServletInitializer 
    public class Application extends SpringBootServletInitializer { 

You could be interested in

Right click context menu with Angular

Right click custom menu inside dynamic lists with Angular Material
2020-05-31

Enums in Angular templates

How to use enum in the html template
2019-01-21

WebApp built by Marco using SpringBoot, Java 16, Mustache, Markdown and Hosted using Jelastic