Skip to content

wardline 02 B java binding

Part II-B: Java Language Binding Reference

⚠ Binding version notice. This binding was last updated against the framework specification v0.2.0. The framework specification v0.3.0 added WL-009 (restoration symmetry), updated the severity matrix from 8 to 9 framework rules, added a non-normative deep-immutability note to §9, and updated conformance criterion 3 to include WL-009. This binding does not yet reflect these changes. In particular:

  • WL-009 has no Java binding mapping (the Python binding maps it to PY-WL-010).
  • The conformance criterion table (B.3/B.6) does not include WL-009 in the structural verification row.
  • The regime composition matrix has not been updated for the ninth framework rule.
  • The deep-immutability supplementary rules (SUP-010/SUP-011 in the Python binding) have no Java equivalents. Java's final keyword on reference fields presents the same shallow-immutability gap that motivates these rules — a Java binding update should consider equivalent rules targeting final fields holding mutable collections.

These updates will be applied in a future revision.

This section defines the Java language binding for the Wardline classification framework. It maps the 17 abstract annotation groups (Part I §7) to concrete Java annotations, defines the interface contract for conformant scanners, and documents the residual risks specific to Java's language and ecosystem characteristics.

Normative status. Section B.3 (interface contract) is normative. All other sections are non-normative — they provide design rationale, implementation guidance, and assessment material.

Minimum Java version. This binding targets Java 17+. Records (Java 16), sealed classes (Java 17), and pattern matching for instanceof (Java 16) are essential language features for the binding's type-system enforcement and runtime structural model.


B.1 Design history

The Java binding is the second wardline language binding, developed after the Python binding (Part II-A) with the benefit of knowing which design decisions were framework-dictated versus Python-specific compensations.

Why Java second. The case study that motivated the wardline concept is Python-based. Java is the second target because it is the dominant language in regulated enterprise and government systems. A specification that addresses only Python reaches the analyst and data-engineering community but misses the enterprise application layer where the bulk of regulated and government code lives.

What transferred. The annotation vocabulary maps all 17 groups from the parent specification. The scanner uses the same two-pass analysis model (symbol collection, then rule evaluation). The severity matrix, governance model, manifest schema, SARIF output contract, and golden corpus specification are binding-independent.

What did not transfer. The Python binding dedicates substantial effort to compensating for language weaknesses: optional type checking, dynamic dispatch blind spots, runtime-only construction discipline, absent ownership semantics. Java eliminates several of these structurally. The Java binding is shorter in some areas (runtime structural enforcement is brief because the language provides it) and longer in others (residual risks address Java-specific concerns — framework proxy blind spots, Lombok-generated code, JPA entity lifecycle — that have no Python equivalent).

Reference implementation posture. This binding is a reference implementation design, not a product specification. It demonstrates that the wardline framework is implementable on the Java platform and identifies the design decisions a production implementation must make. A production scanner may use different analysis tools (e.g., CodeQL instead of JavaParser) provided it satisfies the interface contract (B.3).


B.2 Java language evaluation

The parent specification (§12) defines language evaluation criteria for wardline bindings. This section assesses Java against those criteria, modelling how future binding authors should evaluate their target language.

Criterion Assessment Detail
Annotation expressiveness Very strong Java annotations are first-class language constructs, retained in bytecode (@Retention(RUNTIME) or @Retention(CLASS)), parameterisable, repeatable (since Java 8), and composable via meta-annotations. All 17 annotation groups are expressible without runtime overhead.
Parse tree access Very strong Multiple mature AST frameworks: JavaParser (standalone), Eclipse JDT (IDE-grade), IntelliJ PSI (IDE-grade), javac plugin API (compiler-integrated), and Tree-sitter (language-agnostic).
Type system metadata Very strong Type-use annotations (@Target(TYPE_USE), since Java 8) can annotate any type occurrence. The Checker Framework extends this into a pluggable type system with custom qualifier hierarchies, flow-sensitive refinement, and compile-time enforcement.
Structural typing Strong Sealed interfaces (Java 17) constrain the type hierarchy. Records (Java 16) provide structural data types with compiler-generated accessors. Pattern matching (Java 16+) enables exhaustive deconstruction of sealed hierarchies. Java's type system is nominative, not structural — but sealed hierarchies achieve the same discrimination as Python's Protocols with stronger guarantees.
Runtime object model Very strong Records are immutable by construction — all components are final, no setters, canonical constructor requires all components. The access-before-set problem that motivates Python's AuthoritativeField descriptor cannot occur with records.
Class hierarchy enforcement Very strong sealed classes and interfaces restrict which classes may extend or implement them — enforced at compile time and at the JVM level. final classes prevent extension entirely. The module system restricts visibility across package boundaries.
Serialisation boundary control Moderate Jackson @JsonCreator and @JsonProperty annotations provide structured deserialisation. Records' canonical constructors naturally serve as deserialisation entry points. However, ObjectInputStream remains a known attack surface, and JPA entity lifecycle blurs tier boundaries in ORM-heavy applications.
Tooling ecosystem Very strong Error Prone (compile-time, Google-maintained), SpotBugs (bytecode), PMD (source-level), SonarQube (enterprise), Checker Framework (pluggable types), ArchUnit (architectural constraints), NullAway (null safety).
Java: Language evaluation criteria for wardline binding suitability
Where Java falls short

No ownership model. Like Python, Java lacks ownership semantics. A validated record can be aliased and referenced after the validation context has changed. Records mitigate this partially — immutability means aliases cannot diverge — but the fundamental aliasing problem remains for mutable domain objects.

Framework runtime magic. Enterprise Java frameworks (Spring, Jakarta EE, Quarkus) rely on runtime mechanisms invisible to static analysis: AOP proxy generation, CDI interceptor chains, JPA entity state transitions, dependency injection container lifecycle. A method annotated @ValidatesShape may be intercepted by a Spring proxy that wraps it in a transaction, exception handler, or caching layer — none visible in the source AST. These blind spots are introduced by framework conventions, not by language dynamism, distinguishing them from Python's dynamic dispatch issues.

Lombok-generated code. Lombok generates bytecode not present in source. A scanner operating on source AST does not see Lombok-generated constructors, getters, or builders. A @Builder on a Tier 1 class generates a permissive builder pattern that accepts partial construction — exactly the anti-pattern the wardline exists to prevent.

Where Java structurally exceeds Python
  1. Compile-time enforcement is mandatory. Java's type system is not optional. Checker Framework qualifiers are enforced at compile time. There is no gap between authoring time and CI feedback.

  2. No existence-checking blind spot. If a field is declared on a class, the compiler guarantees it exists. WL-002 applies only to Map-based access patterns and nullable fields — a dramatically narrower surface than Python's hasattr() / "key" in dict patterns.

  3. Immutability by construction. Java records are immutable by language guarantee. Every component is final, there are no setters, and the canonical constructor requires all components. The Python binding's AuthoritativeField descriptor is unnecessary — records make this structurally impossible.

These structural advantages mean the Java binding's assurance ceiling is meaningfully higher than the Python binding's.

Ecosystem tool coverage
Conformance Profile Candidate Tool Implementation Path Fit
Advisory fast path (non-conformant) Error Prone Custom BugChecker — compile-time pattern matching for JV-WL-001 through JV-WL-004. Fires during javac Very strong
Wardline-Core (authoritative) Bespoke scanner (JavaParser-based) or javac plugin Two-pass AST analysis with taint tracking, manifest consumption, SARIF output Required — no existing tool consumes the wardline manifest
Wardline-Type Checker Framework plugin Custom qualifier hierarchy with tier-flow analysis Very strong — designed for pluggable type systems
Wardline-Type (baseline) Standard Java type system Sealed interfaces, records, generic type constraints Strong — structural conformance without a plugin
Wardline-Governance Bespoke CLI (shared with Python binding) Manifest validation, fingerprint baseline, SARIF aggregation Reusable — governance is language-agnostic
Java: Ecosystem tool coverage for conformance profiles

The "Required — bespoke" surface is smaller than in Python. Error Prone provides compile-time advisory feedback (vs. ruff's pre-commit feedback). The Checker Framework provides mandatory type-system enforcement (vs. mypy's optional enforcement). The Java regime achieves higher assurance with less bespoke tooling.


B.3 Interface contract

This section is normative.

Any tool that implements Wardline-Core rules for the Java regime MUST satisfy the following interface contract (extending the parent specification's conformance criteria):

  1. Manifest consumption. The tool MUST consume the wardline manifest (wardline.yaml and overlays) and validate it against the framework's JSON Schemas before producing findings.

  2. Annotation discovery. The tool MUST discover wardline annotation syntax from the target codebase — either from source AST or from compiled bytecode — identifying which methods carry which annotations and extracting their parameters. The Java annotation vocabulary and parameter schemas are defined in B.4. Cross-binding machine identity remains the Part I annotation-group numbering and manifest schema identifiers, not the Java annotation spellings.

  3. Schema default recognition. The tool MUST recognise SchemaDefault.of() as a JV-WL-001 suppression marker. Calls wrapped in SchemaDefault.of() where the default value matches the overlay's declared approved default are governed by the overlay declaration, not by JV-WL-001.

  4. SARIF output. The tool MUST produce findings in SARIF v2.1.0 with the wardline-specific property bags defined in the parent specification (§11.1).

  5. Rule declaration. The tool MUST declare which rules it implements and MUST maintain golden corpus specimens for those rules.

  6. Verification mode. The tool SHOULD support --verification-mode for deterministic output against the golden corpus.


B.4 Annotation vocabulary: design principles, mapping table, and rationale

B.4.1 Design principles

Meta-annotations for composition. Java's meta-annotation mechanism allows wardline annotations to be composed into project-specific shorthand:

@ValidatesShape
@FailClosed
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface StrictShapeValidator {
    // Project-specific composed annotation — the scanner resolves
    // meta-annotations transitively and sees both @ValidatesShape
    // and @FailClosed on any method annotated @StrictShapeValidator.
}

Scanner implementations MUST resolve meta-annotations transitively. This is the primary mechanism for managing annotation burden — projects define domain-meaningful composed annotations during Phase 1 adoption, before enforcement tooling is active.

Annotation inheritance. Scanner implementations MUST resolve annotations transitively through interface implementation and class inheritance. If an interface method carries a wardline annotation and a class implements that interface without repeating the annotation, the scanner treats the implementation as carrying the interface method's annotation. When both the interface and implementation carry wardline annotations and they differ, the implementation takes precedence — consistent with Java's method override semantics. The scanner SHOULD emit an advisory finding recommending that implementations repeat annotations for documentation clarity.

No runtime behaviour in annotations. Wardline annotations are declarative metadata. They do not modify behaviour at runtime. There is no aspect-oriented interceptor, no bytecode weaving, no proxy generation. This is a deliberate departure from the Spring/Jakarta convention where annotations trigger runtime behaviour.

Annotation placement. All wardline annotations target ElementType.METHOD unless otherwise specified. Field-level tier annotations target ElementType.TYPE_USE (for use with the Checker Framework). Class-level annotations (e.g., @TestOnly) target ElementType.TYPE.

Retention policy. All wardline annotations use @Retention(CLASS) — preserved in bytecode for analysis tools but not requiring runtime reflection. Annotations that activate runtime structural enforcement additionally use @Retention(RUNTIME). The Checker Framework tier qualifiers (@Tier1 through @Tier4, @TierBottom) use @Retention(RUNTIME) as required by the Checker Framework.

B.4.2 Meta-annotation composition model

Where the Python binding uses decorator stacking to compose wardline markers, the Java binding uses meta-annotation composition. The difference is architectural:

  • Python: @validates_shape and @fail_closed are stacked on the function definition. The scanner discovers them by inspecting the function's decorator list. Composition is runtime — decorators execute top-to-bottom.
  • Java: @ValidatesShape and @FailClosed can be composed into a single meta-annotation (@StrictShapeValidator). The scanner discovers them by resolving meta-annotations transitively. Composition is declarative — no runtime execution order.

Meta-annotation composition is Java's native mechanism for annotation reuse and is widely understood in the ecosystem (Spring's @RestController is a meta-annotation composing @Controller and @ResponseBody). Projects SHOULD define their meta-annotation vocabulary during Phase 1 adoption so the vocabulary is stable when enforcement begins.

@Retention choices for composed annotations. A composed annotation MUST use @Retention(CLASS) or @Retention(RUNTIME). If any constituent annotation requires RUNTIME (e.g., for Checker Framework integration), the composed annotation MUST also be RUNTIME. The scanner resolves meta-annotations from bytecode (CLASS retention) or from source (SOURCE retention would be invisible to bytecode-level tools — avoid).

B.4.3 Annotation mapping table

The following table maps each of the 17 abstract annotation groups (Part I §7) to their concrete Java annotations. In SARIF and other cross-binding interchange, annotation context is identified by Part I group numbers (wardline.annotationGroups), while Java annotation names remain binding-specific diagnostic detail.

Group Abstract Name Java Annotation(s) Signature / Parameters Brief Description
1 Trust boundary declarations @ExternalBoundary @Target(METHOD) Marks method returning T4 (EXTERNAL_RAW) data from outside the system boundary
@ValidatesShape @Target(METHOD) T4 → T3 transition. Body MUST contain rejection path (JV-WL-007)
@ValidatesSemantic @Target(METHOD) T3 → T2 transition. Body MUST contain rejection path. Scanner verifies validation ordering (JV-WL-008)
@ValidatesExternal @Target(METHOD) Combined T4 → T2. Body MUST satisfy both shape and semantic validation requirements
@IntegralRead @Target(METHOD) Returns T1 (INTEGRAL) data. Body rules at INTEGRAL severity
@IntegralConstruction @Target(METHOD) T2 → T1 transition. All return type fields MUST be explicitly supplied
@IntegralWriter @Target(METHOD) Integrity-sensitive write. INTEGRAL severity. MUST precede @EmitsTelemetry. Fallback paths that bypass the integrity write produce a finding
2 Integrity primacy @IntegrityCritical @Target(METHOD) All @IntegralWriter rules plus implicit @MustPropagate on exception paths
@EmitsTelemetry @Target(METHOD) Telemetry emission. MUST NOT precede @IntegralWriter on shared code paths
3 Plugin/component contract @SystemComponent @Target(METHOD) System-owned contract. Crash-not-catch semantics; exceptions MUST propagate
4 Internal data provenance @IntData @Target(METHOD) Declares internal provenance. INTEGRAL body restrictions. Return tagged UNKNOWN_RAW without @RestorationBoundary
5 Schema contracts @AllFieldsMapped @Target(METHOD) Every field of return type MUST be explicitly supplied or wrapped in SchemaDefault.of()
@OutputSchema(fields = {...}) @Target(METHOD), String[] fields Returned object MUST contain all declared fields
6 Layer boundaries @Layer(value) @Target(TYPE), String value Enforces import direction constraints between architectural layers
7 Template/parse safety @TemplateRenderer @Target(METHOD) Template rendering MUST use parameterised APIs. Input MUST be T2+
@ParseAtInit @Target(METHOD) Method called only from constructors, static initialisers, or @PostConstruct
8 Secret handling @HandlesSecrets @Target(METHOD) Return value MUST NOT appear in logger calls, string concatenation reaching loggers, or serialisation reaching persistence
9 Operation semantics @Idempotent @Target(METHOD) First state-modifying call MUST be preceded by a guard
@Atomic @Target(METHOD) Multiple state-modifying calls MUST be within @Transactional or explicit transaction
@Compensatable(rollback) @Target(METHOD), String rollback Referenced rollback method MUST exist with compatible signature
10 Failure mode @FailClosed @Target(METHOD) No fallback values, no continue-on-error. MUST throw on failure
@FailOpen @Target(METHOD) Graceful degradation permitted. Requires trust classification annotation
@EmitsOrExplains @Target(METHOD) Every exit path MUST emit or explain
@ExceptionBoundary @Target(METHOD) Authorised exception translation point
@MustPropagate @Target(METHOD) Exceptions MUST propagate to an @ExceptionBoundary
@PreserveCause @Target(METHOD) Every catch-and-rethrow MUST chain the cause. Java-specific extension
11 Data sensitivity @HandlesPii(fields) @Target(METHOD), String[] fields Named fields MUST NOT appear in logs, exceptions, or unencrypted persistence
@HandlesClassified(level) @Target(METHOD), String level No mixing with lower classification levels
@Declassifies(fromLevel, toLevel) @Target(METHOD), String fromLevel, String toLevel Body MUST contain rejection path. Input MUST NOT leak unchanged to output
12 Determinism @Deterministic @Target(METHOD) Body MUST NOT contain non-deterministic calls (Random, UUID, Instant.now, HashMap iteration)
@TimeDependent @Target(METHOD) Explicitly declares wall-clock dependency
13 Concurrency/ordering @ThreadSafe @Target(METHOD) Shared mutable state access MUST be synchronised
@OrderedAfter(value) @Target(METHOD), String value At call sites, named method MUST be called first
@NotReentrant @Target(METHOD) Call graph MUST NOT lead back to this method
14 Access/attribution @RequiresIdentity @Target(METHOD) Identity-typed parameter MUST be received and passed to audit/persistence
@PrivilegedOperation @Target(METHOD) Authorisation check MUST precede state modification
15 Lifecycle/scope @TestOnly @Target(TYPE) Production modules MUST NOT import this symbol
@DeprecatedBy(date, replacement) @Target(METHOD), String date, String replacement After expiry: BLOCKING finding. Before: advisory
@FeatureGated(flag) @Target(METHOD), String flag Tracks flag lifecycle and stale flag detection
16 Generic trust boundary @TrustBoundary(fromTier, toTier) @Target(METHOD), int fromTier, int toTier Generic tier transition. Valid tiers 1–4. Skip-promotions to T1 are schema-invalid
@DataFlow(consumes, produces) @Target(METHOD), int consumes, int produces Descriptive only — no enforcement
17 Restoration boundaries @RestorationBoundary(...) @Target(METHOD), int restoredTier, String institutionalProvenance, boolean structuralEvidence, boolean semanticEvidence, IntegrityMethod integrityEvidence Restores serialised internal data to declared tier. Evidence-to-tier mapping per §6.3
Java: Annotation vocabulary mapping for 17 annotation groups
B.4.4 Non-obvious design rationale

Why meta-annotation composition, not decorator stacking. Python decorators are functions — they execute at function definition time, top-to-bottom. Java annotations are metadata — they are discovered by tools, not executed by the runtime. The Python binding stacks decorators because Python has no meta-decorator mechanism. The Java binding uses meta-annotations because they are the idiomatic Java mechanism for annotation composition and are understood by the entire ecosystem (IDE autocompletion, framework documentation, annotation processor resolution).

@Retention choices. The binding uses @Retention(CLASS) as the default, not @Retention(RUNTIME), because wardline annotations are consumed by static analysis tools that read bytecode (Error Prone, reference scanner), not by runtime reflection. CLASS retention preserves annotations in .class files without the runtime overhead of reflection-accessible metadata. The Checker Framework tier qualifiers require @Retention(RUNTIME) because the Checker Framework's annotation processing infrastructure uses runtime reflection during type-checking.

@PreserveCause — Java-specific extension. @PreserveCause has no Python equivalent because Python's exception chaining (raise ... from ...) is idiomatic but not enforced by the Python binding. The Java binding adds this annotation because Java's checked exception system makes cause-chain loss a common and consequential problem — a throw new SomeException(message) inside a catch block that drops the caught exception destroys diagnostic information.

@ValidatesExternal — combined T4 → T2. This annotation combines shape and semantic validation into a single method. It exists because the decomposed @ValidatesShape + @ValidatesSemantic pair requires two methods and an intermediate T3 type. For simple external data sources where the T3 intermediate adds no value, @ValidatesExternal avoids ceremony. The scanner enforces both shape and semantic validation requirements on the method body. The decomposed pair remains available for complex validation pipelines where the T3 intermediate is meaningful.

Spring-specific considerations. The annotation vocabulary deliberately avoids runtime behaviour. Spring annotations (@Transactional, @Cacheable, @Async) trigger runtime proxy generation; wardline annotations are pure metadata. This means wardline annotations and Spring annotations can coexist on the same method without interaction — except where the Spring proxy's behaviour contradicts the wardline annotation's intent (see B.7 for residual risks). The safe-composition table in B.7 defines which Spring annotations are known-safe with wardline annotations.

Checked exception interaction with @FailClosed. A method declared to throw a checked exception forces callers to handle or declare it — compiler-enforced propagation. The scanner treats checked exception declarations as complementary to @MustPropagate. For Tier 1 integrity errors, the binding recommends unchecked exceptions — they are expected to halt processing, not be declared in signatures where every caller must decide what to do with them.

SchemaDefault.of() formal semantics. SchemaDefault is a static utility class in the dev.wardline.annotations package:

public final class SchemaDefault {
    private SchemaDefault() {}
    public static <T> T of(T value) { return value; }
}

The scanner recognises SchemaDefault.of(...) syntactically and suppresses JV-WL-001 for the wrapped expression when the overlay declares the field as optional with an approved default matching the code default. Three conditions MUST all be met for suppression: (1) the field is declared in the overlay's optional_fields, (2) the code default matches the overlay's approved_default exactly, and (3) the call occurs within a @ValidatesShape or @ValidatesExternal boundary. SchemaDefault.of() inside @ValidatesSemantic is a finding — by the time semantic validation runs, field presence is guaranteed by the T3 contract.

Java-specific severity matrix changes. The Java binding's 8-rule × 8-state severity matrix reproduces the parent specification's framework matrix with two cell changes, both moving toward SUPPRESS (less severe):

Cell Parent Spec Java Binding Rationale
JV-WL-002 × GUARDED E/U S/T Records guarantee complete construction — Map.containsKey() patterns do not arise on record types
JV-WL-006 × GUARDED W/R S/T Sealed interfaces with pattern matching provide compile-time exhaustive type dispatch, making runtime instanceof redundant
Java: Binding-specific severity matrix deviations from framework

The Java matrix has 4 SUPPRESS cells (vs. the framework's 2), 25 UNCONDITIONAL cells (vs. 26), and a corpus minimum of 120 effective specimens (60 active cells × 2).


B.5 Type system and runtime enforcement

Type system enforcement

Java uses the Checker Framework for pluggable type-system enforcement. The Checker Framework extends Java's type-use annotations (@Target(TYPE_USE)) into a qualifier hierarchy with flow-sensitive type refinement. A wardline Checker plugin implements the Wardline-Type conformance profile.

Tier qualifiers follow a subtype lattice: @Tier1 <: @Tier2 <: @Tier3 <: @Tier4 (top = least trusted). Unannotated types default to @Tier4 — the conservative choice. The plugin ships with stub files for common libraries (ObjectMapper.readValue() returns @Tier4; ResultSet getters return @Tier4).

Key design decisions:

  • Records with tier-qualified components enforce tier-flow at construction: new AuditRecord(@Tier4 rawValue, ...) produces a compile error
  • Sealed interfaces with pattern matching provide exhaustive type dispatch — the compiler enforces tier discrimination
  • Standard Java type system (records, sealed interfaces, final) provides substantial wardline benefit without any plugin — the Checker Framework adds mandatory compile-time tier-flow enforcement on top

Coverage gap. The UNKNOWN and MIXED taint states are not modelled in the Checker Framework qualifier hierarchy. The reference scanner handles these states independently.

No reference implementation exists at the time of writing. The Checker Framework design is specified to implementation-ready detail but has not been validated against a production codebase.

Runtime structural enforcement

Java's runtime structural enforcement layer is substantially thinner than Python's because the language provides most enforcement structurally:

  • Access-before-set is impossible. Records require all components in the constructor. final fields require initialisation by the end of the constructor — the compiler enforces this.
  • Subclass enforcement is compile-time. sealed classes restrict extension. final classes prevent it entirely.
  • Type discrimination is compile-time. Sealed interface pattern matching replaces Protocol-based isinstance() checks.

Two areas benefit from runtime enforcement: record compact constructors (validate component values at construction time — runs on every construction path including deserialisation), and the module system (module-info.java restricts which packages are accessible, providing module-level access control over authoritative construction paths, enforced by the JVM).


B.6 Regime composition matrix

Capability Best Home Profile Why
Syntactic pattern detection (JV-WL-001–004) Error Prone BugChecker rules Advisory (not conformant) Fires during javac — integrated into compilation. Advisory only: no manifest, no tier grading
Tier-aware severity grading (all WL rules) Reference scanner Wardline-Core (authoritative) Requires manifest consumption and annotation metadata
Taint-flow tracking Reference scanner Wardline-Core No existing tool consumes the manifest's trust topology
Context-dependent rules (JV-WL-005–008) Reference scanner Wardline-Core Requires audit-path annotation awareness, tier classification, structural verification
Tier-flow type checking Checker Framework plugin Wardline-Type Compile-time enforcement — mandatory, not optional
Runtime structural enforcement Records + module system Foundation Language-native; no additional library
Manifest validation, fingerprint baseline, SARIF aggregation, control-law reporting wardline CLI Wardline-Governance Shared with Python regime
Java: Regime composition matrix with tool assignments

Temporal layering:

Compile time:  Error Prone (advisory) + Checker Framework (tier-flow)
CI time:       Reference scanner (authoritative, tier-graded)
Governance:    wardline CLI (manifest, baseline, SARIF aggregation)

In the Python regime, advisory feedback (ruff) requires a separate pre-commit step. In the Java regime, advisory feedback fires during javac — developers receive advisory and type-system feedback simultaneously during compilation, before any separate analysis step.

Profile coverage by tool:

Tool Criteria Covered Profile
wardline-annotations 1 Foundation
Error Prone rules (advisory — not conformant)
Reference scanner 2, 3, 4, 5, 6, 7, 8, 10 Wardline-Core
Checker Framework plugin 1, 5, 6, 7 Wardline-Type
wardline-cli 9, 10 Wardline-Governance
Full regime All 10 Wardline-Full
Java: Conformance criteria coverage by enforcement tool

What does not fit existing tools:

  • Do not force tier-aware taint analysis into Error Prone — it lacks cross-method taint propagation
  • Do not use SpotBugs as the normative scanner — bytecode analysis loses source-level annotation context. Use as supplementary for Lombok-affected classes
  • Do not build an IntelliJ plugin until Checker Framework integration is proven
  • Do not duplicate governance tooling — wardline-cli is shared across regimes

Ecosystem migration candidates. SonarQube (enterprise analysis — SARIF import available since 9.4+), PMD (alternative advisory), SpotBugs (supplementary bytecode), ArchUnit (architectural constraints), CodeQL (deep call-graph analysis). All optional — the core regime provides Wardline-Full conformance.


B.7 Residual risks

The following residual risks are specific to the Java language binding. The parent specification (§13) documents binding-independent residual risks — particularly risk 12 (evasion surface trajectory), which applies to both bindings: as annotation coverage grows, coding-level risk falls but governance risk rises.

Scope clarification: tiers are not security classifications. The wardline's four-tier authority model classifies data by provenance and validation status. It does not classify data by any jurisdictional information classification policy. Assessors SHOULD NOT equate "Tier 1" with "SECRET" or "Tier 4" with "OFFICIAL" — the dimensions are orthogonal.

Framework proxy blind spots

Enterprise Java frameworks generate runtime proxies that wrap annotated methods in interceptor chains. The scanner analyses source code and does not see the proxy's behaviour.

Spring AOP proxies. In the normal case, @Transactional is compatible with wardline semantics. The proxy becomes dangerous in specific edge scenarios:

  • @Retryable + @Recover: A @Recover method provides a fallback value after retry exhaustion — an implicit @FailOpen invisible to source analysis of the @FailClosed method. The scanner SHOULD emit a BLOCKING finding.
  • @Cacheable: Returns a cached value, bypassing the method body. If the method carries @ValidatesShape, the validation is skipped on subsequent calls.
  • @Async void: Changes execution context. Exceptions in @Async void methods are dispatched to an AsyncUncaughtExceptionHandler, which by default logs and discards them — a severe @FailClosed violation.
  • Checked exception rollback: @Transactional rolls back only on unchecked exceptions by default. Checked exceptions cause commit — the opposite of fail-closed. Either use unchecked exceptions for tier-sensitive failures or configure @Transactional(rollbackFor = Exception.class).

Safe proxy compositions (closed set — everything else presumed unsafe and flagged for review):

Spring Annotation Safe With Rationale
@Transactional (default propagation) @FailClosed, @ValidatesShape, @ValidatesSemantic, @IntegralConstruction Unchecked exceptions → rollback, normal return → commit. Does not alter exception or return semantics
@Validated / @Valid All wardline annotations Bean Validation runs before method body
@PreAuthorize / @PostAuthorize All wardline annotations Security checks before/after method. Rejection is an exception, compatible with @FailClosed
Java: Safe Spring proxy compositions with wardline annotations

@Transactional no-op scenarios. Self-invocation (this.method()) bypasses the proxy. No active PlatformTransactionManager causes silent no-op. protected/package-private methods are not proxied by CGLIB. final methods cannot be overridden. All are dangerous when the method also carries @Atomic or @FailClosed.

Self-invocation proxy bypass. The scanner SHOULD emit an advisory finding when a class contains both wardline annotations and Spring AOP annotations and contains intra-class method calls targeting those annotated methods.

JPA entity lifecycle

JPA entities transition through managed → detached → merged states. A managed entity is live — field changes are automatically persisted. entityManager.merge() is a restoration boundary in disguise — it reinstitutes a detached representation as managed without explicit provenance evidence.

Compensating controls: Do not use JPA entities as T1 data models. Use records or immutable domain objects for T1 artefacts. Annotate repository methods with @IntData and @RestorationBoundary. Prefer DTO projection queries (SELECT new PartnerDTO(...)) or Spring Data interface projections over entity fetching for read-only paths.

Lombok-generated code

@Builder on tier-sensitive classes generates a permissive build() that accepts partial construction — an unconditional finding. @Data generates mutable setters violating immutability expectations for T1/T2. @With creates modified copies without governed construction paths. @SneakyThrows silently converts checked exceptions, undermining @MustPropagate and @FailClosed.

Graduated Lombok posture:

Phase Posture
1–2 Lombok permitted. Enable bytecode analysis pass as recommended
3 Scanner flags specific patterns on tier-sensitive classes: @Builder on T1/T2 (ERROR), @Data on T1/T2 (WARNING), @With on T1 (WARNING). Lombok on T3/T4 produces no findings
4 Same as Phase 3. Checker Framework analyses source-level annotations; bytecode pass compensates
Java: Graduated Lombok posture by adoption phase

Migration is remediation, not prerequisite. The recommended migration target for flagged classes is Java records.

Reflection bypass

Java reflection (setAccessible(true), Field.set()) can bypass access controls and modify final fields. The module system restricts reflection access: if the package is not opens-ed in module-info.java, external modules cannot use reflection. The fingerprint baseline tracks classes in tier-sensitive modules.

Serialisation attacks

ObjectInputStream.readObject() is a restoration boundary that provides no evidence — an object deserialised from untrusted bytes is T4 regardless of class type. Jackson/Gson deserialisation is safer but still creates objects without validation boundaries. Never use Java serialisation for tier-sensitive data. Annotate Jackson deserialisation entry points with @ExternalBoundary or @RestorationBoundary.

Annotation retention and injection

A build tool or obfuscator (ProGuard, R8) may strip annotations from bytecode. A malicious annotation processor could inject annotations not present in source. The reference scanner compensates: it operates on source files (JavaParser), so processor-injected annotations are invisible. The Checker Framework does see processor-injected annotations — this vector is covered by the supply chain threat model.

Spring-specific residual risks

@Async void exception swallowing. Exceptions are dispatched to AsyncUncaughtExceptionHandler, which by default logs and discards. Scanner SHOULD emit BLOCKING when @Async (without CompletableFuture return) appears with @FailClosed, @MustPropagate, or @IntegrityCritical. The same applies to @Scheduled and @EventListener / @TransactionalEventListener methods.

@Retryable + @Recover. @Retryable alone is compatible with @FailClosed. @Recover provides an implicit @FailOpen. Scanner SHOULD emit BLOCKING when a @FailClosed method's class contains a @Recover with compatible return type.

Dependency injection container resolution. @Profile, @ConditionalOnProperty, and @Qualifier cause different implementations to be injected at deployment time. The scanner sees each implementation individually but cannot determine which is active at runtime. Annotate each implementation with its actual tier, not the interface's declared tier.

Reactive and asynchronous pipelines

Reactive pipelines (Spring WebFlux, Project Reactor) express data flow as lambda chains connected by operators. The scanner analyses each method individually but cannot verify that the pipeline composition respects tier ordering or that error operators (onErrorResume, onErrorReturn) do not introduce implicit @FailOpen paths.

Compensating controls: Perform tier transitions imperatively at module boundaries, then pass validated data into the reactive pipeline. Annotate terminal operations with the expected output tier. Document reactive pipeline tier flows in governance.

Combined false-negative surface

Java's stronger type system narrows the false-negative surface compared to Python, but virtual method dispatch on non-sealed, non-final classes introduces an additional false-negative source proportional to the codebase's use of non-sealed hierarchies.


B.8 Worked example with SARIF output

This example traces data through the full tier lifecycle — from raw external input to authoritative artefact — demonstrating annotations in context.

Scenario. A risk assessment system receives partner data from an external API, validates it, and produces an authoritative risk assessment record.

Data flow:

External API response (T4)
    → parsePartnerResponse() → PartnerDTO (T3)
        → validatePartnerSemantics() → ValidatedPartner (T2)
            → createRiskAssessment() → RiskAssessment (T1)

Step 1: External boundary — receiving raw data (T4)

@ExternalBoundary
public Map<String, Object> fetchPartnerData(String partnerId) {
    try {
        var request = HttpRequest.newBuilder()
            .uri(URI.create(partnerApiUrl + "/" + partnerId))
            .GET().build();
        var response = httpClient.send(request, BodyHandlers.ofString());
        return objectMapper.readValue(response.body(), MAP_TYPE);
    } catch (IOException | InterruptedException e) {
        throw new ExternalDataException(
            "Failed to fetch partner data: " + partnerId, e);
    }
}

Step 2: Shape validation (T4 → T3)

public record PartnerDTO(
    String partnerId, String name,
    String countryCode, String classification,
    List<String> riskIndicators
) {
    public PartnerDTO {
        Objects.requireNonNull(partnerId);
        Objects.requireNonNull(name);
        Objects.requireNonNull(countryCode);
        Objects.requireNonNull(classification);
        Objects.requireNonNull(riskIndicators);
        riskIndicators = List.copyOf(riskIndicators);
    }
}

@ValidatesShape
public PartnerDTO parsePartnerResponse(Map<String, Object> raw) {
    var partnerId = requireString(raw, "partner_id");
    var name = requireString(raw, "name");
    var countryCode = requireString(raw, "country_code");
    var classification = requireString(raw, "security_classification");

    // risk_indicators is optional-by-contract — declared in overlay
    List<String> indicators;
    var rawIndicators = raw.get("risk_indicators");
    if (rawIndicators == null) {
        indicators = SchemaDefault.of(List.of());
    } else if (rawIndicators instanceof List<?> list) {
        indicators = list.stream()
            .map(item -> {
                if (!(item instanceof String s)) {
                    throw new SchemaException(
                        "risk_indicators item: expected String, got "
                        + item.getClass().getSimpleName());
                }
                return s;
            })
            .toList();
    } else {
        throw new SchemaException(
            "risk_indicators: expected List, got "
            + rawIndicators.getClass().getSimpleName());
    }

    return new PartnerDTO(partnerId, name, countryCode,
        classification, indicators);
}

Step 3: Semantic validation (T3 → T2)

public record ValidatedPartner(
    String partnerId, String name,
    String countryCode, String classification,
    List<String> riskIndicators
) {
    public ValidatedPartner {
        riskIndicators = List.copyOf(riskIndicators);
    }
}

// validation_scope in overlay:
//   consumers: ["recordToLandscape", "generatePartnerReport"]
@ValidatesSemantic
public ValidatedPartner validatePartnerSemantics(PartnerDTO dto) {
    if (!VALID_COUNTRY_CODES.contains(dto.countryCode())) {
        throw new DomainValidationException(
            "Unrecognised country code: " + dto.countryCode());
    }
    if (!VALID_CLASSIFICATION_LEVELS.contains(dto.classification())) {
        throw new DomainValidationException(
            "Invalid classification: " + dto.classification());
    }
    if (dto.name().isBlank()) {
        throw new DomainValidationException("Partner name is empty");
    }
    for (var indicator : dto.riskIndicators()) {
        if (!KNOWN_RISK_INDICATORS.contains(indicator)) {
            throw new DomainValidationException(
                "Unknown risk indicator: " + indicator);
        }
    }
    return new ValidatedPartner(
        dto.partnerId(), dto.name().strip(),
        dto.countryCode(), dto.classification(),
        dto.riskIndicators());
}

Step 4: Trusted construction (T2 → T1)

public record RiskAssessment(
    String assessmentId, String partnerId,
    String partnerName, String riskLevel,
    String classification, String assessedBy,
    Instant assessedAt
) {
    public RiskAssessment {
        Objects.requireNonNull(assessmentId);
        Objects.requireNonNull(partnerId);
        Objects.requireNonNull(partnerName);
        Objects.requireNonNull(riskLevel);
        Objects.requireNonNull(classification);
        Objects.requireNonNull(assessedBy);
        Objects.requireNonNull(assessedAt);
    }
}

@IntegralConstruction
public RiskAssessment createRiskAssessment(
        ValidatedPartner partner, AuditContext context) {
    return new RiskAssessment(
        generateAssessmentId(),
        partner.partnerId(),
        partner.name(),
        computeRiskLevel(partner),
        partner.classification(),
        context.identity(),
        context.timestamp());
}

The complete call chain:

public RiskAssessment assessPartner(String partnerId, AuditContext context) {
    var raw = fetchPartnerData(partnerId);                    // T4
    var dto = parsePartnerResponse(raw);                      // T3
    var validated = validatePartnerSemantics(dto);             // T2
    var assessment = createRiskAssessment(validated, context); // T1
    return assessment;
}

Each line is a tier transition. Each method has one annotation declaring one transition. The types in the signatures tell the tier story even without the annotations.

SARIF output for a violation. If the monolithic version of this code used getOrDefault() on the T1 construction path, the scanner would produce:

{
  "version": "2.1.0",
  "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
  "runs": [{
    "tool": {
      "driver": {
        "name": "wardline-scanner",
        "version": "0.2.0",
        "rules": [{
          "id": "JV-WL-001",
          "shortDescription": {
            "text": "Member access with fallback default"
          }
        }]
      }
    },
    "results": [{
      "ruleId": "JV-WL-001",
      "level": "error",
      "message": {
        "text": "Map.getOrDefault() provides fabricated default on INTEGRAL path"
      },
      "locations": [{
        "physicalLocation": {
          "artifactLocation": {
            "uri": "src/main/java/com/myorg/service/PartnerService.java"
          },
          "region": {
            "startLine": 47,
            "startColumn": 29
          }
        }
      }],
      "properties": {
        "wardline.rule": "JV-WL-001",
        "wardline.taintState": "INTEGRAL",
        "wardline.enclosingTier": 1,
        "wardline.severity": "ERROR",
        "wardline.exceptionability": "UNCONDITIONAL",
        "wardline.excepted": false,
        "wardline.annotationGroups": [1, 16],
        "wardline.enclosingAnnotation": "@IntegralConstruction",
        "wardline.boundaryFunction": "com.myorg.service.PartnerService.createRiskAssessment"
      }
    }],
    "properties": {
      "wardline.manifestHash": "sha256:a1b2c3d4e5f6...",
      "wardline.coverageRatio": 0.68,
      "wardline.controlLaw": "normal",
      "wardline.deterministic": true
    }
  }]
}

The mandatory property bags (wardline.rule, wardline.taintState, wardline.enclosingTier, wardline.severity, wardline.exceptionability, wardline.excepted, wardline.annotationGroups) follow the parent specification's SARIF contract (Part I §11.1). The additional properties (wardline.enclosingAnnotation, wardline.boundaryFunction) are binding-specific extensions that provide Java-specific diagnostic context — they are not required by the interface contract but are recommended for implementers.

Agent guidance note. For agents working in wardline-annotated Java codebases, the scanner finding-to-remediation mapping in the original §60.9 provides specific actions for each JV-WL rule. Key patterns: replace .orElse(default) with .orElseThrow() for JV-WL-001; narrow catch (Exception e) to specific types for JV-WL-003; add rejection paths for JV-WL-007; verify validation ordering for JV-WL-008. Multi-agent workflows are expected to treat wardline annotations and governance artefacts as requiring the same human review as single-agent output.

Annotation change impact preview. Java binding implementations SHOULD support annotation change impact preview using the SARIF metadata defined in Part I §11.1. When a developer modifies a tier assignment or annotation — e.g., adding @ValidatesExternal to replace a @ValidatesShape + @ValidatesSemantic pair, or changing a module's tier declaration in the manifest — the tool shows the cascade: newly applicable pattern rules, resolved findings, severity changes, and affected modules. The primary span is the changed annotation; secondary spans (carried in SARIF relatedLocations) are code locations whose compliance status changes. Because Error Prone fires during javac, Phase 2 advisory implementations MAY surface a simplified cascade view at compile time; the full SARIF-based impact preview is a Phase 3 (Wardline-Core) capability.


B.9 Adoption strategy

The Java regime supports four incremental adoption phases:

Phase What You Add What You Get Build Integration
1. Annotations only wardline-annotations (compileOnly, zero transitive dependencies) Documentation value. Annotations visible in code review and IDE navigation None
2. Advisory checks wardline-errorprone Compile-time warnings for JV-WL-001–004. Immediate developer feedback during javac javac plugin (integrated into compilation)
3. Authoritative scanner wardline-scanner in CI Tier-aware severity grading, taint-flow analysis, governance-grade SARIF CI step
4. Type system enforcement wardline-checker Compile-time tier-flow enforcement via Checker Framework. No reference implementation exists at time of writing javac annotation processor
Java: Adoption phases with components and build integration

Phase 2 is the adoption accelerator. Error Prone fires during javac — advisory findings appear in the IDE as developers type, with no additional configuration. This makes Phase 2 nearly frictionless.

Phase 2 is not conformant. A project at Phase 2 does not achieve any wardline conformance profile. It provides advisory warnings that help developers learn the pattern language but does not consume the manifest, produce tier-graded SARIF, or enforce pattern rules authoritatively.

Phase 3 is a legitimate end state. Wardline-Core achieves meaningful assurance: authoritative tier-graded findings, taint-flow analysis, governance-grade SARIF. Phase 4 adds compile-time enforcement but is not required for conformance.

Java version requirement. Java 17+ is a hard minimum. Records, sealed classes, and pattern matching are essential. Projects on earlier versions need to plan a Java 17 migration before adopting this binding. A Java 11 project receives more value from the Python binding on its Python services than from a degraded Java binding.

The adoption bottleneck is organisational, not technical. Annotating code (Phase 1) is immediate. Phase 2 requires platform team approval for Error Prone. Phase 3 requires a new CI step. Phase 4 requires Checker Framework — a GPL-licensed dependency that legal/compliance teams may flag. Each transition crosses an organisational boundary.

Realistic timeline. Plan for 6–12 months from initial interest to Phase 4 deployment when accounting for dependency approval, security assessment, platform team onboarding, and pilot validation. Phase 1 can proceed immediately while remaining dependencies are in the approval pipeline.


B.10 Error handling and control law

The Java regime's scanner error handling follows the same principles as the Python binding, with Java-specific adaptations.

Exit codes are consistent across all wardline regimes:

Code Meaning Action
0 All files scanned, no ERROR-severity findings CI pass
1 At least one ERROR-severity finding CI fail
2 Scanner internal error CI fail — fix configuration
3 Direct law — authoritative scanner did not run Governance policy determines CI behaviour
Java: Scanner exit codes and CI behaviour

Phase-parameterised control law. The control law is parameterised by the project's declared adoption phase. Each phase defines its own normal/alternate/direct thresholds:

Phase Normal Operation Alternate Direct
1 wardline CLI validates manifest CLI absent
2 Error Prone + wardline CLI CLI absent Error Prone absent
3 Reference scanner + wardline CLI Error Prone absent (advisory) Scanner absent OR CLI absent
4 Error Prone + Scanner + Checker + CLI Error Prone absent OR Checker absent Scanner absent OR CLI absent
Java: Phase-parameterised control law thresholds

The declared phase is set in wardline.toml ([regime] phase = 3). Phase transitions are governance events requiring review. Regressing from Phase 4 to Phase 3 reduces assurance and is expected to be documented.

Control-law transition governance:

  • Transitions from normal to alternate/direct SHOULD require acknowledgement from the governance authority
  • Every state change MUST be recorded in SARIF output (wardline.controlLaw.transition)
  • Recommended maximum alternate-law duration: 14 days. Direct-law: 48 hours
  • When returning from degraded operation, a retrospective scan of all code committed during the period SHOULD be performed