


Make Small, Compatible Changes in a Java Project Split Into Modules
Use when an AI changes a Java project split into modules. It reads the build and CI rules first, keeps older Java versions and module dependencies working, tests at the right level, and asks before wide or risky changes.
To use this AI rule
## Read the Build, Formatting, and CI Rules Before Editing
Treat repository guidance, build configuration, formatting rules, and continuous-integration checks as executable contracts. Read the applicable instructions before editing and identify which rules are mechanically enforced.
- Locate the canonical build wrapper, formatter, static checks, test commands, and contribution policy.
- Trace the owning module and its dependencies before changing code.
- Prefer established project utilities and patterns over locally convenient alternatives.
- Distinguish source compatibility, build-tool requirements, runtime support, and distribution behavior.
- Do not disable a quality gate merely to make a change pass.
Use the smallest authoritative input that controls the requested behavior.
## Keep Code Working on the Declared Java Version and Dependencies
A build may require a newer toolchain while its source remains compatible with an older runtime. Compile and test against the declared source level; do not assume that a newer local environment makes newer language features or library APIs acceptable.
- Replace unsupported convenience APIs with compatible equivalents.
- Use the repository's relocated or shaded dependency namespaces when required.
- Prefer project-owned utilities for validation, serialization, collections, files, futures, and time.
- Keep direct third-party imports out when the distribution isolates those libraries.
- Discuss compatibility, licensing, packaging, and transitive impact before adding a dependency.
Verify compatibility through the repository's designated build target, not by local compilation alone.
## Use Factories, Builders, and Immutable Results in Public APIs
Mark APIs according to their intended stability: stable public contracts, evolving public contracts, and internal implementation surfaces must remain distinct.
- Return interfaces from factories and hide construction behind private constructors or static creation methods.
- Use builders for multi-step configuration, with fluent methods and one validated construction point.
- Keep result objects immutable with final state, no setters, and complete equality, hashing, and string representations.
- Provide generic and typed interfaces when callers need both forms without unsafe casts.
- Document thread-safety expectations on public types.
Changing a public contract requires compatibility analysis and the appropriate versioning path.
## Keep Module Dependencies Pointing in One Direction
Map the module graph before implementation. Foundation modules should own shared types and interfaces; protocol or transport layers build above them; peer client and server modules should not cross-depend; integrations belong above stable lower layers.
- Define shared interfaces in the lowest appropriate module and implementations above them.
- Keep connectors and optional integrations from leaking dependencies into the core.
- Prevent circular dependencies and convenience imports across peer modules.
- Mirror production package structure in tests.
- Place generated code, distribution assets, documentation generators, and test utilities in their designated modules.
When a feature crosses boundaries, change each owning layer explicitly rather than bypassing the architecture.
## Keep Error Types Distinct and Protect Shared State With Locks
Validate inputs at API boundaries with project-standard precondition helpers and actionable messages. Preserve the exception hierarchy so callers can distinguish user errors, retryable failures, and unchecked internal failures.
- Propagate asynchronous failures through the established future utilities and error handlers.
- Compose asynchronous work rather than blocking it without a documented reason.
- Protect shared mutable state with explicit locks and document guarded fields.
- Use visibility primitives only where access outside the lock requires them.
- Close resources through structured resource management.
- Make close operations idempotent when repeated cleanup is possible.
Do not hide checked failures or collapse retryable and permanent errors into one undifferentiated exception.
## Keep Configuration Keys and Message Schemas Compatible
Define configuration through typed builders with defaults, descriptions, optional values, and deprecated-key handling where applicable. Use hierarchical, consistent keys and access values through the repository's configuration abstraction.
For serialization and remote messages:
- preserve the declared schema and protocol version;
- use only field forms supported by that version;
- regenerate code with the canonical generator after schema changes;
- keep wire messages immutable and mark internal transport types accordingly;
- represent optional fields explicitly;
- never edit generated files when a generation workflow owns them.
Update compatibility tests and generated documentation whenever a configuration or wire contract changes.
## Write Unit and Integration Tests With the Project's Test Tools
Use the repository's chosen test framework, assertion library, naming conventions, extensions, and base fixtures. Do not mix in alternative assertions or timeout mechanisms that conflict with the global harness.
- Write focused unit tests for local behavior and integration tests for cross-component contracts.
- Use descriptive test names that state the behavior under test.
- Assert both successful results and expected failures.
- Reuse supported cluster, storage, or service fixtures instead of creating parallel infrastructure.
- Run the narrow affected test first, then the directly affected suite.
- Confirm that the test fails for the intended reason before relying on it to prove a fix.
Test through public behavior whenever an internal implementation detail is not part of the contract.
## Build and Check Through the Project's Build Wrapper
Use the repository wrapper for builds, formatting, static analysis, license checks, code generation, and tests. Canonical commands may prepare generated assets or enforce compatibility that direct tool invocations miss.
- Build with the documented toolchain while preserving the declared source target.
- Start local service dependencies and components in the documented order.
- Edit only supported configuration files and values.
- Run formatting before static checks.
- Run affected suites before broader verification.
- Use the coverage profile when the change can alter tested behavior.
Understand the continuous-integration stages and reproduce the relevant stage locally before reporting completion.
## Follow the Formatter and Keep Unrelated Changes Out of the Diff
Follow the enforced import order, modifier order, array syntax, brace rules, whitespace policy, and naming conventions. Let the canonical formatter resolve mechanical style.
- Avoid wildcard imports and repository-prohibited comment markers or attribution tags.
- Keep public and protected APIs documented when the project requires it.
- Respect file-size limits by extracting coherent responsibilities, not by compressing readability.
- Update documentation when commands, configuration, compatibility, or public behavior changes.
- Review the final diff for unrelated rewrites, accidental generated-file edits, secrets, and incomplete cleanup.
Do not combine a focused behavior change with an unrelated refactor.
## Publish Changes Through Review Branches, Never by Force-Pushing
Work on an authorized branch and verify every remote before pushing. Keep upstream or shared branches protected; publish changes through the project's review path rather than writing directly to protected targets.
- Rebase or update from the canonical base branch using the documented workflow.
- Write a concise, component-scoped commit subject and explain motivation in the body when needed.
- Review the complete branch diff before publishing.
- Run formatting, static checks, license checks, and affected tests.
- Include a plain summary and test plan in the review request.
- Identify assisted changes for human review when the contribution policy requires it.
Never force-push a shared branch or use destructive history operations without explicit authorization.
## Ask Before Wide or Destructive Work Like New Dependencies or Resets
Proceed autonomously with scoped reading, local builds, tests, formatting, diagnostics, and fixes that follow existing rules. Ask before broad or consequential work, including cross-module refactors, new dependencies, schema migrations, build-system changes, continuous-integration changes, or destructive operations.
Without explicit permission, never:
- commit credentials or sensitive values;
- push directly to a protected upstream target;
- force-push shared history or delete branches;
- run hard resets or destructive cleanup;
- bypass hooks, static checks, or formatting rules;
- modify generated output instead of its source;
- weaken quality gates to obtain a passing result.
Before completion, verify tests, formatting, static checks, required headers, and the full intended diff. When scope or reversibility is uncertain, stop and explain the decision needed.
