# How the Multi-Module Gradle Build Works in Stirling-PDF

> Understand the Stirling-PDF multi-module Gradle build. Learn how it manages shared versions, integrates proprietary features, and builds a single runnable JAR.

- Repository: [Stirling Tools/Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF)
- Tags: internals
- Published: 2026-03-01

---

**Stirling-PDF uses a three-module Gradle build—`:stirling-pdf`, `:common`, and `:proprietary`—where the root project coordinates shared versions, conditionally includes optional closed-source features, and aggregates tasks so that only the core module produces a runnable Spring Boot JAR.**

The multi-module Gradle build in Stirling-PDF cleanly separates the main application, shared utilities, and optional proprietary extensions. This architecture allows developers to build a minimal backend-only artifact or a full-featured distribution with the React frontend embedded, while optionally excluding closed-source code for security-hardened deployments.

## Project Structure and Module Overview

The repository is organized into three logical modules registered in the root `settings.gradle`. Each module maps to a physical directory under `app/` and serves a distinct purpose in the build lifecycle.

### Module Declaration in settings.gradle

The root `settings.gradle` registers the sub-projects and maps their logical names to physical directories:

```gradle
rootProject.name = 'Stirling PDF'

include 'stirling-pdf', 'common', 'proprietary'

project(':stirling-pdf').projectDir = file('app/core')
project(':common'      ).projectDir = file('app/common')
project(':proprietary' ).projectDir = file('app/proprietary')

```

This structure allows developers to reference modules by name (e.g., `project(':common')`) while keeping the source organized under `app/core`, `app/common`, and `app/proprietary`.

### Logical Module Mapping

| Module | Directory | Purpose |
|--------|-----------|---------|
| `:stirling-pdf` | `app/core` | Main Spring Boot application; produces the executable JAR and optionally embeds the React UI |
| `:common` | `app/common` | Shared libraries (PDFBox wrappers, sanitizers, metadata extractors) used by all modules |
| `:proprietary` | `app/proprietary` | Optional closed-source features; conditionally included based on security flags |

## Root Build Configuration (build.gradle)

The root `build.gradle` acts as the central coordination point for the multi-module Gradle build. It defines shared versions, applies common plugins to all sub-projects, and implements conditional logic for including the proprietary module.

### Shared Version Management

The root project uses `ext` properties to centralize version numbers, ensuring consistency across modules:

```gradle
ext {
    springBootVersion = "4.0.3"
    pdfboxVersion     = "3.0.6"
    modernJavaVersion = 21
}

```

Sub-projects reference these properties in their dependency declarations, creating a single source of truth for library versions.

### Conditional Module Inclusion

The build uses environment variables and project properties to conditionally include the proprietary module. This allows security-hardened builds to exclude closed-source code:

```gradle
ext.isSecurityDisabled = { ->
    System.getenv('DOCKER_ENABLE_SECURITY') == 'false' ||
    System.getenv('DISABLE_ADDITIONAL_FEATURES') == 'true' ||
    (project.hasProperty('DISABLE_ADDITIONAL_FEATURES') &&
     System.getProperty('DISABLE_ADDITIONAL_FEATURES') == 'true')
}

if (rootProject.ext.isSecurityDisabled()) {
    implementation project(':proprietary')
}

```

When `DOCKER_ENABLE_SECURITY` is set to `false` or `DISABLE_ADDITIONAL_FEATURES` is `true`, the proprietary module is excluded from the build.

### Aggregated Build Tasks

The root project defines custom tasks to synchronize versions across the backend, desktop (Tauri), and simulation configurations:

* **`syncAppVersion`** – Updates [`tauri.conf.json`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/tauri.conf.json) and simulation source files with the current project version
* **`writeVersion`** – Generates `version.properties` in `app/common/src/main/resources` for runtime access

These tasks are hooked into the `processResources` lifecycle:

```gradle
tasks.register('syncAppVersion') { … }
tasks.register('writeVersion', WriteProperties) { … }

```

The root `build` task aggregates the core JAR, restart helper, and version synchronization:

```gradle
tasks.named('build') {
    dependsOn ':stirling-pdf:bootJar', 'buildRestartHelper', 'syncAppVersion'
}

```

## Core Module: stirling-pdf (app/core)

The `:stirling-pdf` module is the only module configured to produce a runnable artifact. It contains the Spring Boot application, REST controllers, and optional embedded React frontend assets.

### Spring Boot JAR Configuration

The core module explicitly enables `bootJar` and disables the standard `jar` task to ensure only a single executable artifact is produced:

```gradle
bootJar {
    enabled = true
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    zip64 = true
    exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.EC'
    manifest {
        attributes('Implementation-Title': 'Stirling-PDF',
                   'Implementation-Version': project.version)
    }
}

jar { enabled = false }

```

The `zip64 = true` setting allows the JAR to exceed the 4GB limit, necessary for distributions containing multiple PDF engines and language packs.

### Frontend Integration

The build supports conditional frontend compilation via the `buildWithFrontend` project property:

```gradle
def buildWithFrontend = project.hasProperty('buildWithFrontend') && project.property('buildWithFrontend') == 'true'

if (buildWithFrontend) {
    processResources.dependsOn copyFrontendAssets
} else {
    processResources.dependsOn copyApiLandingPage
}

```

When `buildWithFrontend=true`, custom tasks (`npmInstall`, `npmBuild`, `copyFrontendAssets`) compile the React application and copy the resulting static files into `src/main/resources/static`. When disabled, a minimal API landing page is served instead.

## Common and Proprietary Modules

### Shared Libraries (app/common)

The `:common` module acts as a library dependency for the other modules. It disables the Spring Boot run task since it is not an executable application:

```gradle
bootRun { enabled = false }

```

Dependencies are declared using the `api` configuration, making them transitively available to consumers:

```gradle
api 'org.apache.pdfbox:pdfbox:$pdfboxVersion'
api 'org.springframework.boot:spring-boot-starter-webmvc'

```

This module houses PDFBox wrappers, HTML sanitizers, and metadata extraction utilities used by both the core application and optional proprietary extensions.

### Optional Extensions (app/proprietary)

The `:proprietary` module contains closed-source or optional features. It follows the same structure as `:common` but is conditionally included in the multi-module Gradle build based on the `isSecurityDisabled` predicate. When excluded, the build produces a fully open-source artifact; when included, additional enterprise features are compiled into the final JAR.

## Build Commands and Workflows

| Goal | Command | Description |
|------|---------|-------------|
| **Full build with UI** | `./gradlew clean build -PbuildWithFrontend=true` | Compiles all modules, builds React frontend, and packages everything into the Spring Boot JAR |
| **Backend-only build** | `./gradlew clean build` | Builds core and common modules only; serves minimal API landing page |
| **Run application** | `./gradlew bootRun` | Starts the Spring Boot server on port 8080 via the core module |
| **Generate OpenAPI** | `./gradlew generateOpenApiDocs` | Creates [`SwaggerDoc.json`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/SwaggerDoc.json) using the SpringDoc plugin |
| **Sync versions** | `./gradlew syncAppVersion` | Updates Tauri and simulation configurations with current version |
| **Build specific module** | `./gradlew :stirling-pdf:bootJar` | Produces only the runnable JAR without full aggregation |

## Summary

- The **multi-module Gradle build** in Stirling-PDF organizes code into three logical modules—`:stirling-pdf`, `:common`, and `:proprietary`—registered in `settings.gradle` with physical directory mappings.
- The **root `build.gradle`** centralizes version management via `ext` properties, applies common plugins through `subprojects`, and conditionally includes the proprietary module based on environment variables.
- Only the **`:stirling-pdf` module** produces a runnable artifact; it configures `bootJar` with `zip64` support and conditionally embeds React frontend assets when `-PbuildWithFrontend=true` is passed.
- The **`:common` module** provides transitive dependencies via `api` declarations and disables `bootRun`, while the **`:proprietary` module** is excluded from security-hardened builds when `DOCKER_ENABLE_SECURITY=false`.

## Frequently Asked Questions

### How do I build Stirling-PDF without the proprietary module?

Set the environment variable `DOCKER_ENABLE_SECURITY` to `false` or define the project property `DISABLE_ADDITIONAL_FEATURES=true`. The root `build.gradle` evaluates the `isSecurityDisabled` closure and excludes the `:proprietary` module from the dependency graph, producing a fully open-source artifact.

### Why does only the core module produce a JAR file?

The `:stirling-pdf` module explicitly enables `bootJar` and disables the standard `jar` task, while `:common` and `:proprietary` are configured as library modules with `bootRun` disabled. This ensures that only one executable Spring Boot artifact is generated, avoiding confusion between runnable and library JARs.

### How do I include the React frontend in the build?

Pass the `-PbuildWithFrontend=true` flag when running Gradle. This triggers the `npmInstall` and `npmBuild` tasks, compiles the React application, and copies the resulting static assets into `src/main/resources/static` via `copyFrontendAssets`. Without this flag, the build includes only a minimal API landing page.

### What is the purpose of the common module?

The `:common` module houses shared utilities such as PDFBox wrappers, HTML sanitizers, and metadata extractors that are reused across the core application and optional proprietary extensions. It declares dependencies using the `api` configuration, making them transitively available to any module that depends on `:common`, while disabling Spring Boot-specific tasks since it is not an executable application.