How to Run Multiple SpringBoot Microservices on a Single Tomcat with Minimal Resources
This guide explains how to adapt a distributed micro‑service architecture to run all SpringBoot services inside one external Tomcat instance by changing packaging to WAR, configuring server.xml, setting memory options, adjusting virtual addresses, handling logs, and registering services with Nacos for low‑memory environments.
Background
When your system adopts a distributed micro‑service architecture designed for massive data, high concurrency, and high throughput, you split many services and use numerous distributed middleware to decouple business.
Design looks beautiful, implementation is skinny!
Problems arise:
Engineers report server memory reaching 95%.
Product managers say the configured servers are too expensive and need to be downsized.
Developers claim services crash because resources are insufficient.
It feels like using a cannon to kill a chicken.
How can the same framework support both hundreds of millions of capacity with horizontal scaling and also run on a single PC with 8 GB / 16 GB memory?
Solution Overview
Principle: keep the underlying architecture unchanged, modify the packaging method, and run all services in a single external Tomcat process – a "beautiful transformation".
1. Tomcat Version Selection
SpringBoot 2.1.6.RELEASE recommends Tomcat 9; the latest version is 9.0.106. Download URL:
https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.106/bin/apache-tomcat-9.0.106-windows-x64.zip2. Tomcat Configuration Changes
2.1 server.xml support multiple ports and services
<?xml version="1.0" encoding="UTF-8"?>
<!-- Apache License omitted for brevity -->
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
<!-- APR connector and OpenSSL support using Tomcat Native -->
<Listener className="org.apache.catalina.core.AprLifecycleListener"/>
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml"/>
</GlobalNamingResources>
<!-- Example service -->
<Service name="pbp-auth-server">
<Connector port="27020" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="28020" maxParameterCount="1000"/>
<Engine name="pbp-auth-server" defaultHost="pbp-auth-server">
<Realm className="org.apache.catalina.realm.LockOutRealm"/>
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
<Host name="pbp-auth-server" appBase="pbp-auth-server" unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t \"%r\" %s %b"/>
</Host>
</Engine>
</Service>
<!-- Additional services omitted for brevity -->
</Server>2.2 Tomcat memory configuration
Add setenv.bat in tomcat\bin:
set "JAVA_HOME=C:\Program Files\Java\jre1.8.0_311"
set "CATALINA_OPTS=-Xms512m -Xmx8192m -Dspring.profiles.active=dev"
set JAVA_OPTS=-Xms1024m -Xmx8192m -XX:PermSize=256M -XX:MaxNewSize=500m -XX:MaxPermSize=500m -Djava.awt.headless=true2.3 Service virtual address (key point)
Each service is stored in its own folder; ensure Service Name == appBase == Host Name == folder name.
Expect to access each service via http://127.0.0.1:servicePort/ rather than through a context path that would cause Tomcat to restart.
In Tomcat, "/" refers to the ROOT folder of the appBase.
Therefore we name the folder ROOT under each appBase so that the service can be accessed directly by its port.
http://127.0.0.1:27060/v1/user/insert
http://127.0.0.1:27010/pbp-system/v1/user/insert2.4 Log storage
Tomcat logs are under logs in the root directory.
When started via startup.bat, runtime logs appear in bin\logs.
When installed as a Windows service ( service.bat install), logs are in logs\serviceName.
3. SpringBoot Microservice Refactoring
3.1 Change packaging to WAR
<packaging>war</packaging>
<build>
<finalName>ROOT</finalName>
</build>3.2 Exclude embedded Tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>3.3 Extend SpringBootServletInitializer
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableFeignClients
@EnableDiscoveryClient
@EnableScheduling
@MapperScan(basePackages="com.pilot.basic.history.mapper")
@TableShardModelScan(basePackages={"com.pilot.basic.history.entity.model"})
@ConsumerListenerScan
public class BasicHistoryApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(BasicHistoryApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(BasicHistoryApplication.class);
}
}3.4 Nacos registration for WAR deployment
@Component
@Slf4j
public class NacosRegisterOnWar implements ApplicationRunner {
@Autowired
private NacosRegistration registration;
@Autowired
private NacosAutoServiceRegistration nacosAutoServiceRegistration;
@Value("${server.port}") Integer port;
@Value("${spring.application.name:pbp-history-data}") String applicationName;
@Override
public void run(ApplicationArguments args) throws Exception {
if (registration != null && port != null) {
Integer tomcatPort = port;
try {
tomcatPort = Integer.valueOf(getTomcatPort());
} catch (Exception e) {
log.warn("Failed to get external Tomcat port:", e);
}
registration.setPort(tomcatPort);
nacosAutoServiceRegistration.start();
}
}
public String getTomcatPort() throws Exception {
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
Set<ObjectName> objectNames = beanServer.queryNames(
new ObjectName(applicationName + ":type=Connector,*"),
Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
String port = objectNames.iterator().next().getKeyProperty("port");
log.info("External Tomcat port: {}", port);
return port;
}
}3.5 Additional configuration
Set a unique JMX domain for each Tomcat service to avoid conflicts:
spring:
jmx:
default-domain: pbp-io-server4. Result
Running on a machine with 16 GB memory shows stable logs and successful service registration.
Architect's Alchemy Furnace
A comprehensive platform that combines Java development and architecture design, guaranteeing 100% original content. We explore the essence and philosophy of architecture and provide professional technical articles for aspiring architects.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
