Master Multi‑Module Spring Boot 3: Build Independent Sub‑Modules with a Shared Parent

This article demonstrates a Spring Boot 3 multi‑module architecture where each module runs its own DispatcherServlet and ApplicationContext while sharing a common parent container, providing complete code examples, configuration steps, and testing insights for building loosely‑coupled backend services.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Multi‑Module Spring Boot 3: Build Independent Sub‑Modules with a Shared Parent

Environment: SpringBoot 3.4.2

1. Introduction

In a previous article we introduced a multi‑module development approach that starts multiple Tomcat instances in a single Spring Boot project. This article presents another approach where each module has an independent DispatcherServlet and its own ApplicationContext, while sharing a common parent container.

2. Practical Cases

2.1 User Module

Entity defined as a Java record:

public record User(Long id, String name, Integer age) {}

Simple controller:

@RestController
public class UserController {
  @GetMapping("/u/query")
  public ResponseEntity<User> query() {
    return ResponseEntity.ok(new User(1L, "pack", 22));
  }
}

Configuration scans the user package and enables Spring MVC:

@Configuration
@ComponentScan(basePackages = {"cn.pack.module.user"})
@EnableWebMvc
public class UserConfig {}

2.2 Order Module

Entity: public record Order(Long id, String orderNo) {} Controller injects a common service from the parent container:

@RestController
public class OrderController {
  private final CommonService commonService;
  public OrderController(CommonService commonService) {
    this.commonService = commonService;
  }
  @GetMapping("/o/query")
  public ResponseEntity<Order> query() {
    this.commonService.query();
    return ResponseEntity.ok(new Order(66L, "XP00001"));
  }
}

Configuration:

@Configuration
@ComponentScan(basePackages = {"cn.pack.module.order"})
@EnableWebMvc
public class OrderConfig {}

2.3 Parent Container

Common service shared by sub‑modules:

@Service
public class CommonService {
  public void query() {
    System.err.println("Common service query...");
  }
}

Initializer for the user module creates a child AnnotationConfigServletWebApplicationContext, registers UserConfig, and maps the servlet to /users/*:

@Component
public class UserServletContextInitializer implements ServletContextInitializer {
  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
    AnnotationConfigServletWebApplicationContext context = new AnnotationConfigServletWebApplicationContext();
    context.setServletContext(servletContext);
    context.register(UserConfig.class);
    DispatcherServlet servlet = new DispatcherServlet();
    servlet.setApplicationContext(context);
    Dynamic dynamic = servletContext.addServlet("userDispatcherServlet", servlet);
    dynamic.addMapping("/users/*");
    dynamic.setAsyncSupported(true);
  }
}

Order module initializer is analogous, mapping to /orders/*:

@Component
public class OrderServletContextInitializer implements ServletContextInitializer {
  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
    AnnotationConfigServletWebApplicationContext context = new AnnotationConfigServletWebApplicationContext();
    context.setServletContext(servletContext);
    context.register(OrderConfig.class);
    DispatcherServlet servlet = new DispatcherServlet();
    servlet.setApplicationContext(context);
    Dynamic dynamic = servletContext.addServlet("orderDispatcherServlet", servlet);
    dynamic.addMapping("/orders/*");
    dynamic.setAsyncSupported(true);
  }
}

2.4 Testing

When accessing /orders/query the console prints “Common service query…”. Accessing /users/query returns 404 because the servlet is mapped to /users/* only.

Test result
Test result
404 result
404 result

2.5 Why does the order module see the parent bean?

During DispatcherServlet initialization, FrameworkServlet.initServletBean() sets the parent WebApplicationContext and refreshes the context, allowing child modules to inherit beans from the shared parent.

FrameworkServlet initialization
FrameworkServlet initialization
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Spring BootMulti‑moduleapplicationcontextDispatcherServlet
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.