Java Architecture and Response-Aware Enforcement

The native Java engine consumes an AiwafRequest and returns an AiwafDecision. Spring and Servlet integrations map HTTP traffic into that contract; storage contexts and model providers are owned by the engine.

Source and package versions

One AIWAF repository. Python: py/aiwaf/ · Node.js: js/ · Rust/WASM: rust/ · Java: java/. Source and canonical README. Versions shown describe the source checkout; package releases are independent. Current Python/Node source targets are aiwaf 1.1.8 (Python) and aiwaf 1.1.5 (npm), with Rust/Python 0.2.7 and WASM 0.2.5 and Java 1.3.5. Sandbox guides retain their separately pinned builds.

The engine contracts

ContractPurpose
AiwafRequestNormalized method, path, client, country, headers, parameters, timing context, and disabled middleware metadata.
AiwafConfigTyped limits, exemptions, path rules, storage, model, and observability settings.
AiwafDecisionAllow or deny outcome with a response status and reason.
AiwafEngineOrchestrates checks using its storage context, model provider, UUID lookup, and telemetry.

The engine is independent of a controller or servlet response object. Adapters decide how to map an engine decision into HTTP behavior. Java runs natively and does not load the Rust extension.

Two-stage Spring execution

  1. Map and inspectDiscover route metadata, buffer permitted request data, and construct AiwafRequest.
  2. Before responseevaluateBeforeResponse runs deterministic controls and can prevent controller execution.
  3. ControllerThe allowed request traverses the normal filter chain.
  4. After responseUse actual status and duration for UUID behavior and anomaly evaluation when the response is still replaceable.

AiwafFilter sends an early error for a pre-response denial. After a synchronous handler, it checks whether the response is committed. An uncommitted response can be reset and replaced if the final decision denies it.

A committed response is recorded through recordCommittedResponse. Asynchronous completion uses a listener and records once. These paths update relevant history and logs without trying to replace output that has already been sent.

The request-only evaluate(request) API remains available and uses provisional status and timing for its AI features. The generic Servlet filter uses that request-time path; Spring's filter has the richer response-aware boundary.

Inside the pre-response engine

  1. Validate request shape, compressed-body policy, and prohibited Java-serialization payloads.
  2. Resolve the path rule, rate overrides, and path/IP exemption state.
  3. Reject active blocklisted clients unless the client exemption applies.
  4. Enforce the configured method policy.
  5. Run applicable keyword, UUID, honeypot, header, geo, and rate/flood checks.
  6. Return an allow decision, deferring anomaly scoring for the two-stage API.

Each failing stage finalizes a decision and returns early. Path exemptions do not bypass the initial request-shape and method gates, and an exempt path does not automatically bypass an active client blocklist. Geo-only exemptions disable geo enforcement without implying a complete bypass.

Discovery and configuration before requests arrive

Spring Boot servlet auto-configuration creates the configuration, engine, and ordered filter unless the application supplies replacements. The live MVC mappings expose registered routes and handler annotations. This lets the filter honor route metadata without maintaining a separate controller list.

Manifest generation writes discovered routes and policy metadata; PathManifestCore loads and compiles it for the engine. Generated rules are appended after explicit path rules, preserving explicit equal-prefix precedence. Optional JPA metamodel discovery supplies UUID-valued primary-key and unique-property lookup.

JSON configuration uses the shared nested setting vocabulary. Environment variables and Spring properties overlay file values. Configuration loading decides the engine's policy and dependencies before request evaluation begins.

Per-engine state and atomic Redis operations

Each engine holds a RuntimeStorage.Context. The context combines typed operational stores with RuntimeState for rate counters, form timing, UUID scores, and recent behavior. Creating another engine does not replace the first engine's context.

BackendOwnershipBehavior across application instances
MemoryLocal engine state.No shared enforcement across instances.
File, CSV, SQLiteLocal persistent store with per-engine request state.Persistence alone does not distribute every request counter.
Standalone RedisShared namespace with versioned keys.Atomic server-side operations coordinate rate, form, UUID, and anomaly state as well as operational stores.

The static storage and blocklist helpers remain compatibility facades for CLI and administrative usage; the engine evaluates requests against its own context.

Redis failure and upgrade boundaries

Java 1.3 supports standalone Redis and validates unsupported Sentinel or Cluster configurations rather than pretending they provide equivalent semantics. Connection, socket, pool-size, and pool-wait limits bound the request's wait for state.

fail_closed prevents startup or raises a request-path storage failure when Redis is unavailable. fail_open uses nonblocking enforcement fallbacks and reports failed mutations at the storage boundary. These policies change availability behavior, not model semantics.

Versioned keys and a namespace compatibility marker prevent incompatible state schemas from sharing one prefix during an upgrade. A schema mismatch refuses the namespace even under fail_open. Use separate prefixes for separate applications, environments, or intentionally incompatible schemas.

Response-derived UUID and anomaly evidence

Malformed UUID input contributes an immediate weighted signal. A valid UUID request that returns 404 adds a different probing signal; successful requests can decay the score. The post-response stage supplies the real outcome instead of treating every request as an assumed success.

The lazy model provider loads the configured model on demand or through preloading. Scoring builds the Python-aligned six-feature vector, evaluates the forest, and applies threshold and behavior-confirmation rules. Forest availability and sufficient behavior evidence determine whether the AI path can deny a request.

FeatureWhat it describesWhy it helps
path_lenLength of the request path.Unusually long probing paths.
kw_hitsMatches against malicious keywords.Known probing patterns within a path.
resp_timeResponse duration in seconds.Behavior that differs from the normal timing distribution.
status_idxAn encoded response-status category.Response outcomes without treating status codes as continuous measurements.
burst_countRequests associated with the client in the burst window.Rapid repeated activity.
total_404Not-found responses in the relevant per-client history.Repeated discovery of nonexistent routes.

The feature order is part of the model contract. A trainer and scorer must agree on the order, status mapping, time units, and window semantics. Summary fields ending in _ms use milliseconds; the feature vector uses seconds. Sharing feature names alone does not make every model artifact interchangeable.

New Java models persist safe JSON containing the supported Python Rust-model forest state. Restricted signed legacy Java models remain compatibility inputs, including older feature schemas. A schema match must be checked before exchanging an artifact.

Worked example: a missing UUID resource

A valid UUID request passes deterministic checks and reaches a Spring controller. The controller returns 404. If the response is uncommitted, the filter supplies that status and duration to the post-response engine. The engine updates weighted UUID behavior, evaluates eligible anomaly evidence, records recent history, and returns the final decision.

If the controller already streamed its response, the committed path records the result without replacing it. The observed 404 still matters for subsequent behavior. System overview explains the broader feedback loop; operations covers Redis and model administration.

Trace the implementation

Follow these files in the monorepo to connect the architecture to the code: