{"id":683448,"date":"2026-02-25T13:40:36","date_gmt":"2026-02-25T12:40:36","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=idea&#038;p=683448"},"modified":"2026-03-03T13:55:02","modified_gmt":"2026-03-03T12:55:02","slug":"migrating-to-modular-monolith-using-spring-modulith-and-intellij-idea","status":"publish","type":"idea","link":"https:\/\/blog.jetbrains.com\/ko\/idea\/2026\/02\/migrating-to-modular-monolith-using-spring-modulith-and-intellij-idea","title":{"rendered":"Migrating to Modular Monolith using Spring Modulith and IntelliJ IDEA"},"content":{"rendered":"\n<p>As applications grow in complexity, maintaining a clean architecture becomes increasingly challenging. The traditional <strong>package-by-layer<\/strong> approach of organizing code into <em>controllers<\/em>, <em>services<\/em>, <em>repositories<\/em>, and <em>entities<\/em> packages often leads to tightly coupled code that&#8217;s hard to maintain and evolve.<\/p>\n\n\n\n<p>Spring Modulith, combined with IntelliJ IDEA&#8217;s excellent tooling support, offers a powerful solution for building well-structured modular monoliths.<\/p>\n\n\n\n<p>In this article, we will use a <a href=\"https:\/\/github.com\/sivaprasadreddy\/spring-modulith-workshop\" target=\"_blank\" rel=\"noopener\">bookstore sample application<\/a> as an example to demonstrate Spring Modulith features.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>If you are interested in building a Modular Monolith using Spring and Kotlin, check out <a href=\"https:\/\/blog.jetbrains.com\/kotlin\/2026\/02\/building-modular-monoliths-with-kotlin-and-spring\/\" data-type=\"link\" data-id=\"https:\/\/blog.jetbrains.com\/kotlin\/2026\/02\/building-modular-monoliths-with-kotlin-and-spring\/\">Building Modular Monoliths With Kotlin and Spring<\/a><\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">1. The Problem with Monoliths and Package-by-Layer<\/h2>\n\n\n\n<p>Many Spring Boot applications are organized <strong>by technical layer<\/strong> rather than by business capability. A typical layout looks like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">bookstore\n  |-- config\n  |-- entities\n  |-- exceptions\n  |-- models\n  |-- repositories\n  |-- services\n  |-- web<\/pre>\n\n\n\n<p>This <strong>package-by-layer<\/strong> style causes several problems.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Code Structure Doesn\u2019t Express What the Application Does<\/h3>\n\n\n\n<p>When you open the project, you see \u201crepositories,\u201d \u201cservices,\u201d and \u201cweb,\u201d but not \u201ccatalog,\u201d \u201corders,\u201d or \u201cinventory.\u201d The domain is hidden behind technical folders, which makes it harder for developers to find feature-related code and understand boundaries.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Everything Tends to Become Public<\/h3>\n\n\n\n<p>In a layer-based layout, types in one package are often used from many others. To allow that, classes are made <strong>public<\/strong>, which effectively exposes them to the whole application. There is no clear \u201cpublic API\u201d per feature, and hence anything can depend on anything.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Tight Coupling and Spaghetti Code<\/h3>\n\n\n\n<p>With no explicit boundaries, services and controllers from different features depend on each other\u2019s internals. For example, order logic might call catalog\u2019s <em>ProductService<\/em> directly or reuse internal DTOs. Over time this turns into a tightly coupled <strong>\u201cbig ball of mud\u201d<\/strong> where changing one feature risks breaking others.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Fragile Changes<\/h3>\n\n\n\n<p>Adding or changing a feature often forces you to touch code in <em>repositories<\/em>, <em>services<\/em>, and <em>web<\/em> at once, with no clear \u201cmodule\u201d to test or reason about. Refactoring becomes risky because the impact is hard to see.<br><br><strong>In short:<\/strong> package-by-layer encourages a single, undivided monolith with weak boundaries and unclear ownership. Spring Modulith addresses this by turning your codebase into an explicit set of <strong>modules<\/strong> with clear APIs and enforced boundaries.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. What Benefits Spring Modulith Brings<\/h2>\n\n\n\n<p><a href=\"https:\/\/spring.io\/projects\/spring-modulith\" target=\"_blank\" rel=\"noopener\">Spring Modulith<\/a> helps you build <strong>modular monoliths<\/strong>: one deployable application, but with clear, domain-driven modules and enforced structure.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Explicit Module Boundaries<\/h3>\n\n\n\n<p>Modules are <strong>direct sub-packages<\/strong> of your application\u2019s base package (e.g. <code>com.example.bookstore.catalog<\/code>, <code>com.example.bookstore.orders<\/code>). Spring Modulith treats each as a module and checks that:<\/p>\n\n\n\n<ul>\n<li>Other modules do not depend on <strong>internal<\/strong> types unless they are explicitly exposed.<\/li>\n\n\n\n<li>There are <strong>no circular dependencies<\/strong> between modules.<\/li>\n\n\n\n<li>Dependencies between modules are <strong>declared<\/strong> (e.g. via <code>allowedDependencies<\/code>), so the architecture stays intentional.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Clear Public APIs<\/h3>\n\n\n\n<p>Each module can define a <strong>provided interface<\/strong> (public API): a small set of types and beans that other modules are allowed to use. Everything else is internal. This reduces coupling and makes it obvious how modules interact.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Event-Driven Communication<\/h3>\n\n\n\n<p>Spring Modulith encourages <strong>events<\/strong> for cross-module communication (e.g. <code>OrderCreatedEvent<\/code>). It provides:<\/p>\n\n\n\n<ul>\n<li><strong>@ApplicationModuleListener<\/strong> for module-aware event handling.<\/li>\n\n\n\n<li><strong>Event publication registry<\/strong> (e.g. JDBC) so events can be persisted and processed reliably.<\/li>\n\n\n\n<li><strong>Externalized events<\/strong> (e.g. AMQP, Kafka) to integrate with message brokers and other applications.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<p>This keeps modules loosely coupled and makes it easier to later extract a module into a separate service.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Testability<\/h3>\n\n\n\n<p>You can test <strong>one module at a time<\/strong> with <code>@ApplicationModuleTest<\/code>, controlling which modules and beans are loaded. You mock other modules\u2019 APIs instead of pulling in the whole application, which speeds up tests and keeps them focused.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Documentation and Verification<\/h3>\n\n\n\n<p>Spring Modulith can:<\/p>\n\n\n\n<ul>\n<li><strong>Verify<\/strong> modular structure in tests via <code>ApplicationModules.of(...).verify()<\/code>.<\/li>\n\n\n\n<li><strong>Generate C4-style documentation<\/strong> from the same model.<\/li>\n<\/ul>\n\n\n\n<p><br>So the documented architecture and the actual code stay in sync.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Gradual Migration Path<\/h3>\n\n\n\n<p>You can introduce Spring Modulith into an existing Spring Boot monolith step by step: first refactor to package-by-module, then add the Spring Modulith dependencies and ModularityTest, and fix violations one by one. You don\u2019t need to rewrite the application.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. How to Add Spring Modulith to a Spring Boot Project<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Add the Dependencies<\/h3>\n\n\n\n<p>Use the Spring Modulith BOM and add the <em>core<\/em> and <em>test<\/em> starters:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"xml\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;properties>\n    &lt;spring-modulith.version>2.0.3&lt;\/spring-modulith.version>\n&lt;\/properties>\n\n&lt;dependencyManagement>\n    &lt;dependencies>\n        &lt;dependency>\n            &lt;groupId>org.springframework.modulith&lt;\/groupId>\n            &lt;artifactId>spring-modulith-bom&lt;\/artifactId>\n            &lt;version>${spring-modulith.version}&lt;\/version>\n            &lt;type>pom&lt;\/type>\n            &lt;scope>import&lt;\/scope>\n        &lt;\/dependency>\n    &lt;\/dependencies>\n&lt;\/dependencyManagement>\n\n&lt;dependencies>\n    &lt;!-- other dependencies -->\n    \n    &lt;dependency>\n        &lt;groupId>org.springframework.modulith&lt;\/groupId>\n        &lt;artifactId>spring-modulith-starter-test&lt;\/artifactId>\n        &lt;scope>test&lt;\/scope>\n    &lt;\/dependency>\n&lt;\/dependencies><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Enable IntelliJ IDEA Support<\/h3>\n\n\n\n<p>Spring Modulith support is <strong>bundled in IntelliJ IDEA with the Ultimate Subscription<\/strong> and is enabled by default once the Spring Modulith dependencies are on the classpath.<\/p>\n\n\n\n<p>To confirm the plugin is enabled:<\/p>\n\n\n\n<ol>\n<li>Open <strong>Settings<\/strong> (Ctrl+Alt+S \/ Cmd+,).<\/li>\n\n\n\n<li>Go to <strong>Plugins<\/strong> \u2192 <strong>Installed<\/strong>.<\/li>\n\n\n\n<li>Search for <strong>Spring Modulith<\/strong> and ensure it is checked.<\/li>\n<\/ol>\n\n\n\n<p>You can then use module indicators in the project tree, the Structure tool window, and Modulith-specific inspections and quick-fixes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Add a Modularity Test<\/h3>\n\n\n\n<p>Add a test that verifies your modular structure so that violations are caught in CI:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.sivalabs.bookstore;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.modulith.core.ApplicationModules;\n\nclass ModularityTest {\n    static ApplicationModules modules = ApplicationModules.of(BookStoreApplication.class);\n\n    @Test\n    void verifiesModularStructure() {\n        modules.verify();\n    }\n}<\/pre>\n\n\n\n<p>After refactoring to <strong>package-by-module<\/strong>, this test will fail until all boundary and dependency rules are satisfied. Fixing those failures is the main migration work.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Converting a Monolith into a Modulith: Refactoring to Package-by-Module<\/h2>\n\n\n\n<p>Let&#8217;s see how we can convert a monolith application into a modular monolith one step at a time.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: Reorganize to Package-by-Module<\/h3>\n\n\n\n<p>Move from layer-based packages to <strong>module-based<\/strong> (<em>package-by-module<\/em>) packages. Each top-level package becomes a module.<\/p>\n\n\n\n<p><strong>Target structure (example):<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">bookstore\n  |- config\n  |- common\n  |- catalog\n  |- orders\n  |- inventory<\/pre>\n\n\n\n<p>Practical steps:<\/p>\n\n\n\n<ul>\n<li>Create the new package structure (e.g. <code>catalog<\/code>, <code>orders<\/code>, <code>inventory<\/code>, <code>common<\/code> with subpackages like <code>domain<\/code>, <code>web<\/code>, etc).<\/li>\n\n\n\n<li>Move classes from <code>entities<\/code>, <code>repositories<\/code>, <code>services<\/code>, <code>web<\/code> into the appropriate feature package. Prefer <strong>package-private<\/strong> (no modifier) for types that should stay internal.<\/li>\n\n\n\n<li>Replace a single <strong>GlobalExceptionHandler<\/strong> with <strong>module-specific<\/strong> exception handlers (e.g. <code>CatalogExceptionHandler<\/code>, <code>OrdersExceptionHandler<\/code>) in each module\u2019s <code>web<\/code> (or equivalent) package.<\/li>\n\n\n\n<li>Move and adjust tests to match the new structure.<\/li>\n<\/ul>\n\n\n\n<p><br>After this, the code is organized by feature, but Spring Modulith is not yet enforcing boundaries. Adding the dependency and running <code>ModularityTest<\/code> will surface the next set of issues.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: Fix Module Boundary Violations<\/h3>\n\n\n\n<p>When you run <code>ModularityTest<\/code>, you\u2019ll see errors such as:<\/p>\n\n\n\n<ul>\n<li><strong>Module &#8216;catalog&#8217; depends on non-exposed type &#8230; PagedResult within module &#8216;common&#8217;!<\/strong><\/li>\n\n\n\n<li><strong>Module &#8216;inventory&#8217; depends on non-exposed type &#8230; OrderCreatedEvent within module &#8216;orders&#8217;!<\/strong><\/li>\n\n\n\n<li><strong>Module &#8216;orders&#8217; depends on non-exposed type &#8230; ProductService within module &#8216;catalog&#8217;!<\/strong><\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<p>Fixing these errors is where <strong>module types<\/strong>, <strong>named interfaces<\/strong>, and <strong>public APIs<\/strong> come in.<\/p>\n\n\n\n<p>Add the following dependency to use Spring Modulith features to specify module types, named interfaces, etc:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"xml\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;dependency>\n    &lt;groupId>org.springframework.modulith&lt;\/groupId>\n    &lt;artifactId>spring-modulith-starter-core&lt;\/artifactId>\n&lt;\/dependency><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Use OPEN for Shared \u201cCommon\u201d Modules<\/h4>\n\n\n\n<p>If a module (e.g. <code>common<\/code>) is meant to be used by many others and doesn\u2019t need a strict API, mark it as <strong>OPEN<\/strong> so all its types are considered exposed:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@ApplicationModule(type = ApplicationModule.Type.OPEN)\npackage com.sivalabs.bookstore.common;\n\nimport org.springframework.modulith.ApplicationModule;<\/pre>\n\n\n\n<p>Add this in <code>package-info.java<\/code> in the module\u2019s root package.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Expose Specific Packages with @NamedInterface<\/h4>\n\n\n\n<p>When only certain types (e.g. events or DTOs) should be used by other modules, expose that package via a <strong>named interface<\/strong>:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@NamedInterface(\"order-models\")\npackage com.sivalabs.bookstore.orders.domain.models;\n\nimport org.springframework.modulith.NamedInterface;<\/pre>\n\n\n\n<p>Then other modules can depend on <code>orders::order-models<\/code> (or the whole module) in their <code>allowedDependencies<\/code>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Introduce a Public API (Provided Interface)<\/h4>\n\n\n\n<p>When another module needs to call your module\u2019s logic, don\u2019t expose the internal service. Expose a <strong>facade<\/strong> or <strong>API<\/strong> class in the module\u2019s root package (or a dedicated API package):<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">package com.sivalabs.bookstore.catalog;\n\n@Service\npublic class CatalogApi {\n    private final ProductService productService;\n\n    public CatalogApi(ProductService productService) {\n        this.productService = productService;\n    }\n\n    public Optional&lt;Product> getByCode(String code) {\n        return productService.getByCode(code);\n    }\n}<\/pre>\n\n\n\n<p>Then in the <strong>orders<\/strong> module, depend on <code>CatalogApi<\/code> instead of <code>ProductService<\/code>. Spring Modulith will treat <code>CatalogApi<\/code> as the <strong>provided interface<\/strong> and <code>ProductService<\/code> as internal.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: Declare Explicit Module Dependencies (Optional but Recommended)<\/h3>\n\n\n\n<p>By default, a module may depend on any other module that doesn\u2019t create a cycle. To make dependencies <strong>explicit<\/strong>, list allowed targets in <code>package-info.java<\/code>:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@ApplicationModule(allowedDependencies = {\"catalog\", \"common\"})\npackage com.sivalabs.bookstore.orders;\n\nimport org.springframework.modulith.ApplicationModule;<\/pre>\n\n\n\n<p>If the <strong>orders<\/strong> module later uses something from a module not in this list (e.g. <code>inventory<\/code>), <code>modules.verify()<\/code> will fail and IntelliJ will show a violation. This keeps the dependency graph intentional and documented.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 4: Prefer Event-Driven Communication<\/h3>\n\n\n\n<p>For cross-module side effects (e.g. \u201cwhen an order is created, update inventory\u201d), prefer <strong>events<\/strong> instead of direct calls:<\/p>\n\n\n\n<ul>\n<li><strong>Publishing module<\/strong> (e.g. orders): publishes <code>OrderCreatedEvent<\/code> via <code>ApplicationEventPublisher<\/code>.<\/li>\n\n\n\n<li><strong>Consuming module<\/strong> (e.g. inventory): handles it with <code>@ApplicationModuleListener<\/code> (and optionally event persistence or externalization).<\/li>\n<\/ul>\n\n\n\n<p><br>This avoids the consuming module depending on the publisher\u2019s internals and keeps the path open for later extraction to a separate service or messaging.<\/p>\n\n\n\n<p>Add the following dependency:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;dependency>\n    &lt;groupId>org.springframework.modulith&lt;\/groupId>\n    &lt;artifactId>spring-modulith-events-api&lt;\/artifactId>\n&lt;\/dependency><\/pre>\n\n\n\n<p>Publish events using <code>ApplicationEventPublisher<\/code> and implement event listener using <code>@ApplicationModuleListener<\/code> as follows:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/\/Event Publisher\n@Service\nclass OrderService {\n    private final ApplicationEventPublisher publisher;\n\n    void create(OrderCreateRequest req) {\n       \/\/...\n\tvar event = new OrderCreatedEvent(...);\n       publisher.publish(event);\n    }\n}\n\n\/\/Event Listener\n@Component\nclass OrderCreatedEventHandler {\n    @ApplicationModuleListener\n    void handle(OrderCreatedEvent event) {\n        log.info(\"Received order created event: {}\", event);\n\t \/\/... \n    }\n}<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Event Publication Registry<\/h4>\n\n\n\n<p>The events can be persisted in a persistence store (eg: database) so that they can be processed without losing then on application failures.<\/p>\n\n\n\n<p>Add the following dependency:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"xml\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;dependency>\n   &lt;groupId>org.springframework.modulith&lt;\/groupId>\n   &lt;artifactId>spring-modulith-starter-jdbc&lt;\/artifactId>\n&lt;\/dependency><\/pre>\n\n\n\n<p>Configure the following properties to initialize the events schema and events processing behaviour:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">spring.modulith.events.jdbc.schema-initialization.enabled=true\n# completion-mode options: update | delete | archive\nspring.modulith.events.completion-mode=update\nspring.modulith.events.republish-outstanding-events-on-restart=true<\/pre>\n\n\n\n<p>When the application publishes events, first they will be stored in a database table, and after successful processing they will be deleted or marked as processed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. How does IntelliJ IDEA Help with Inspections and Quick Fixes?<\/h2>\n\n\n\n<p>Spring Modulith violations don\u2019t cause compilation or runtime errors by themselves, they fail <strong>Modulith-specific tests<\/strong> (e.g. <code>ModularityTest<\/code>). IntelliJ IDEA\u2019s Spring Modulith support turns these into <strong>editor-time feedback<\/strong> with inspections and quick-fixes so you can fix structure issues as you code.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Inspections and Severity<\/h3>\n\n\n\n<p>IntelliJ runs a set of <strong>inspections<\/strong> that check your code against Spring Modulith\u2019s rules. By default, they are configured as <strong>errors<\/strong> (red underlines), even though the project still compiles. This helps you treat modularity as a first-class constraint.<\/p>\n\n\n\n<p>You can adjust severity in <strong>Settings \u2192 Editor \u2192 Inspections<\/strong> under the Spring Modulith group if you want to start with warnings.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Violations Shown in the Editor<\/h3>\n\n\n\n<p>As soon as you introduce a dependency that breaks module boundaries, IntelliJ highlights it. For example:<\/p>\n\n\n\n<ul>\n<li>A class in <strong>catalog<\/strong> module using <code>PagedResult<\/code> from <strong>common<\/strong> without <strong>common<\/strong> being OPEN or exposing that type.<\/li>\n\n\n\n<li>A class in <strong>orders<\/strong> using <strong>catalog<\/strong>\u2019s internal <code>ProductService<\/code> instead of the public <code>CatalogApi<\/code>.<\/li>\n\n\n\n<li>A class in <strong>inventory<\/strong> using <strong>orders<\/strong>\u2019 internal <code>OrderCreatedEvent<\/code> type before it is exposed via a named interface.<\/li>\n<\/ul>\n\n\n\n<p><br>You don\u2019t have to run the full test suite to see these issues, they appear as you write or refactor code.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1341\" height=\"325\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2026\/02\/ij-modulith-violation-1.png\" alt=\"\" class=\"wp-image-683449\"\/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Quick-Fixes (Alt+Enter)<\/h3>\n\n\n\n<p>When the cursor is on a Modulith violation, <strong>Alt+Enter<\/strong> (or the lightbulb) opens <strong>quick-fixes<\/strong> that align the code with the modular structure. Typical options:<\/p>\n\n\n\n<ol>\n<li><strong>Annotate the type with @NamedInterface<\/strong>: Expose the class (or its package) as a named interface so other modules can use it.<\/li>\n<\/ol>\n\n\n\n<ol start=\"2\">\n<li><strong>Open the module that contains the type<\/strong>: IntelliJ creates or updates <code>package-info.java<\/code> in that module and marks it as <code>@ApplicationModule(type = ApplicationModule.Type.OPEN)<\/code>, exposing all its types.<\/li>\n\n\n\n<li><strong>Move the component to the base package<\/strong>: Move the bean to the application\u2019s root package so it\u2019s outside any module (use sparingly).<\/li>\n<\/ol>\n\n\n\n<p>Choosing the right fix depends on your design: use <strong>OPEN<\/strong> for shared utility modules, <strong>NamedInterface<\/strong> for a few shared types (e.g. events), and <strong>public API classes<\/strong> for behavioral dependencies.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1253\" height=\"311\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2026\/02\/ij-modulith-violation-quick-fix.png\" alt=\"\" class=\"wp-image-683460\"\/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Bean Injection and Module Boundaries<\/h3>\n\n\n\n<p>IntelliJ\u2019s <strong>Spring bean autocompletion<\/strong> is aware of module boundaries. If you try to inject a bean that belongs to another module and is not part of that module\u2019s public API, the completion list can show a <strong>warning icon<\/strong> next to that bean. This helps you avoid introducing boundary violations when wiring dependencies.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Undeclared Module Dependencies<\/h3>\n\n\n\n<p>When a module has <strong>explicit<\/strong> allowedDependencies (e.g. orders only allow <code>catalog<\/code> and <code>common<\/code>) but you use a type from another module (e.g. <code>inventory<\/code>), IntelliJ reports a violation: the dependency is not declared.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"975\" height=\"344\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2026\/02\/ij-modulith-violation-using-undeclared-modules.png\" alt=\"\" class=\"wp-image-683471\"\/><\/figure>\n\n\n\n<p><strong>Quick-fix:<\/strong> Add the missing module (or the required named interface) to <code>allowedDependencies<\/code> in the module\u2019s <code>package-info.java<\/code>. IntelliJ can suggest adding the dependency.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1358\" height=\"345\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2026\/02\/ij-modulith-add-explicit-dependency.png\" alt=\"\" class=\"wp-image-683482\"\/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Working with allowedDependencies<\/h3>\n\n\n\n<p>In <code>package-info.java<\/code>, when you edit <code>allowedDependencies = {\"...\"}<\/code>, IntelliJ provides:<\/p>\n\n\n\n<ul>\n<li><strong>Completion<\/strong> (Ctrl+Space) with:\n<ul>\n<li><strong>module<\/strong> \u2014 dependency on the whole module.<\/li>\n\n\n\n<li><strong>module::interface<\/strong> \u2014 dependency on a specific named interface.<\/li>\n\n\n\n<li><strong>module::*<\/strong> \u2014 dependency on all named interfaces of that module.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Validation<\/strong>: if a listed module or interface doesn\u2019t exist, IntelliJ highlights the reference so you can fix it before running tests or starting the app.<\/li>\n\n\n\n<li><strong>Navigation<\/strong>: Ctrl+B on a module name in <code>allowedDependencies<\/code> jumps to that module in the Project view.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Circular Dependencies<\/h3>\n\n\n\n<p>Spring Modulith\u2019s verification detects <strong>cycles<\/strong> between modules, e.g.:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Cycle detected: Slice catalog ->\n                Slice orders ->\n                Slice catalog<\/pre>\n\n\n\n<p>To fix this, you need to break the cycle in code: remove the dependency (e.g. <strong>catalog<\/strong> \u2192 <strong>orders<\/strong>) by using events, moving shared types to <strong>common<\/strong>, or redefining which module owns which responsibility.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Visualizing Modules in IntelliJ IDEA<\/h3>\n\n\n\n<p><strong>Project tool window (Alt+1):<\/strong> Top-level modules are marked with a <strong>green lock<\/strong>; internal (non-exposed) components can be marked with a <strong>red lock<\/strong>. This gives a quick visual of boundaries.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" width=\"504\" height=\"672\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2026\/02\/intellij-spring-modulith-support-1-1.png\" alt=\"\" class=\"wp-image-683504\" style=\"width:500px\"\/><\/figure>\n\n\n\n<p><strong>Structure tool window (Alt+7):<\/strong> With the main <code>@SpringBootApplication<\/code> class selected, open <strong>Structure<\/strong> and use the <strong>Modules<\/strong> node to see the list of application modules, their IDs, allowed dependencies, and named interfaces.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"573\" height=\"323\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2026\/02\/intellij-spring-modulith-modules-in-structure-toolwindow.png\" alt=\"\" class=\"wp-image-683515\"\/><\/figure>\n\n\n\n<p>Using both views helps you understand and fix dependency and boundary issues quickly.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6. Verifying and Evolving Your Modular Structure<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Keep Running ModularityTest<\/h3>\n\n\n\n<p>After each refactoring step, run <code>ModularityTest<\/code>. It should pass, once we have completed the following:<\/p>\n\n\n\n<ul>\n<li>All cross-module references go to exposed types (<strong>OPEN<\/strong> modules, named interfaces, or public API classes).<\/li>\n\n\n\n<li>There are no circular dependencies.<\/li>\n\n\n\n<li>Any explicit <code>allowedDependencies<\/code> include all modules (and interfaces) that are actually used.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">6.2 Generate Documentation<\/h3>\n\n\n\n<p>You can extend the test to generate <a href=\"https:\/\/c4model.com\/\" data-type=\"link\" data-id=\"https:\/\/c4model.com\/\" target=\"_blank\" rel=\"noopener\">C4-style documentation<\/a> so the architecture is visible and up to date:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@Test\nvoid verifiesModularStructure() {\n    modules.verify();\n    new Documenter(modules).writeDocumentation();\n}<\/pre>\n\n\n\n<p>Output is written under <code>target\/spring-modulith-docs<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Test Modules in Isolation<\/h3>\n\n\n\n<p>Use <code>@ApplicationModuleTest<\/code> to load only one module (and optionally its dependencies) and mock other modules dependencies:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@ApplicationModuleTest(mode = BootstrapMode.STANDALONE)\n@Import(TestcontainersConfiguration.class)\n@AutoConfigureMockMvc\nclass OrderRestControllerTests {\n    @MockitoBean\n    CatalogApi catalogApi;\n    \/\/ ...\n}<\/pre>\n\n\n\n<p>Bootstrap modes control how much of the application is loaded, making tests faster and more focused.<\/p>\n\n\n\n<ul>\n<li><strong>STANDALONE (default):<\/strong> Load only the module being tested<\/li>\n\n\n\n<li><strong>DIRECT_DEPENDENCIES:<\/strong> Load the module and its direct dependencies<\/li>\n\n\n\n<li><strong>ALL_DEPENDENCIES:<\/strong> Load all transitive dependencies<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">7. Conclusion<\/h2>\n\n\n\n<p>Building a <strong>modular monolith<\/strong> with Spring Modulith improves long-term maintainability and prepares the codebase for possible extraction of modules into separate services. The main ideas:<\/p>\n\n\n\n<ul>\n<li><strong>Avoid package-by-layer:<\/strong> Organize by <strong>feature\/module<\/strong> (package-by-feature) so that the structure reflects the domain.<\/li>\n\n\n\n<li><strong>Define clear boundaries:<\/strong> Use <strong>OPEN<\/strong> for shared utility modules, <strong>named interfaces<\/strong> for shared types (e.g. events), and <strong>public API classes<\/strong> for cross-module behavior.<\/li>\n\n\n\n<li><strong>Declare dependencies:<\/strong> Use allowedDependencies so the intended dependency graph is explicit and violations are caught early.<\/li>\n\n\n\n<li><strong>Prefer events<\/strong> for cross-module side effects to keep coupling low.<\/li>\n\n\n\n<li><strong>Verify continuously<\/strong> with ModularityTest and optional documentation generation.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<p><strong>IntelliJ IDEA\u2019s Spring Modulith support<\/strong> turns modularity into a day-to-day concern: module indicators, Modulith inspections, quick-fixes, and dependency completion help you respect boundaries and fix common issues without leaving the editor. For more detail, see <a href=\"https:\/\/www.jetbrains.com\/help\/idea\/spring-modulith.html\" target=\"_blank\" rel=\"noopener\">IntelliJ IDEA\u2019s Spring Modulith documentation<\/a>.<\/p>\n\n\n\n<p>Start by refactoring one area to package-by-feature, add Spring Modulith and a modularity test, then fix violations step by step using IntelliJ IDEA\u2019s feedback to guide the way.<\/p>\n","protected":false},"author":1517,"featured_media":683814,"comment_status":"closed","ping_status":"closed","template":"","categories":[4759],"tags":[40,3211,9043],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/idea\/683448"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/idea"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/types\/idea"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/users\/1517"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/comments?post=683448"}],"version-history":[{"count":9,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/idea\/683448\/revisions"}],"predecessor-version":[{"id":685084,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/idea\/683448\/revisions\/685084"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/media\/683814"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/media?parent=683448"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/categories?post=683448"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/tags?post=683448"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/ko\/wp-json\/wp\/v2\/cross-post-tag?post=683448"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}