Spring Boot backend and web frontend in separate Maven modules

I like to keep my backend from my frontend separated during development. This sounds like an obvious best practice, but it’s not easily done when your frontend is a web app and your backend is Spring Boot: because Spring Boot is self-contained, it encourages you to keep everything in one big module. This is convenient in many ways: Spring Boot takes care of serving your API and web app, hot-reloading when there are changes, and it can package the whole thing in a standalone fat jar using a dedicated Maven plugin. On the other hand, it has some drawbacks: the hot-reload feature of Spring Boot is not the most efficient, if your webapp involves webpack or some other build mechanism it becomes seriously complicated to work in dev mode, and most importantly: it couples together two pieces of software that should only be linked by a set of URLs.

What I want to do

Keep my backend and frontend each in a separate Maven module, while keeping the ability to package them together into a Spring Boot standalone jar.

Justin Calleja’s method

While researching how to achieve this, I found Justin Calleja’s very useful blog post: Serving a Webpack bundle in Spring Boot , which was my biggest influence. In short his solution is as follows:

  • frontend
    • module uses Maven plugin  frontend-maven-plugin to wrap node / npm / webpack
    • webpack is configured to generate its output into target/classes/META-INF/resources/webjars/module/version
    • packaged as a jar
  • backend
    • has a Maven dependency on the frontend module
    • Spring MVC configured to serve URLs beginning with /webjars from its classpath (which includes the jar from the frontend module)

What I wanted to change

At first I liked that solution very much, but I was put off by the fact that the backend module has a dependency on the frontend module. This is not right.

I understand that since Spring Boot manages the final packaging, it’s convenient if it has access to the client code too, but this is not what I wanted to achieve, which is: independence of backend and frontend.

This is a classic dependency inversion problem, so the solution is obvious: add a third module that depends on both others, and let it do the packaging.

What I ended up doing

I will only highlight the differences with Justin Calleja’s solution.


The frontend module is quite similar, except that my web app was generated with vue-cli so I didn’t want to mess with webpack’s configuration. I let it generate its stuff into the dist subdirectory, and I declare this directory as a resource for Maven build. Here’s the relevant POM part:

                <!-- The webapp directory as generated by webpack -->
                <!-- We put this into the static directory so that Spring Boot serves it through HTTP as static content -->

By ultimately putting it into the static directory, we make sure that Spring Boot will serve it as static content (once it has been merged with the backend jar). The rest of the POM is just the frontend-maven-plugin configured just as in  Justin Calleja’s solution.


The backend module is different in the following ways:

  • it does not have a dependency on the frontend module
  • it does not use spring-boot-maven-plugin to repackage the jar into an executable jar
  • The specific Spring MVC configuration for webjars is not required, as the merge will ensure that the webapp files are directly available in the main jar.


Here comes a new module: bundle. The bundle module has dependencies on both the backend  and frontend modules; its goal is to merge the two jars into one and package it as a Spring Boot self-contained executable jar.

How do we merge two jars? I’ve tried several approaches but the one that seems to work best is to use maven-dependency-plugin to unpack the dependencies into target/classes, and then let the jar plugin create a single jar from the unpacked files.


Now we just need to add spring-boot-maven-plugin to repackage this jar into an executable jar.

Running in deployed mode

The whole point of this setup was to generate this last artefact, a Spring Boot app packaged as a self-contained, executable jar.

To let Maven manage the build order of our three modules, the Maven idiom is to create a root module one level up with a “pom” packaging, like this:


To build it we just run “mvn package” from the root maven module; the Maven reactor will build in sequence: the backend module, the frontend module, and the bundle. The final jar will be in bundle/target. If all goes well we should be able to run it with “java -jar bundle-x-y-z-SNAPSHOT.jar”

As you will notice, this process is quite long and is not intended to be used during the development period, but only to generate the production-ready deliverable. However, we need to make sure the process works as intended, so we can safely work in dev mode and be assured that we can generate the production deliverable anytime.

Running in dev mode

The part regarding webpack-dev-server from Justin Calleja’s post still applies; however if you used vue-cli (or any other similar cli) a lot of the tedious work has been done for you.

In dev mode, we will be running two distinct servers: the Spring Boot app will serve the API requests, while webpack-dev-server will serve the static contents. This way we have the best of both worlds: any change on the backend side will only require hot-reloading the Spring Boot app, and any change on the client side will be picked up by webpack and processed to update the frontend in near-real-time.

The backend is started as any Spring Boot app, likely through your IDE. Obviously both servers cannot listen on the same port, so we will change the webpack-dev-server port to something other than the Spring Boot port (8080 by default). That’s the only change we will make to the webpack configuration.

If you’ve used vue-cli, then this change takes place in config/index.js:

    // Various Dev Server settings
    host: 'localhost', // can be overwritten by process.env.HOST
    port: 8081, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined

vue-cli also generates a complete package.json file, including a “dev” script that starts webpack-dev-server, so all we have to do to start the frontend dev server is to run “npm run dev” from the webapp directory. After a while your app will be available on http://localhost:8081.

Since the API requests will come from a page that has been loaded from a different server, we will also need to enable CORS, by simply declaring a bean in the Spring Boot app as follows:

    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            public void addCorsMappings(CorsRegistry registry) {

After this changed you can then check that API calls to http://localhost:8080 are processed correctly.

Wrapping up

We have achieved our goal which was to have independent Maven modules for backend and frontend, while being able to generate a standalone Spring Boot executable jar, and maintaining the ability to have a fast turnaround time in dev mode.

However there’s always room for improvement. I’d like to hear your similar stories.


Leave a Reply

Your email address will not be published. Required fields are marked *