structural
A lightweight Gradle plugin for defining which packages can import from each other in Kotlin and Java projects. Enforce an architecture in places where Gradle modules won't work.
Quick start
1. Apply the plugin
Add Maven Central to plugin resolution in settings.gradle.kts (merge this into your existing
pluginManagement block, if present):
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}
Apply Structural in the build.gradle.kts of the project you want to check. Replace <latest>
with the version shown in the Maven Central badge above:
plugins {
id("com.adrianczuczka.structural") version "<latest>"
}
repositories {
mavenCentral()
}
2. Define your boundaries
Create structural.yml next to that project's build file:
rules:
- data <- domain -> ui
This allows data and ui to import from domain. Imports in the reverse direction, or between
data and ui, fail the check.
3. Run the check
./gradlew structuralCheck
A forbidden import produces a message like this (path shortened):
🚨 Import rule violations found:
/src/main/kotlin/com/example/ui/Screen.kt:3 : `com.example.ui` cannot import from `com.example.data`
How rules work
data <- domain means data may import from domain. You can express the same rule as
domain -> data, or use a map with the importer as the key:
rules:
data:
- domain
ui:
- domain
For either configuration above:
| Importing package | Imported package | Result |
|---|---|---|
com.example.ui |
com.example.domain |
Allowed |
com.example.data |
com.example.domain |
Allowed |
com.example.domain |
com.example.data |
Forbidden |
com.example.ui |
com.example.data |
Forbidden |
Every package identifier in rules: becomes a tracked layer. Short names such as data match
that package segment and its subpackages. Imports within a layer are allowed; imports between
tracked layers need a direct or inherited permission. Imports from untracked packages, including standard and
third-party libraries, are allowed.
See configuration for fully qualified names, wildcards, and more rule formats, or class allowlists for exceptions to package boundaries.
Add it to your build
Make structuralCheck a dependency of Gradle's check task to run it alongside your tests:
tasks.named("check") {
dependsOn("structuralCheck")
}
Violations fail the task, so CI can run ./gradlew check to enforce the same boundaries.
Structural checks the main source set when a JVM plugin is applied, and supports custom source
locations. The check is incremental and cacheable. See sources and compatibility.
Adopt it in an existing project
Snapshot existing violations so the build only fails on new ones:
./gradlew structuralGenerateBaseline
This writes baseline.xml in the project directory. Commit it so the whole team uses the same
baseline. See baselines for custom paths and shared baselines across modules.
Documentation
- Configuration — rule formats, package matching, and wildcards
- Class allowlist — exceptions, token syntax, and limitations
- Sources and compatibility — source selection and Kotlin isolation
- Baselines — existing violations and multi-module aggregation
- Migration — inherited permissions in 2.0 and Gradle configuration changes
Examples
Explore the Kotlin and Java sample projects and their shared rules. Both contain intentional violations to demonstrate what Structural catches.
