How to Build a SpringBoot Multi‑Module Project with Service/DAO/Web Layered Design

The article explains why a single‑module SpringBoot project becomes problematic as business complexity grows, outlines the benefits of a multi‑module architecture—including clear responsibilities, high cohesion, faster compilation, and smoother microservice migration—and provides a step‑by‑step guide with Maven configuration, module responsibilities, code examples, packaging commands, and common pitfalls.

Java Tech Workshop
Java Tech Workshop
Java Tech Workshop
How to Build a SpringBoot Multi‑Module Project with Service/DAO/Web Layered Design

1. Why use a multi‑module project

In enterprise development a single‑module SpringBoot project quickly becomes problematic when business logic grows, the team expands, or iteration speeds increase. Issues include tangled code, unclear responsibilities, difficult maintenance, slow compilation, and obstacles to later microservice extraction.

SpringBoot multi‑module architecture addresses these problems by providing engineering‑level standardization and layered decoupling.

Single responsibility, high cohesion, low coupling

Controller only handles request reception and response.

Service only contains business logic, transactions, and workflow orchestration.

Dao only interacts with the database.

Each layer depends only on the layer below.

Strong code reuse

Common utilities, constants, and global configuration live in module-common.

Entity classes are placed in module-pojo and shared across the whole project.

Significant build speed improvement

Modifying a single module only rebuilds that module, saving time for large projects.

Facilitates team collaboration

Frontend developers depend only on the web module.

Business developers focus on service.

DBAs work on dao.

Smooth transition to microservices

Each module can later be split into an independent microservice.

2. Standard multi‑module structure

springboot-multi-module
├── pom.xml                     # Parent project: unified version & dependency management
├── module-common               # Common utilities, exceptions, configuration, constants
├── module-pojo                # Entity/DTO/VO/BO shared across the project
├── module-dao                 # Data access layer: mapper, repository, XML
├── module-service             # Business logic layer: service interfaces, implementations, transactions
└── module-web                 # API layer: controllers, interceptors, startup class

Module responsibilities

module-common

Global utilities (DateUtil, StringUtil, BeanUtil)

Global exception handling and unified response wrapper

Global configuration (Redis, MyBatis, thread pool)

Constants, enums, common annotations

module-pojo

Entity classes mapping to database tables

DTO for request parameters

VO for response objects

BO for internal business transfer

Query objects for pagination criteria

module-dao

MyBatis mapper interfaces and XML files

Repository abstractions for JPA/ES/MongoDB

Data source configuration

module-service

Service interfaces and implementations

Transaction control

Business validation and multi‑table operations

Cache logic and third‑party API calls

module-web

REST controllers

Global CORS, filters, interceptors

Application entry class

Swagger/Knife4j API documentation

3. Create the parent project (version management)

The parent pom.xml uses packaging = pom and contains only dependency management; no business code.

<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>com.demo</groupId>
    <artifactId>springboot-multi-module</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    <modules>
        <module>module-common</module>
        <module>module-pojo</module>
        <module>module-dao</module>
        <module>module-service</module>
        <module>module-web</module>
    </modules>
    <properties>
        <java.version>1.8</java.version>
        <spring.boot.version>2.7.18</spring.boot.version>
        <mybatis.plus.version>3.5.3.1</mybatis.plus.version>
        <lombok.version>1.18.30</lombok.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis.plus.version}</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

4. Create sub‑modules one by one

4.1 module‑pojo (entity module)

All data structures are stored here and do not depend on any business module; only Lombok is required.

<parent>
    <groupId>com.demo</groupId>
    <artifactId>springboot-multi-module</artifactId>
    <version>1.0.0</version>
</parent>
<artifactId>module-pojo</artifactId>
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

Example entity:

@Data
@TableName("t_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private Integer status;
    private LocalDateTime createTime;
}

Example VO:

@Data
public class UserVO {
    private Long id;
    private String username;
    private Integer status;
}

4.2 module‑common (common utilities)

Depends on module-pojo and provides global utilities, exception handling, and a unified Result wrapper.

public class Result<T> {
    private Integer code;
    private String msg;
    private T data;
    public static <T> Result<T> success(T data) { /* ... */ }
    public static <T> Result<T> fail(String msg) { /* ... */ }
}

4.3 module‑dao (data access layer)

Depends on module-pojo, module-common, MyBatis‑Plus, and MySQL driver.

public interface UserMapper extends BaseMapper<User> { }

4.4 module‑service (business layer)

Depends on module-dao and module-common.

public interface UserService {
    UserVO getUserById(Long id);
}

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Transactional(rollbackFor = Exception.class)
    public UserVO getUserById(Long id) {
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new RuntimeException("用户不存在");
        }
        return BeanUtil.copyProperties(user, UserVO.class);
    }
}

4.5 module‑web (API layer)

Depends on module-service and contains the SpringBoot entry class.

@SpringBootApplication(scanBasePackages = "com.demo")
@MapperScan("com.demo.dao.mapper")
public class WebApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebApplication.class, args);
    }
}

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("/{id}")
    public Result<UserVO> getUser(@PathVariable Long id) {
        return Result.success(userService.getUserById(id));
    }
}

5. Module dependency relationships

web → service
service → dao
dao → pojo, common
all modules → common

6. Layered design principles

Controller does only three things:

Receive parameters

Call the service layer

Return a unified result

Service is the business core, responsible for transaction control, business decisions, multi‑table operations, and cache logic.

Dao only reads/writes data; it never contains business logic or decisions.

Pojo contains no logic, only fields and getters/setters.

7. Packaging and running

mvn clean package -DskipTests

The executable JAR is generated in module-web/target.

Run with java -jar module-web-1.0.0.jar.

8. Common pitfalls

Mapper not found / Invalid bound statement

Dao module may miss resource packaging for XML files.

@MapperScan path may be incorrect.

Startup class cannot scan other modules

Add scanBasePackages = "com.demo" to the @SpringBootApplication annotation.

Dependency conflicts

Use the parent project's dependencyManagement to unify versions.

Circular dependencies

Extract shared code into module-common and redesign the layer hierarchy.

Configuration files not effective

Place all configuration files under module-web/src/main/resources.

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.

backend-architecturemavenSpringBootmulti-moduleservice-layerdao-layerweb-layer
Java Tech Workshop
Written by

Java Tech Workshop

Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.

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.