$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

AI Coding Rules for Java Teams

Java's verbosity is where AI shines — but without rules, it generates pre-Java 17 patterns, raw types, and checked exception anti-patterns.

8 min read·June 12, 2025

AI generates Java 8 patterns in 2026 — unless you tell it not to

Records, sealed classes, Stream API, and modern Spring Boot conventions

Why Java Needs Specific AI Coding Rules

Java has evolved dramatically since Java 8 — records, sealed classes, pattern matching, text blocks, virtual threads. But AI assistants trained on decades of Java code default to the patterns that dominate their training data: verbose POJOs with getters/setters, raw types, checked exception hierarchies, and pre-Stream iteration patterns.

The result is AI-generated Java that looks like it was written in 2015. It compiles, it works, but it ignores every modern feature that makes Java productive. Your team writes concise code with records and pattern matching; the AI writes 50-line POJOs with boilerplate equals/hashCode.

A handful of rules that say 'use modern Java' with specific examples transforms AI output from legacy-style to modern-style immediately. The AI knows about these features — it just needs permission to use them by default.

Rule 1: Use Modern Java Features by Default

The single most impactful rule for Java teams: tell the AI which Java version you're on and which modern features to prefer. Without this, the AI generates the broadest-compatible code — which means Java 8 patterns.

The rule: 'This project uses Java 21. Use records instead of POJOs for immutable data classes. Use sealed interfaces for type hierarchies. Use pattern matching in switch expressions and instanceof checks. Use text blocks for multi-line strings. Use var for local variables when the type is obvious from the right-hand side.'

Be explicit about which features to use and when. 'Use records for DTOs, request/response objects, and value types. Use regular classes for mutable entities, services, and components with behavior.' This prevents the AI from using records everywhere, including where they don't fit.

💡 Version Matters

Start your CLAUDE.md with 'This project uses Java 21.' Without this, the AI defaults to Java 8 patterns — the broadest-compatible code from its training data.

Rule 2: Stream API and Collection Patterns

AI assistants frequently generate for-each loops with manual accumulation instead of Stream operations. The code works but misses the declarative, composable patterns that modern Java encourages.

The rule: 'Use Stream API for collection transformations: filter, map, reduce, collect. Prefer Stream.toList() over Collectors.toList() (Java 16+). Use Optional instead of null checks — never return null from methods that could return Optional. Chain stream operations fluently; break into named variables only when the chain exceeds 4 operations.'

For common patterns: 'Use Map.of() and List.of() for immutable collections. Use Map.computeIfAbsent() instead of check-then-put patterns. Use Stream.flatMap() for nested collections instead of nested loops.'

  • Stream API for all collection transformations — no manual for-each accumulation
  • Optional instead of null — never return null where Optional fits
  • Map.of() / List.of() for immutable collections — not Collections.unmodifiable*()
  • Stream.toList() over Collectors.toList() (Java 16+)
  • Limit stream chains to 4 operations before extracting named variables

Rule 3: Spring Boot Conventions

Most enterprise Java projects use Spring Boot, and the AI's Spring patterns are often a generation behind. Constructor injection instead of field injection, proper use of Spring profiles, and modern WebFlux patterns need explicit rules.

The rule: 'Use constructor injection exclusively — never @Autowired on fields. Use @RequiredArgsConstructor (Lombok) or explicit constructors. Use Spring profiles for environment-specific configuration. Use @ConfigurationProperties for typed config instead of @Value annotations. Use ResponseEntity for explicit HTTP response control.'

For Spring Boot 3+: 'Use jakarta.* imports, not javax.*. Use ProblemDetail for RFC 7807 error responses. Use Spring Security's SecurityFilterChain configuration, not WebSecurityConfigurerAdapter (deprecated). Use virtual threads with spring.threads.virtual.enabled=true where appropriate.'

⚠️ Spring Boot 3

Spring Boot 3 uses jakarta.* imports, not javax.*. The AI frequently generates javax.* from older training data. One rule ('Use jakarta.* exclusively') prevents every import error.

Rule 4: Exception Handling Strategy

Java's checked exception system is where AI assistants make the most controversial decisions. Without guidance, the AI either catches everything with `catch (Exception e)` or creates a 10-level checked exception hierarchy that makes every method signature unreadable.

The rule: 'Use unchecked exceptions (extending RuntimeException) for business logic errors. Define a base ApplicationException with error code and HTTP status. Only use checked exceptions at system boundaries (I/O, network). Never catch Exception or Throwable — catch specific types. Never swallow exceptions with empty catch blocks — always log or rethrow.'

For REST APIs: 'Use @ControllerAdvice with @ExceptionHandler for centralized exception mapping. Map business exceptions to HTTP responses in one place, not scattered across controllers.'

Rule 5: Testing with JUnit 5 and Mockito

AI assistants sometimes generate JUnit 4 tests (using @RunWith, @Rule) instead of JUnit 5 (using @ExtendWith, @Nested). They also tend to over-mock — mocking everything including the class under test.

The rule: 'Use JUnit 5 exclusively. Use @Nested classes to group related tests. Use @DisplayName for human-readable test names. Use AssertJ for fluent assertions (assertThat(result).isEqualTo(expected)), not assertEquals. Use Mockito's @Mock and @InjectMocks for unit tests. Use @SpringBootTest with @AutoConfigureMockMvc for integration tests.'

For test organization: 'Use the Given-When-Then pattern in test method names: givenValidUser_whenSave_thenReturnsCreated. One assertion concept per test. Use test fixtures with @BeforeEach, not shared mutable state.'

  • JUnit 5 only — @ExtendWith, @Nested, @DisplayName (not JUnit 4 patterns)
  • AssertJ for assertions — assertThat().isEqualTo() over assertEquals()
  • Mockito for mocking — @Mock + @InjectMocks, verify interactions
  • @SpringBootTest + @AutoConfigureMockMvc for integration tests
  • Given-When-Then naming: givenX_whenY_thenZ
  • One assertion concept per test — not 10 asserts in one method
ℹ️ JUnit 5 Only

AI assistants still generate JUnit 4 patterns (@RunWith, @Rule) from older training data. Specify 'JUnit 5 exclusively' to get @ExtendWith, @Nested, and @DisplayName.

Complete Java Rules Template

Consolidated template for Java 17+ teams using Spring Boot. Adapt the Spring-specific rules if you use a different framework.

  • Java 21 features: records for DTOs, sealed interfaces, pattern matching switch, text blocks, var
  • Stream API for collections — no manual for-each accumulation, Optional over null
  • Constructor injection only — @RequiredArgsConstructor, never @Autowired on fields
  • Unchecked exceptions for business logic — checked only at system boundaries
  • JUnit 5 + AssertJ + Mockito — @Nested groups, Given-When-Then naming
  • Spring Boot 3: jakarta.* imports, ProblemDetail errors, SecurityFilterChain
  • Lombok: @Data for mutables, @Value for immutables, @Builder for complex construction
  • Flyway or Liquibase for migrations — never manual DDL