Agent skill
java-guide
Java language guardrails, patterns, and best practices for AI-assisted development. Use when working with Java files (.java), pom.xml, build.gradle, or when the user mentions Java. Provides modern Java patterns (records, sealed classes, streams), testing standards, and Spring conventions specific to this project's coding standards.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/java-guide
Metadata
Additional technical details for this skill
- author
- samuel
- version
- 1.0
- category
- language
- language
- java
- extensions
- .java
SKILL.md
Java Guide
Applies to: Java 17+, Spring Boot, Maven/Gradle, Enterprise Applications
Core Principles
- Immutability by Default: Prefer records,
finalfields, and unmodifiable collections - Explicit Over Implicit: Clear type declarations, no raw types, no unchecked casts
- Fail Fast: Validate inputs at boundaries, use
Objects.requireNonNullliberally - Composition Over Inheritance: Favor delegation and interfaces over deep class hierarchies
- Standard Library First: Use
java.util,java.time,java.niobefore adding dependencies
Guardrails
Version & Dependencies
- Use Java 17+ (LTS) with preview features disabled in production
- Manage dependencies with Maven (
pom.xml) or Gradle (build.gradle.kts) - Pin dependency versions explicitly (no dynamic versions like
1.+) - Run
mvn dependency:analyzeorgradle dependenciesto detect unused/undeclared deps - Check for vulnerabilities:
mvn org.owasp:dependency-check-maven:check
Code Style
- Follow Google Java Style Guide
- Classes:
PascalCase| Methods/fields:camelCase| Constants:UPPER_SNAKE_CASE - Packages:
com.company.project.module(lowercase, no underscores) - One top-level class per file (name matches filename)
- Use
varfor local variables only when the type is obvious from the right side - No wildcard imports (
import java.util.*is forbidden)
Records & Sealed Classes
- Use
recordfor immutable data carriers instead of manual POJOs - Use
sealedclasses/interfaces for restricted type hierarchies - Add compact constructors for validation in records
- Sealed classes enable exhaustive
switchexpressions with pattern matching
public record UserId(String value) {
public UserId {
Objects.requireNonNull(value, "UserId must not be null");
if (value.isBlank()) {
throw new IllegalArgumentException("UserId must not be blank");
}
}
}
public sealed interface PaymentResult
permits PaymentResult.Success, PaymentResult.Declined, PaymentResult.Error {
record Success(String transactionId, BigDecimal amount) implements PaymentResult {}
record Declined(String reason) implements PaymentResult {}
record Error(Exception cause) implements PaymentResult {}
}
Streams & Optional
- Use streams for transformations, not for side effects
- Never call
Optional.get()-- useorElseThrow(),orElse(),map(),flatMap() - Do not use
Optionalas a method parameter or field type (only as a return type) - Avoid parallel streams unless measured to be faster (overhead is real)
- Prefer
toList()(Java 16+) overcollect(Collectors.toList())
List<String> activeEmails = users.stream()
.filter(User::isActive)
.map(User::email)
.toList();
String displayName = userRepository.findById(id)
.map(User::displayName)
.orElseThrow(() -> new UserNotFoundException(id));
Resource Management
- Always use try-with-resources for
AutoCloseabletypes - Never rely on
finalize()(deprecated and unreliable) - Close resources in reverse order of acquisition
try (var connection = dataSource.getConnection();
var statement = connection.prepareStatement(sql);
var resultSet = statement.executeQuery()) {
while (resultSet.next()) {
results.add(mapRow(resultSet));
}
}
Project Structure
myproject/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/com/company/project/
│ │ │ ├── Application.java # Entry point
│ │ │ ├── config/ # Configuration classes
│ │ │ ├── controller/ # REST controllers / API layer
│ │ │ ├── service/ # Business logic
│ │ │ ├── repository/ # Data access
│ │ │ ├── model/ # Domain entities and records
│ │ │ ├── dto/ # Data transfer objects (records)
│ │ │ └── exception/ # Custom exceptions
│ │ └── resources/
│ │ ├── application.yml
│ │ └── db/migration/ # Flyway/Liquibase migrations
│ └── test/java/com/company/project/ # Mirrors main structure
└── target/ # Build output (gitignored)
- Packages map to bounded contexts (not technical layers at the top)
- Test structure mirrors source structure
- Keep
resources/flat; usedb/migration/for schema changes
Key Patterns
Sealed Classes with Pattern Matching
public static double area(Shape shape) {
return switch (shape) {
case Shape.Circle c -> Math.PI * c.radius() * c.radius();
case Shape.Rectangle r -> r.width() * r.height();
case Shape.Triangle t -> 0.5 * t.base() * t.height();
};
}
Immutable Collections
List<String> roles = List.of("ADMIN", "USER", "GUEST");
Map<String, String> config = Map.ofEntries(
Map.entry("host", "localhost"),
Map.entry("port", "8080"));
List<Item> snapshot = List.copyOf(mutableList);
Text Blocks
String query = """
SELECT u.id, u.email, u.created_at
FROM users u
WHERE u.active = true
ORDER BY u.created_at DESC
LIMIT ?
""";
Optional Chaining
// Fallback chain: cache -> database -> remote
User user = cache.findUser(id)
.or(() -> database.findUser(id))
.or(() -> remoteService.fetchUser(id))
.orElseThrow(() -> new UserNotFoundException(id));
// Conditional execution without get()
userRepository.findById(userId)
.ifPresentOrElse(
u -> log.info("Found user: {}", u.name()),
() -> log.warn("User {} not found", userId));
Testing
Standards
- Test files mirror source under
src/test/java - Test class names:
ClassNameTest(notTestClassName) - Test methods:
@Test void shouldDescribeBehavior() - Use
@DisplayNamefor complex scenarios,@Nestedto group related tests - Coverage target: >80% for business logic, >60% overall
- Prefer AssertJ over JUnit assertions for readability
Basic Test Structure
class UserServiceTest {
private UserRepository userRepository;
private UserService userService;
@BeforeEach
void setUp() {
userRepository = mock(UserRepository.class);
userService = new UserService(userRepository);
}
@Test
void shouldReturnUserWhenFound() {
var expected = new User("1", "[email protected]", "Alice");
when(userRepository.findById("1")).thenReturn(Optional.of(expected));
User result = userService.getUser("1");
assertThat(result).isEqualTo(expected);
verify(userRepository).findById("1");
}
@Test
void shouldThrowWhenUserNotFound() {
when(userRepository.findById("999")).thenReturn(Optional.empty());
assertThatThrownBy(() -> userService.getUser("999"))
.isInstanceOf(UserNotFoundException.class)
.hasMessageContaining("999");
}
}
Parameterized Tests
@ParameterizedTest
@CsvSource({
"[email protected], true",
"[email protected], true",
"invalid-email, false",
"'', false",
})
void shouldValidateEmail(String email, boolean expected) {
assertThat(EmailValidator.isValid(email)).isEqualTo(expected);
}
@ParameterizedTest
@MethodSource("provideMoneyAdditions")
void shouldAddMoneySameCurrency(Money a, Money b, Money expected) {
assertThat(a.add(b)).isEqualTo(expected);
}
static Stream<Arguments> provideMoneyAdditions() {
var usd = Currency.getInstance("USD");
return Stream.of(
Arguments.of(new Money(BigDecimal.ONE, usd),
new Money(BigDecimal.TEN, usd),
new Money(new BigDecimal("11"), usd))
);
}
Tooling
Maven Commands
mvn clean install # Build and install locally
mvn test # Run all tests
mvn verify # Run tests + integration tests
mvn dependency:tree # Show dependency tree
mvn dependency:analyze # Find unused/undeclared deps
mvn spotbugs:check # Static analysis
mvn checkstyle:check # Style check
Gradle Commands
./gradlew build # Full build with tests
./gradlew test # Run unit tests
./gradlew check # Run all verification tasks
./gradlew dependencies # Show dependency tree
./gradlew jacocoTestReport # Generate coverage report
References
For detailed patterns and examples, see:
- references/patterns.md -- Spring patterns, Stream API recipes, Optional chaining, builder patterns
External References
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?