codingstairs
NotesEDULifeContact
⌕Search⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

Get in touch

Send without signing in. Add your email if you'd like a reply.

  • Leave a message anonymously →
  • ✉ warragon112@gmail.com
  • KakaoTalk Open Chat ↗

© 2026 codingstairs

  • Notes
  • EDU
  • Search
  • Life
  • Contact
  • Legal
  • RSS
  • GitHub
Notes›backend

Spring Multi-Module (Gradle)

Published 2026-04-28· Updated 2026-05-18·0 views

Spring Multi-Module (Gradle)

When several Spring applications live in a single repository, we have to settle the location of shared code, the build unit, and the direction of dependencies. Gradle's multi-project build is the standard tool for handling this kind of structure.

1. About Gradle multi-project

Gradle itself was started by Hans Dockter, first released publicly in 2007, and reached 1.0 in 2012. Multi-project builds are one of Gradle's core features — several subprojects sit under a root project and each is built as an independent module.

The core file set:

File Role
settings.gradle(.kts) Sets the root name and registers subprojects with include.
Root build.gradle(.kts) Applies common configuration via subprojects {} · allprojects {}.
<module>/build.gradle(.kts) Per-module dependencies and plugins.

Spring Modulith is a library released by the Spring team in 2022 that manages and verifies module boundaries at the package level inside a single application. It is a different flavor from multi-project builds.

2. include in settings.gradle

rootProject.name = 'platform'
include 'common'
include 'foo-api'
include 'bar-api'

Each include-ed directory is compiled, tested, and packaged as an independent module. Dependencies between subprojects are expressed as project references like dependencies { implementation project(':common') }.

3. subprojects block

In the root build.gradle we can apply common configuration to every subproject at once.

subprojects {
    apply plugin: 'java'
    java {
        toolchain {
            languageVersion = JavaLanguageVersion.of(21)
        }
    }
    repositories { mavenCentral() }
    tasks.withType(Test) { useJUnitPlatform() }
}

allprojects includes the root, subprojects excludes it. The Spring Boot plugin is usually applied only to runnable modules that need bootJar, while library modules apply only io.spring.dependency-management — that's the typical separation.

4. The common + service-api pattern

A common shape when several services grow inside the same repository:

platform/
├── settings.gradle
├── build.gradle               # subprojects common
├── common/                    # domain-agnostic utilities, exceptions, DTO bases
│   └── build.gradle
├── foo-api/                   # service A runnable module (bootJar)
│   └── build.gradle
└── bar-api/                   # service B runnable module (bootJar)
    └── build.gradle

common is built as a library jar, and *-api modules are built as runnable boot jars. The dependency direction is one-way — *-api → common. The reverse (common → *-api) breaks the module boundary.

5. Comparison with Maven multi-module

Item Gradle Maven
First release 2007 2004 (1.0)
Build script Groovy/Kotlin DSL XML (pom.xml)
Multi-module declaration settings.gradle include Parent pom.xml <modules>
Build cache Build/configuration cache built-in Not provided by default
Parallel build Supported by default -T option

Maven is declarative and convention-driven, while Gradle offers script-based flexibility. Saying one is absolutely superior is hard — the option the team is more familiar with usually has lower operational cost.

6. Spring Modulith and a single module

Spring Modulith treats packages as modules inside a single application jar and verifies and documents module dependencies. If multi-project is about separating build units, Modulith is the approach where the runtime is one piece while code boundaries are enforced. The two can coexist.

For a small service, not splitting modules and just separating by package can be operationally simpler. Module separation is a tradeoff between gains in build time, reusability, and ownership separation against the configuration complexity it adds.

7. Module split criteria

  • By domain — order-api · payment-api · common. Natural when service units are clear.
  • By technical layer — web · domain · infrastructure. Often used in hexagonal or clean architecture variants.

Splitting by domain and layer simultaneously explodes the module count. Usually we cut by domain first and split layers only inside large domains.

8. Version catalogs

Gradle 7.0+ provides a standard mechanism for managing dependency versions.

# libs.versions.toml
[versions]
spring-boot = "4.0.6"

[libraries]
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" }

When several modules share the same version, we can treat it as a single source.

9. Common pitfalls

Cyclic dependency — a → b → a. Gradle blocks this at build time, but it can be worked around in unintended ways (for example, lifting an interface up to common).

Unintended bootJar application — applying the Spring Boot plugin as-is to a library module can prevent a regular jar from being produced. We make the bootJar { enabled = false } and jar { enabled = true } combination explicit.

api vs implementation — dependencies exposed via api propagate to downstream projects. To keep the surface small, we default to implementation.

Sharing test fixtures — for sharing test helpers across modules, the java-test-fixtures plugin provides a standardized way.

Closing thoughts

Bringing multi-module into a small service makes the build and configuration burden grow faster than the code itself. It is safer to introduce it once two or more domains are clearly distinct. Once it settles, the gains from build cache, parallel builds, and ownership separation become visible.

Next

  • spring-webflux-vs-mvc

See Gradle Multi-Project Builds · Gradle Version Catalogs · Spring Boot Gradle Plugin · Spring Modulith · Maven Multi-Module Project · Java Test Fixtures.

More in backend

All in this category →
  • Wrap public OpenAPIs with your own BFF
  • Email Delivery and OTP — SMTP
  • Audit Log — logAdminAction pattern
  • WebSocket and SSE — real-time communication
  • REST API introduction
  • OpenAPI Specification