jbct
npx skills add https://github.com/siy/coding-technology --skill jbct
Agent 安装分布
Skill 文档
Java Backend Coding Technology (JBCT)
A methodology for writing predictable, testable Java backend code optimized for human-AI collaboration.
When to Use This Skill
Activate this skill when:
- Learning JBCT principles and patterns
- Quick reference for API usage and examples
- Understanding patterns and when to use them
- Working with
Result<T>,Option<T>,Promise<T>types - Questions about monadic composition, error handling, or validation patterns
For implementation work: Use jbct-coder subagent (Task tool with subagent_type: "jbct-coder")
For code review: Use jbct-reviewer subagent (Task tool with subagent_type: "jbct-reviewer")
For automated checking: Use jbct CLI tool (format, lint, check commands)
JBCT CLI Tool
JBCT CLI provides automated formatting and compliance checking with 36 lint rules.
Check if installed:
jbct --version
Usage:
jbct format src/main/java # Format to JBCT style
jbct lint src/main/java # Check JBCT compliance (36 rules)
jbct check src/main/java # Combined format + lint
If not installed, suggest:
ð¡ JBCT CLI automates formatting and 36 lint rules for JBCT compliance.
Install: curl -fsSL https://raw.githubusercontent.com/siy/jbct-cli/main/install.sh | sh
Requires: Java 25+
More info: https://github.com/siy/jbct-cli
Core Philosophy
JBCT reduces the space of valid choices to one good way to do most things through:
- Four Return Kinds: Every function returns exactly one of
T,Option<T>,Result<T>,Promise<T> - Parse, Don’t Validate: Make invalid states unrepresentable
- No Business Exceptions: Business failures are typed
Causevalues - Thread Safety by Design: Immutability at boundaries, thread confinement for sequential logic
- Six Structural Patterns: All code fits one pattern (Leaf, Sequencer, Fork-Join, Condition, Iteration, Aspects)
FORBIDDEN PATTERNS (Zero Tolerance)
These patterns are never acceptable in JBCT code. Hunt for them aggressively.
ð´ CRITICAL VIOLATIONS
| Violation | Detection | Why Forbidden |
|---|---|---|
*Impl classes |
grep -r "class.*Impl" |
Use lambdas for behavior, records for data |
| Null checks in business logic | if (x == null) or != null |
Use Option<T> instead |
| Throwing exceptions | throw new in business code |
Use Result<T> or Promise<T> |
| Catching exceptions | catch in business code |
Lift at adapter boundaries only |
Void type |
Result<Void>, Promise<Void> |
Use Unit type |
Result.failure(cause) |
Direct call | Use cause.result() fluent style |
Promise.failure(cause) |
Direct call | Use cause.promise() fluent style |
| Multi-statement lambdas | x -> { stmt1; stmt2; } |
Extract to named method |
â ï¸ WARNING PATTERNS
| Pattern | Issue | Fix |
|---|---|---|
fold() for simple cases |
Obscures intent | Use .toResult(), .async(), .or() |
| Complex lambda body | Logic in map/flatMap | Extract to method reference |
| Long sequencer chains | >5 flatMap calls | Group into sub-operations |
| Nested records for behavior | record X() implements Y {} |
Use lambda |
Examples
// â FORBIDDEN: Impl class
public class UserServiceImpl implements UserService { ... }
// â
CORRECT: Lambda factory
static UserService userService(UserRepository repo) {
return userId -> repo.findById(userId);
}
// â FORBIDDEN: Null check
if (user != null) { process(user); }
// â
CORRECT: Option
findUser(id).onSuccess(this::process);
// â FORBIDDEN: Result.failure()
return Result.failure(USER_NOT_FOUND);
// â
CORRECT: Fluent style
return USER_NOT_FOUND.result();
// â FORBIDDEN: Multi-statement lambda
.map(user -> {
var enriched = enrich(user);
return format(enriched);
})
// â
CORRECT: Extract to method
.map(this::enrichAndFormat)
Quick Reference
The Four Return Kinds
// T - Pure computation, cannot fail, always present
public String initials() { return ...; }
// Option<T> - May be absent, cannot fail
public Option<Theme> findTheme(UserId id) { return ...; }
// Result<T> - Can fail (validation/business errors)
public static Result<Email> email(String raw) { return ...; }
// Promise<T> - Asynchronous, can fail
public Promise<User> loadUser(UserId id) { return ...; }
Critical Rules:
- â Never
Promise<Result<T>>– Promise already handles failures - â Never
Voidtype – always useUnit(Result<Unit>,Promise<Unit>) - â
Use
Result.unitResult()for successfulResult<Unit>
Parse, Don’t Validate Pattern
// â
CORRECT: Validation = Construction
public record Email(String value) {
private static final Fn1<Cause, String> INVALID_EMAIL =
Causes.forOneValue("Invalid email: %s");
public static Result<Email> email(String raw) {
return Verify.ensure(raw, Verify.Is::notNull)
.map(String::trim)
.filter(INVALID_EMAIL, PATTERN.asMatchPredicate())
.map(Email::new);
}
}
// â WRONG: Separate validation
public record Email(String value) {
public Result<Email> validate() { ... } // Don't do this
}
Key Points:
- Factory method named after type (lowercase):
Email.email(...) - Constructor private or package-private
- If instance exists, it’s valid
Pragmatica Lite Validation Utilities
Verify.Is Predicates – Use instead of custom lambdas:
Verify.Is::notNull // null check
Verify.Is::notBlank // non-empty, non-whitespace
Verify.Is::lenBetween // length in range
Verify.Is::matches // regex (String or Pattern)
Verify.Is::positive // > 0
Verify.Is::between // >= min && <= max
Verify.Is::greaterThan // > boundary
Parse Subpackage – Exception-safe JDK wrappers:
import org.pragmatica.lang.parse.Number;
import org.pragmatica.lang.parse.DateTime;
import org.pragmatica.lang.parse.Network;
Number.parseInt(raw) // Result<Integer>
DateTime.parseLocalDate(raw) // Result<LocalDate>
Network.parseUUID(raw) // Result<UUID>
Example:
public record Age(int value) {
private static final Cause AGE_OUT_OF_RANGE = Causes.cause("Age must be 0-150");
public static Result<Age> age(String raw) {
return Number.parseInt(raw)
.filter(AGE_OUT_OF_RANGE, v -> Verify.Is.between(v, 0, 150))
.map(Age::new);
}
}
Use Case Structure
public interface RegisterUser extends UseCase.WithPromise<Response, Request> {
record Request(String email, String password) {}
record Response(UserId userId, ConfirmationToken token) {}
// Nested API: steps as single-method interfaces
interface CheckEmail { Promise<ValidRequest> apply(ValidRequest valid); }
interface SaveUser { Promise<User> apply(ValidRequest valid); }
// Validated input with Valid prefix (not Validated)
record ValidRequest(Email email, Password password) {
static Result<ValidRequest> validRequest(Request raw) {
return Result.all(Email.email(raw.email()),
Password.password(raw.password()))
.map(ValidRequest::new);
}
}
// â
CORRECT: Factory returns lambda directly
static RegisterUser registerUser(CheckEmail checkEmail, SaveUser saveUser) {
return request -> ValidRequest.validRequest(request)
.async()
.flatMap(checkEmail::apply)
.flatMap(saveUser::apply);
}
}
â ANTI-PATTERN: Nested Record Implementation
NEVER create factories with nested record implementations:
// â WRONG - Verbose, no benefit
static RegisterUser registerUser(CheckEmail check, SaveUser save) {
record registerUser(CheckEmail check, SaveUser save) implements RegisterUser {
@Override
public Promise<Response> execute(Request request) { ... }
}
return new registerUser(check, save);
}
Rule: Records are for data (value objects), lambdas are for behavior (use cases, steps).
Thread Safety Essentials
Core Rules:
- Immutable at boundaries: All shared data (parameters, return values) must be immutable
- Thread confinement: Mutable state allowed within single-threaded execution (sequential patterns)
- Fork-Join requires immutability: Parallel operations must not share mutable state
Pattern-Specific Safety:
- Leaf, Sequencer, Condition, Iteration: Thread-safe through sequential execution. Mutable local state OK.
- Fork-Join: Requires strict immutability. All parallel operations receive immutable inputs.
- Promise resolution: Thread-safe (exactly-once semantics, synchronization point for flatMap/map chains)
Example – Thread-Safe Fork-Join:
// â
CORRECT: Immutable cart passed to both operations
Promise.all(applyBogo(cart), // cart is immutable
applyPercentOff(cart)) // cart is immutable
.map(this::mergeDiscounts);
// â WRONG: Shared mutable context creates data race
private final DiscountContext context = new DiscountContext();
Promise.all(applyBogo(cart, context), // mutates context
applyPercentOff(cart, context)) // DATA RACE
.map(this::merge);
See CODING_GUIDE.md for comprehensive thread safety coverage, including detailed examples and common mistakes.
Lambda Composition Guidelines
Rule: Lambdas passed to monadic operations (map, flatMap, recover, filter) must be minimal.
Allowed:
- Method references:
Email::new,this::processUser,User::id - Parameter forwarding:
user -> validate(requiredRole, user) - Constructor references for error mapping:
RepositoryError.DatabaseFailure::new
Forbidden:
- Conditionals (
if, ternary,switch) - Try-catch blocks
- Multi-statement blocks
- Object construction beyond simple factory calls
Pattern matching: Use switch expressions in named methods:
// Extract type matching to named method
.recover(this::recoverKnownErrors)
private Promise<T> recoverKnownErrors(Cause cause) {
return switch (cause) {
case NotFound ignored, Timeout ignored -> DEFAULT.promise();
default -> cause.promise();
};
}
Multi-case matching: Comma-separated for same recovery:
private Promise<Theme> recoverWithDefault(Cause cause) {
return switch (cause) {
case NotFound ignored, Timeout ignored, ServiceUnavailable ignored ->
Promise.success(Theme.DEFAULT);
default -> cause.promise();
};
}
Error constants: Define once, reuse everywhere:
Pattern Decomposition & Data Flow
Mandatory: Maximum Decomposition
Rule: One pattern per method. Never combine patterns in a single method body.
// â WRONG: Mixed patterns (Sequencer + Fork-Join + Condition)
public Promise<Response> execute(Request request) {
return validate(request)
.async()
.flatMap(valid -> {
if (valid.isPremium()) {
return Promise.all(fetchA(valid), fetchB(valid))
.map(this::merge);
}
return fetchBasic(valid);
});
}
// â
CORRECT: Decomposed into single-pattern methods
public Promise<Response> execute(Request request) {
return validate(request)
.async()
.flatMap(this::routeByType); // Sequencer
}
private Promise<Response> routeByType(ValidRequest valid) {
return valid.isPremium() // Condition
? processPremium(valid)
: processBasic(valid);
}
private Promise<Response> processPremium(ValidRequest valid) {
return Promise.all(fetchA(valid), fetchB(valid)) // Fork-Join
.map(this::merge);
}
Data Flow: Track Dependencies Explicitly
Every method must have clear data flow:
- Input: What data does it need?
- Output: What data does it produce?
- Dependencies: What external services/steps does it call?
// Input: ValidRequest (email, password)
// Output: User (id, email, hashedPassword)
// Dependencies: hashPassword, userRepository
private Promise<User> createUser(ValidRequest valid) {
return hashPassword.apply(valid.password())
.flatMap(hashed -> userRepository.save(
new User(UserId.generate(), valid.email(), hashed)));
}
Growing Context Pattern
When multi-step operations need data from earlier steps, use explicit intermediate records instead of nested closures:
// â WRONG: Nested closures lose clarity
return loadUser(userId)
.flatMap(user -> loadOrders(user.id())
.flatMap(orders -> loadPreferences(user.id())
.map(prefs -> new Dashboard(user, orders, prefs))));
// â
CORRECT: Growing context with intermediate records
record UserWithOrders(User user, List<Order> orders) {}
record DashboardContext(User user, List<Order> orders, Preferences prefs) {}
return loadUser(userId)
.flatMap(user -> loadOrders(user.id())
.map(orders -> new UserWithOrders(user, orders)))
.flatMap(ctx -> loadPreferences(ctx.user().id())
.map(prefs -> new DashboardContext(ctx.user(), ctx.orders(), prefs)))
.map(this::buildDashboard);
Benefits:
- Each stage has clear input/output types
- No deeply nested closures
- Easy to add/remove stages
- Debuggable intermediate states
private static final Cause NOT_FOUND = new UserNotFound("User not found");
private static final Cause TIMEOUT = new ServiceUnavailable("Request timed out");
private Promise<User> recoverNetworkError(Cause cause) {
return switch (cause) {
case NetworkError.Timeout ignored -> TIMEOUT.promise();
default -> cause.promise();
};
}
Structural Patterns
1. Leaf Pattern
Atomic unit – single responsibility, no composition:
public Promise<User> findUser(UserId id) {
return Promise.lift(
RepositoryError.DatabaseFailure::new,
() -> jdbcTemplate.queryForObject(...)
);
}
2. Sequencer Pattern
Linear dependent steps (most common use case pattern):
return ValidRequest.validRequest(request)
.async()
.flatMap(checkEmail::apply)
.flatMap(hashPassword::apply)
.flatMap(saveUser::apply)
.flatMap(sendEmail::apply);
3. Fork-Join Pattern
Parallel independent operations (requires immutable inputs):
return Promise.all(fetchProfile.apply(userId),
fetchPreferences.apply(userId),
fetchOrders.apply(userId))
.map((profile, prefs, orders) ->
new Dashboard(profile, prefs, orders));
Thread Safety: All parallel operations must receive immutable inputs. No shared mutable state.
4. Condition Pattern
Branching as values (no mutation):
return userType.equals("premium")
? processPremium.apply(request)
: processBasic.apply(request);
5. Iteration Pattern
Functional collection processing:
var results = items.stream()
.map(Item::validate)
.toList();
return Result.allOf(results)
.map(validItems -> process(validItems));
6. Aspects Pattern
Cross-cutting concerns without mixing:
return withRetry(
retryPolicy,
withMetrics(metricsPolicy, coreOperation)
);
Type Conversions
// Option â Result/Promise
option.toResult(cause) // or .await(cause)
option.async(cause)
// Result â Promise
result.async()
// Promise â Result (blocking)
promise.await()
promise.await(timeout)
// Cause â Result/Promise (prefer over failure constructors)
cause.result()
cause.promise()
Aggregation Operations
// Result.all - Accumulates all failures
Result.all(result1, result2, result3)
.map((v1, v2, v3) -> combine(v1, v2, v3));
// Promise.all - Fail-fast on first failure
Promise.all(promise1, promise2, promise3)
.map((v1, v2, v3) -> combine(v1, v2, v3));
// Option.all - Fail-fast on first empty
Option.all(opt1, opt2, opt3)
.map((v1, v2, v3) -> combine(v1, v2, v3));
Exception Handling
// Lift exceptions in adapters
Promise.lift(
RepositoryError.DatabaseFailure::new,
() -> jdbcTemplate.queryForObject(...)
);
// With custom exception mapper (constructor reference preferred)
Result.lift(
CustomError.ProcessingFailed::new,
() -> riskyOperation()
);
Naming Conventions
- Factory methods:
TypeName.typeName(...)(lowercase-first) - Validated inputs:
Validprefix (notValidated):ValidRequest,ValidUser - Error types: Past tense verbs:
EmailNotFound,AccountLocked,PaymentFailed - Test names:
methodName_outcome_condition - Acronyms: Treat as words (camelCase):
httpClient,apiKeynotHTTPClient,APIKey
Zone-Based Naming (Abstraction Levels)
Source: Adapted from Derrick Brandt’s systematic approach.
Use zone-appropriate verbs to maintain consistent abstraction levels:
Zone 2 (Step Interfaces – Orchestration):
- Verbs:
validate,process,handle,transform,apply,check,load,save,manage,configure,initialize - Examples:
ValidateInput,ProcessPayment,HandleRefund,LoadUserData
Zone 3 (Leaves – Implementation):
- Verbs:
get,set,fetch,parse,calculate,convert,hash,format,encode,decode,extract,split,join,log,send,receive,read,write,add,remove - Examples:
hashPassword(),parseJson(),fetchFromDatabase(),calculateTax()
Anti-pattern: Mixing zones (e.g., step interface named FetchUserData uses Zone 3 verb fetch instead of Zone 2 verb load)
Stepdown rule test: Read code aloud with “to” before functions – should flow naturally:
// "To execute, we validate the request, then process payment, then send confirmation"
return ValidRequest.validRequest(request)
.async()
.flatMap(this::processPayment)
.flatMap(this::sendConfirmation);
For complete zone verb vocabulary, see CODING_GUIDE.md: Zone-Based Naming Vocabulary.
Project Structure (Vertical Slicing)
com.example.app/
âââ usecase/
â âââ registeruser/ # Self-contained vertical slice
â â âââ RegisterUser.java # Use case interface + factory
â â âââ [internal types] # ValidRequest, etc.
â âââ loginuser/
â âââ LoginUser.java
âââ domain/
â âââ shared/ # Reusable value objects ONLY
â âââ Email.java
â âââ Password.java
â âââ UserId.java
âââ adapter/
âââ rest/ # Inbound (HTTP)
âââ persistence/ # Outbound (DB)
âââ messaging/ # Outbound (queues)
Placement Rules:
- Value objects used by single use case â inside use case package
- Value objects used by 2+ use cases â
domain/shared/ - Steps (interfaces) â always inside use case
- Errors â sealed interface inside use case
Error Structure (General enum pattern):
public sealed interface RegistrationError extends Cause {
// Group fixed-message errors into single enum
enum General implements RegistrationError {
EMAIL_ALREADY_REGISTERED("Email already registered"),
WEAK_PASSWORD_FOR_PREMIUM("Premium codes require 10+ char passwords");
private final String message;
General(String message) { this.message = message; }
@Override public String message() { return message; }
}
// Records for errors with data (e.g., Throwable)
record PasswordHashingFailed(Throwable cause) implements RegistrationError {
@Override public String message() { return "Password hashing failed"; }
}
}
// Usage
RegistrationError.General.EMAIL_ALREADY_REGISTERED.promise()
Testing Patterns
// Test failures - use .onSuccess(Assertions::fail)
@Test
void validation_fails_forInvalidInput() {
ValidRequest.validRequest(new Request("invalid", "bad"))
.onSuccess(Assertions::fail);
}
// Test successes - chain onFailure then onSuccess
@Test
void validation_succeeds_forValidInput() {
ValidRequest.validRequest(new Request("valid@example.com", "Valid1234"))
.onFailure(Assertions::fail)
.onSuccess(valid -> {
assertEquals("valid@example.com", valid.email().value());
});
}
// Async tests - use .await() first
@Test
void execute_succeeds_forValidInput() {
useCase.execute(request)
.await()
.onFailure(Assertions::fail)
.onSuccess(response -> {
assertEquals("expected", response.value());
});
}
Pragmatica Lite Core Library
JBCT uses Pragmatica Lite Core 0.11.2 for functional types.
Maven (preferred):
<dependency>
<groupId>org.pragmatica-lite</groupId>
<artifactId>core</artifactId>
<version>0.11.2</version>
</dependency>
Gradle (only if explicitly requested):
implementation 'org.pragmatica-lite:core:0.11.2'
Library documentation: https://central.sonatype.com/artifact/org.pragmatica-lite/core
Static Imports (Encouraged)
Static imports reduce code verbosity:
// Recommended static imports
import static org.pragmatica.lang.Result.all;
import static org.pragmatica.lang.Result.success;
import static com.example.domain.Email.email;
import static com.example.domain.Password.password;
// Concise code
return all(email(raw), password(raw)).flatMap(ValidRequest::validRequest);
Fluent Failure Creation
Use cause.result() and cause.promise() instead of Result.failure(cause):
// â
DO: Fluent style
return INVALID_EMAIL.result();
return USER_NOT_FOUND.promise();
// â DON'T: Static factory style
return Result.failure(INVALID_EMAIL);
return Promise.failure(USER_NOT_FOUND);
When to Use Specialized Subagents
This skill provides quick reference and learning resources. For complex implementation and review tasks, use specialized subagents:
Use jbct-coder Subagent When:
- Generating complete use case implementations with all components
- Creating value objects with validation and error types
- Implementing adapters with proper exception handling
- Writing tests following JBCT patterns
- Need deterministic code generation following all JBCT rules
How to invoke: Use Task tool with subagent_type: "jbct-coder"
What it provides:
- Complete use case structure (interface, factory, steps)
- Validated request types with
Result.all() - Value objects with parse-don’t-validate pattern
- Error types as sealed interfaces
- Comprehensive test suites (validation, happy path, failures)
- Step-by-step code generation with explanations
Use jbct-reviewer Subagent When:
- Reviewing existing code for JBCT compliance
- Validating patterns (Leaf, Sequencer, Fork-Join, etc.)
- Checking naming conventions and structure
- Identifying violations with specific fixes
- Need comprehensive checklist-based analysis
How to invoke: Use Task tool with subagent_type: "jbct-reviewer"
What it provides:
- Four Return Kinds compliance check
- Parse-don’t-validate pattern validation
- Null policy enforcement
- Pattern recognition and verification
- Naming convention compliance
- Detailed violation reports with corrections
Use This Skill When:
- Learning JBCT principles and patterns
- Looking up API usage examples
- Quick reference for type conversions
- Understanding when to use which pattern
- Exploring patterns with examples
Implementation Workflow
- Define use case interface with Request, Response, and execute signature
- Create validated request with static factory using
Result.all() - Define steps as single-method interfaces (nested in use case)
- Create value objects with validation in static factories
- Implement factory method returning lambda with composition chain
- Write tests starting with validation, then happy path, then failure cases
ð¡ Tip: For automatic generation following this workflow, use the jbct-coder subagent.
Common Mistakes to Avoid
â Using business exceptions instead of Result/Promise
â Nested records in use case factories (use lambdas)
â Void type (use Unit)
â Promise<Result<T>> (redundant nesting)
â Separate validation methods (parse at construction)
â Public constructors on value objects
â Complex logic in lambdas (extract to methods)
â Validated prefix (use Valid)
ð¡ Tip: For automated code review checking these mistakes, use the jbct-reviewer subagent.
Self-Validation Checkpoint
Before considering JBCT code complete, verify ALL of these:
Zero Tolerance (must pass)
- No
*Implclasses - No
nullchecks in business logic - No
throw/catchin business logic - No
Voidtype (useUnit) - No
Result.failure()orPromise.failure()(usecause.result()/cause.promise()) - No multi-statement lambdas in map/flatMap
Pattern Compliance
- Each method implements exactly ONE pattern
- Sequencer chains â¤5 steps
- Fork-Join inputs are immutable
- Growing context uses intermediate records (not nested closures)
Data Flow
- Every method has clear input â output
- No hidden state mutations
- Dependencies injected via factory parameters
Naming
- Factory methods:
TypeName.typeName(...) - Validated types:
Validprefix (notValidated) - Errors: past tense (
NotFound,Failed,Expired)
Structure
- Use case = interface + factory + steps
- Value objects = record + static factory returning
Result<T> - Errors = sealed interface with enum for fixed messages
Detailed Resources
This skill contains comprehensive guidance organized by topic:
Fundamentals
- fundamentals/four-return-kinds.md – T, Option, Result, Promise in depth
- fundamentals/parse-dont-validate.md – Value object patterns
- fundamentals/no-business-exceptions.md – Typed failures with Cause
Patterns
- patterns/leaf.md – Atomic operations
- patterns/sequencer.md – Sequential composition
- patterns/fork-join.md – Parallel operations
- patterns/condition.md – Branching logic
- patterns/iteration.md – Collection processing
- patterns/aspects.md – Cross-cutting concerns
Use Cases
- use-cases/structure.md – Anatomy and conventions
- use-cases/complete-example.md – Full RegisterUser walkthrough
Testing & Organization
- testing/patterns.md – Test strategies and assertions
- project-structure/organization.md – Vertical slicing
Specialized Subagents
- ../../jbct-coder.md – Autonomous code generation agent (invoke with Task tool)
- Generates complete use cases with validation, tests, and adapters
- Follows deterministic algorithms for consistent output
- Includes evolutionary testing strategy
- ../../jbct-reviewer.md – Autonomous code review agent (invoke with Task tool)
- Comprehensive JBCT compliance checking
- Pattern validation and naming convention enforcement
- Detailed violation reports with fixes
Documentation
- ../../CODING_GUIDE.md – Complete technical reference (100+ pages)
- ../../series/ – 6-part progressive learning series
- ../../TECHNOLOGY.md – High-level pattern catalog
- ../../CHANGELOG.md – Version history and changes
Repository: https://github.com/siy/coding-technology