Running Multiple Spring Boot Apps in the Same JVM

Never miss one of my articles: Readers of my newsletter get my articles before anyone else. Subscribe here!

In the last week or so, I was playing a little bit with microservices (hey! a buzzword!), and I used Spring Boot to create those services. On of my first questions was: How can I test a set of services from a business point of view with a single click in my IDE - I.e. how can I ensure that the complete application has the right features? I wanted a way to start multiple Spring Boot web applications in the same JVM. Here is how I did it.

All of this is a work in progress, so I don’t have some complete code for you on github. I will maybe write a more in-depth, step-by-step guide how I created this application later on quickglance.at. And this guide will then come with a complete example application. If I ever write it ;)

The Problem

I wanted to create an application that handles location data. It would consist of three services: One to write locations (“locations-command”, one to read them (“locations-query”) and a web application. You see, we are doing CQRS here (another buzzword!).

The web application contains only Spring WebMVC controllers and ViewModels (add MVVM to the list of buzzwords…). It calls the locations-query or locations-command service to do the real work. Those services would then use some storage backend to store and retrieve locations - Probably couchbase, but I have not decided yet.

I want to test this application from a business point of view using “executable specifications” written in FitNesse. I want to run those tests either in FitNesse or with JUnit from within my IDE. But I do not want to build and run a set of docker containers every time - I want to run the tests all in the same IDE, so I start them with a single click and debug them if necessary.

I also don’t want those tests to use the real storage backend. I want to be able to mock backend calls, and only test against the real database in a separate set of tests (which would then really spin up all those docker containers). I have not completely solved this part yet, so I’ll not cover it here. Maybe in a later blog post…

Third, I want that all the services to use port 8080 when they run in their own docker container - I don’t want to customize ports within the application. I can do this later with docker. But when I run the services within the same JVM, they have to use different ports.

And fourth, all spring boot applications have to run completely independent from each other - They cannot share the classpath or anything else.

“webapp”, “locations-command” and “locations-query” in the project root are the services. Those are standard Spring Boot web applications - You can create them, for example, with this web application: start.spring.io.

“specifications” contains the test fixtures and support code for the FitNesse tests. The three subprojects under “servicerunners” contain the code to start the web applications we need for testing.

For the tests we will only start the two locations services as spring boot applications. We will drive the tests through the ViewModels of the web application, so we don’t need to start the Jetty of “webapp”.

Test Suite Setup and Teardown

The specifications project must have the “webapp” in its classpath (so we can drive the tests through the ViewModels), but it cannot reference any other projects directly. Otherwise the two services would share the same classpath, and we don’t want that. It also has to reference the required libraries from Spring Boot, so they are available for the service runners (see later).

The “startBackend” method starts each service in its own classloader using a “BackendRunner”. To do this, it has to configure the correct classpath: We need the service itself (“backendUrl”), the backend runner for this specific service (“runnerUrl”) and the generic backend runner (“backendRunnerUrl”). We also have to keep a reference to all backends, so we can stop them after the test suite has finished (“stopAllBackends()”).

The “BackendRunner” for each service is very simple: It only contains a constructor that configures the generic BackendRunner.

Actually this class only has to call appContext = SpringApplication.run(backendClasses, new String[]{}). But it has to do so on a new thread with the correct contextClassLoader, otherwise Spring would not pick up the correct classloader.

So we have to run the Spring Boot applicatoin in its own thread (“BackendRunnerThread”) and also wait until it finished starting up. We do this by waiting on a monitor in “waitUntilBackendIsStarted()” - The runner thread will call “monitor.notify()” when the application is started.

To Recap

We can start multiple Spring Boot web applications when

They all run in their own classloader.

They run in their own thread, so Spring can use the contextClassLoader of the thread

They use different ports when running in the same VM

The application under test (who calls the services) and the test fixtures do not have a direct reference to them.

The code to support this is actually surprisingly simple (at least IMHO), but you’ll need some extra subprojects to configure the classpath correctly.

Cheap plastic drills: Most people think construction workers should have great tools. A lot of people think paying more than 1000 Euros for an office chair is a waste of money, even for a software developer who uses it 8 hours a day. Good tools are expensive.