structural

Introduction: A lightweight Gradle plugin for enforcing package dependency rules in Android & Kotlin projects.
More: Author   ReportBugs   
Tags:

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.

Maven Central Build GitHub issues License

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

Examples

Explore the Kotlin and Java sample projects and their shared rules. Both contain intentional violations to demonstrate what Structural catches.

Apache 2.0 license

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools
AI Daily Digest