smithery/siy

JBCT

Java Backend Coding Technology skill for designing, implementing, and reviewing functional Java backend code.

Installation

$ npx skills add smithery/siy --skill jbct

Summary

  • Java Backend Coding Technology skill for designing, implementing, and reviewing functional Java backend code.
  • Use when working with Result, Option, Promise types, value objects, use cases, or when asked about JBCT patterns, monadic composition, parse-don't-validate, structural patterns (Leaf, Sequencer, Fork-Join), or functional Java backend architecture.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/siy.

npx skills add smithery/siy

Browse all from smithery/siy

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 41,248 B
  • docs SUMMARY.md 369 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

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 subagenttype: "jbct-coder") For code review: Use jbct-reviewer subagent (Task tool with subagenttype: "jbct-reviewer") For automated checking: Use jbct CLI tool (format, lint, check commands)

Source-Anchored Chapters (read this first)

Parts of this skill live in the class-level header comments of Pragmatica Core source files — the single source of truth that cannot drift from the API. When a task touches one of these areas, READ THE SOURCE HEADER before writing code:

Chapter Source of truth
Core monads: combinator maps, construction, aggregation, conversions org/pragmatica/lang/Result.java, Option.java, Promise.java headers
Validation: ensure families, full Is predicate catalog, ensureOption, combine org/pragmatica/lang/Verify.java header
Intent annotations: when void/blocking/null is legitimate, decision procedures org/pragmatica/lang/Contract.java, TerminalOperation.java, NullReturn.java headers
Built-in value objects: catalog, factories, validation rules org/pragmatica/lang/vo/package-info.java (+ per-class headers)
Exception-safe parsing: wrapper catalog org/pragmatica/lang/parse/package-info.java
Utilities: failure vocabulary, resilience (Retry/CircuitBreaker/RateLimiter/Idempotency), memoization, scheduling org/pragmatica/lang/utils/package-info.java

Resolution order (first that succeeds):

  1. jbct doc <ClassOrPackage> — if the jbct CLI is available (jbct --version), this is the

preferred shortcut: jbct doc Verify, jbct doc org.pragmatica.lang.vo, jbct doc Result --api. It applies the local-then-download resolution below automatically.

  1. Local checkout (preferred if present) — if a local pragmatica checkout exists, read

<checkout>/core/src/main/java/<path> directly. In sibling layouts (e.g. working inside coding-technology/) this is ../pragmatica/core/src/main/java/<path>; inside the pragmatica repo itself it is core/src/main/java/<path>.

  1. Download if no local checkout — fetch the Pragmatica Core sources for the project's declared

core version, then read the header: - Maven Central sources jar: mvn dependency:get -Dartifact=org.pragmatica-lite:core:<version>:jar:sources then unzip -p ~/.m2/repository/org/pragmatica-lite/core/<version>/core-<version>-sources.jar org/pragmatica/lang/Verify.java | head -120 - or GitHub release / raw: https://raw.githubusercontent.com/pragmaticalabs/pragmatica/main/core/src/main/java/<path>

  1. Degrade gracefully — proceed with this file's summaries plus imitation of neighboring code,

and say in your report that source chapters were unreachable.

Read only the class header (first ~120 lines), not whole implementation files.

JBCT CLI Tool

JBCT CLI provides automated formatting and compliance checking. Every finding names the rule that produced it: JBCT-RET- (return types), JBCT-VO- (value objects), JBCT-EX- (exceptions), JBCT-NAM- (naming), JBCT-LAM- (lambdas), JBCT-STY- (style), JBCT-LOG- (logging), JBCT-MIX- (I/O in domain).

Check if installed:

jbct --version

Usage:

jbct format src/main/java     # Format to JBCT style
jbct lint src/main/java       # Check JBCT compliance
jbct check src/main/java      # Combined format + lint
jbct init --slice my-service   # Scaffold new slice project
jbct add-slice <name>          # Add slice to existing project
jbct add-event <name>          # Add event scaffolding
jbct add-persistence           # Add PostgreSQL persistence support

If not installed, suggest:

💡 JBCT CLI automates formatting and lint checks 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 Cause values
  • 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 parameter Result<Void>, Promise<Void> Use Unit. void return OK with @Contract (external API) or fire-and-forget
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
Promise.await() in business logic .await() in domain/usecase Blocks thread. Stay in chain. OK in tests; use @TerminalOperation for CLI/fire-and-forget
@SuppressWarnings misuse Used instead of @Contract/@TerminalOperation Use @Contract for void return (external API), @TerminalOperation for legitimate await()
Abandoned Result/Promise Statement discarding return value Every Result/Promise must be returned or handled

Exception: Methods annotated with @Contract are exempt from all JBCT lint rules. Use @Contract for Java API boundary methods (annotation processors, Maven Mojos).

⚠️ 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 Void type parameter - always use Unit (Result<Unit>, Promise<Unit>). void return is OK for fire-and-forget
  • ✅ Use Result.unitResult() for successful Result<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::present)
                     .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 Core Validation Utilities

Verify first, lambdas last. Before writing any predicate lambda in a validation chain, check the Verify.Is catalog — full catalog and ensure overload families: see the Verify.java source header (Source-Anchored Chapters above). Hand-rolling a check that duplicates a catalog predicate is a JBCT violation. Hottest entries:

Verify.Is::present          // not null and not blank — the "required string" check
Verify.ensure(v, Is::lenBetween, 3, 50)   // parameterized predicates need no capturing lambda
Verify.ensureOption(opt, predicate)       // Result<Option<T>> contract for optional values

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 Thread Safety 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

JBCT's six patterns come from the process side — the data dependency graph's operators — and code written in them is an executable business process specification.

Pattern Role
Leaf Atomic operation, no composition
Sequencer Dependent steps in order
Fork-Join Independent concurrent operations
Condition Routing, no transformation
Iteration Collection processing
Aspects Cross-cutting concerns wrapping logic

1. Leaf Pattern

Atomic unit - one operation, 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 (1-15 params)
Result.all(result1, result2, result3)
       .map((v1, v2, v3) -> combine(v1, v2, v3));

// Promise.all - Parallel, fail-fast on first failure (1-15 params)
Promise.all(promise1, promise2, promise3)
        .map((v1, v2, v3) -> combine(v1, v2, v3));

// Promise.allOrCancel - Like all(), but cancels remaining on first failure (1-15 params)
Promise.allOrCancel(promise1, promise2, promise3)
        .map((v1, v2, v3) -> combine(v1, v2, v3));

// Option.all - Fail-fast on first empty (1-15 params)
Option.all(opt1, opt2, opt3)
       .map((v1, v2, v3) -> combine(v1, v2, v3));

// Collection variants
Promise.allOf(collection)             // Promise<List<Result<T>>> - collects all
Promise.allOfOrCancel(collection)     // Like allOf(), cancels remaining on first failure
Promise.any(promise1, promise2)       // First success wins
Result.allOf(collection)              // Result<List<T>> - accumulates failures

Instance variants (for-comprehension style, same semantics):

promise.all(fn1, fn2, fn3).map(combine);            // Parallel, fail-fast
promise.allOrCancel(fn1, fn2, fn3).map(combine);     // Parallel, fail-fast + cancel

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()
);

Recovery: What To Do When A Step Fails

Absorbing a failure requires saying why. A .recover(...) or swallowing .onFailure(...) in a composition drops a failure the caller will never see, so the site must name which recovery strategy it is using and what guarantee that earns. Absorption without a stated justification is the defect — not absorption itself.

// FER: the buy is already committed by the time the fact is published, so a publish failure is
// swallowed rather than reported to a buyer who has been charged. Guarantee earned: the response
// is truthful about the purchase, not about the fact. Mechanism: a single attempt -- no retry and
// no outbox, so a lost fact leaves downstream projections stale until the next fact or an
// operator re-drive.
private Promise<Response> publishSold(Confirmation confirmation) {
    return seatSold.publish(confirmation.fact())
                   .recover(_ -> Unit.unit())
                   .map(_ -> confirmation.response());
}

Name the triple, the guarantee, and the mechanism. The rule and its vocabulary are book-owned:

<!-- book:recovery-triple --> The patterns above answer "this operation failed — what value do I return instead?" A harder question sits one level up: a step fails after earlier steps already changed state — a seat is held, an authorization placed — and that state is now invalid. There are exactly three responses, and naming all three keeps the choice deliberate instead of defaulting to the first.

  • BER — Backward Error Recovery. Series long name: compensate-by-inverse. Undo by an inverse action: release the held seat, void the authorization, reverse the ledger entry. The classic rollback or saga shape. Reach for it when the change is reversible and correctness demands the system look as if nothing happened — money, inventory.
  • FER — Forward Error Recovery. Series long name: degrade-and-continue. Do not undo; continue with degraded state. Queue a confirmation email for retry while the booking stands; let a cached value decay fresh -> stale -> expired rather than fail outright. The .or(...) and graceful-degradation patterns above are FER. Reach for it when forward progress is worth more than perfect consistency — telemetry, notifications, optional enrichment.
  • Design-out. Change the model so the invalidation cannot arise: a reservation type where two bookings of one seat is structurally impossible; an idempotent write safe to repeat; an append-only log corrected by appending. The failure mode is removed rather than handled — the strongest option, when the model permits it.

Which applies is a judgment — reversibility, the value of partial progress, the domain's shape, coordination cost — and mixed strategies are normal: one booking flow can use BER for the payment, FER for the confirmation email, and design-out for the seat model, all at once. Name the triple for each step that changes state, and recovery becomes a design decision rather than an afterthought. <!-- /book:recovery-triple -->

Naming Conventions

  • Factory methods: TypeName.typeName(...) (lowercase-first)
  • Validated inputs: Valid prefix (not Validated): ValidRequest, ValidUser
  • Error types: Past tense verbs: EmailNotFound, AccountLocked, PaymentFailed
  • State-machine state types: State suffix for the sealed sum of lifecycle states — HoldState, BookingState, SeatState — with variants kept bare (Free, Held, Confirmed, Cancelled, never HeldState). Reserve the suffix for the lifecycle sum a guarded transition advances, not every mutable holder; it joins the suffix-by-role family (Request, *Response, Cause).
  • Test names: method[scenario]expectation — at least two underscore-separated segments (validaterejectsEmpty, or the fuller registersucceeds_forNewEmail)
  • Acronyms: Treat as words (camelCase): httpClient, apiKey not HTTPClient, APIKey

Zone-Based Naming (Abstraction Levels)

Source: Adapted from Derrick Brandt's systematic approach.

Use zone-appropriate verbs to maintain consistent abstraction levels. The rule and the tables are book-owned and reproduced below.

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);

<!-- book:zone-verbs --> The zone is the constraint; the verb lists below are illustrative, not exhaustive. A name is correct when its verb matches the altitude it is declared at, not when it appears in a table. The tables name representative verbs for each zone so the distinction has something concrete to stand on — they were never meant as a closed vocabulary, and a census of real JBCT codebases found the majority of production method names heading verbs no list contained.

The distinction that does the work is this: Zone 2 names the intent, Zone 3 names the mechanism. A step interface says what the workflow needs to happen; a leaf says how it is done. LoadUser is a step because loading is the intent; fetchFromDatabase and findByEmail are leaves because fetching over a network and searching an index are mechanisms. This is why the anti-pattern below is a real defect rather than a style preference.

Zone 2 verbs (step interfaces — orchestration), representative:

Verb When to Use Example
validate Checking rules/constraints ValidateInput
process Transforming or interpreting data ProcessPayment
handle Coordinating reactions to events HandleRefund
load Retrieving data for use LoadUserProfile
save Persisting changes SaveOrder
check Verifying conditions CheckInventory

Zone 3 verbs (leaves — implementation), representative:

Verb Typical Use Example
get Retrieve a value getTimestamp()
fetch Pull from external source fetchWeatherData()
find Search for a value that may be absent findByEmail()
parse Break down structured input parseJson()
calculate Perform computation calculateTax()
create Construct a value from parts createInvoice()
build Assemble a value incrementally buildQuery()
insert Write a new row or entry insertPayment()
hash Cryptographic transformation hashPassword()
format Build structured output formatDate()
send Transmit over network sendEmail()

The primary test — do not mix zones. A step interface that uses a Zone 3 verb has named a mechanism where it owed an intent, and the mismatch is checkable without consulting any list: a step interface named FetchUserData should be LoadUserData, because fetch commits the orchestration layer to how the data arrives. This test catches real defects. Absence from a table does not — a verb missing from both lists is unlisted, not wrong. <!-- /book:zone-verbs -->

<!-- book:predicate-naming --> Methods returning boolean take an is, has, or can prefix, and the set is closed — these three, no others:

boolean isExpired()        // state of the receiver
boolean hasBalance()       // possession of a part or property
boolean canWithdraw()      // permission or capability

The prefix says which question is being asked, so a caller reading if (account.canWithdraw()) knows a permission is being checked rather than a state inspected. Predicates never take a verb from the zone tables: checkExpiry() returning boolean should be isExpired(). <!-- /book:predicate-naming -->

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("[email protected]", "Valid1234"))
                .onFailure(Assertions::fail)
                .onSuccess(valid -> {
                    assertEquals("[email protected]", 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 Core Library

JBCT uses Pragmatica Core 1.0.0-rc1 for functional types.

Maven (preferred):

<dependency>
   <groupId>org.pragmatica-lite</groupId>
   <artifactId>core</artifactId>
   <version>1.0.0-rc1</version>
</dependency>

Gradle (only if explicitly requested):

implementation 'org.pragmatica-lite:core:1.0.0-rc1'

Library documentation: https://central.sonatype.com/artifact/org.pragmatica-lite/core

Library Value Objects

Check org.pragmatica.lang.vo BEFORE writing any value object — hand-rolling a VO that duplicates a built-in (Email, Url, Uuid, NonBlankString, IsoDateTime) is a JBCT violation. Catalog with factories and validation rules: see the vo/package-info.java source header (Source-Anchored Chapters above). Build custom VOs only for domain-specific types (OrderId, Username, ReferralCode).

Note: Email appears throughout this skill as a teaching example for writing validation chains — in production code, use org.pragmatica.lang.vo.Email.

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 org.pragmatica.lang.vo.Email.email;      // built-in VO
import static com.example.domain.Password.password;    // domain-specific VO

// 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

  1. Define use case interface with Request, Response, and execute signature
  2. Create validated request with static factory using Result.all()
  3. Define steps as single-method interfaces (nested in use case)
  4. Create value objects with validation in static factories
  5. Implement factory method returning lambda with composition chain
  6. 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 parameter (use Unit; void return is OK for fire-and-forget) ❌ 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 *Impl classes
  • No null checks in business logic
  • No throw/catch in business logic
  • No Void type parameter (use Unit; void return OK for fire-and-forget)
  • No Result.failure() or Promise.failure() (use cause.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: Valid prefix (not Validated)
  • 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](fundamentals/four-return-kinds.md) - T, Option, Result, Promise in depth
  • [fundamentals/parse-dont-validate.md](fundamentals/parse-dont-validate.md) - Value object patterns
  • [fundamentals/no-business-exceptions.md](fundamentals/no-business-exceptions.md) - Typed failures with Cause

Patterns

  • [patterns/leaf.md](patterns/leaf.md) - Atomic operations
  • [patterns/sequencer.md](patterns/sequencer.md) - Sequential composition
  • [patterns/knowledge-gathering.md](patterns/knowledge-gathering.md) - Context-preserving stages (mapWith/flatMapWith/ensureWith, stage-accretion records)
  • [patterns/fork-join.md](patterns/fork-join.md) - Parallel operations
  • [patterns/condition.md](patterns/condition.md) - Branching logic
  • [patterns/iteration.md](patterns/iteration.md) - Collection processing
  • [patterns/aspects.md](patterns/aspects.md) - Cross-cutting concerns

Use Cases

  • [use-cases/structure.md](use-cases/structure.md) - Anatomy and conventions
  • [use-cases/complete-example.md](use-cases/complete-example.md) - Full RegisterUser walkthrough

Testing & Organization

  • [testing/patterns.md](testing/patterns.md) - Test strategies and assertions
  • [project-structure/organization.md](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

  • JBCT book - Complete technical reference (JBCT book)
  • ../../TECHNOLOGY.md - High-level pattern catalog
  • ../../CHANGELOG.md - Version history and changes

Repository: https://github.com/siy/coding-technology