Jakarta Servlet Setup

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.

Integrate native Java enforcement into Jakarta Servlet and verify how the adapter observes requests and responses.

Before you begin

You need Java 17+, Maven, and an existing Jakarta Servlet 6.1 application and a compatible container. These are integration snippets, not a standalone generated application. First confirm that an ordinary endpoint runs without AIWAF.

1. Add the Java dependency

pom.xml / dependencies

Java source version 1.3.5 is declared in java/pom.xml. It targets Java 17, Spring Framework 7.0, and Jakarta Servlet 6.1, and runs natively without Rust. Use the Maven dependency below once this version is published to Maven Central; source builds are available from the monorepo.

<dependency>
  <groupId>io.github.aiwaf-project</groupId>
  <artifactId>aiwaf-java</artifactId>
  <version>1.3.5</version>
</dependency>

Build from source when needed

If the required version is unavailable in your registry, install the reviewed monorepo source in your local Maven repository:

git clone https://github.com/aiwaf-project/aiwaf.git
cd aiwaf/java
mvn -Dgpg.skip=true install

Return to your application checkout afterward. Match its dependency version to the version built from java/pom.xml.

2. Understand the engine boundary

The following code belongs inside application initialization/request handling, not at the top level of a Java source file. It illustrates the core API; the filter shown next performs this adapter work for Servlet requests.

Use the two-stage API when the integration can observe the handler response. Deterministic controls run before the application; anomaly scoring then receives the actual status and elapsed time.

import com.aiwaf.core.AiwafConfig;
import com.aiwaf.core.AiwafDecision;
import com.aiwaf.core.AiwafEngine;
import com.aiwaf.core.AiwafRequest;

import java.util.Map;
import java.util.Set;

AiwafConfig config = new AiwafConfig();
config.rateLimitEnabled = true;
config.rateLimitMax = 20;
config.rateLimitWindowSeconds = 10;

AiwafEngine engine = new AiwafEngine(config);
AiwafRequest request = new AiwafRequest(
    "GET",
    "/api/profile",
    "203.0.113.10",
    "US",
    Map.of("User-Agent", "Mozilla/5.0", "Accept", "application/json"),
    Map.of(),
    System.currentTimeMillis(),
    Set.of()
);

long started = System.currentTimeMillis();
AiwafDecision decision = engine.evaluateBeforeResponse(request);
if (decision.allowed()) {
    decision = engine.evaluateAfterResponse(
        request,
        200,
        System.currentTimeMillis() - started
    );
}

evaluate(request) remains available for request-only integrations and uses provisional response values for AI features.

3. Register the Servlet filter

During servlet-context initialization, add the filter before the handlers it protects. Import com.aiwaf.servlet.AiwafServletFilter in the class that owns this initialization. Keep the container's existing initialization and filter ordering.

AiwafConfig config = new AiwafConfig();
AiwafEngine engine = new AiwafEngine(config);

servletContext
    .addFilter("aiwaf", new AiwafServletFilter(engine))
    .addMappingForUrlPatterns(null, false, "/*");

The generic servlet filter uses request-time evaluation. Spring applications should prefer AiwafFilter for handler metadata and post-response scoring.

Configuration and operations

AreaMain AiwafConfig fields
Rate limitingrateLimitEnabled, rateLimitScope, rateLimitWindowSeconds, rateLimitMax, rateLimitFloodThreshold
HeadersheaderValidationEnabled, requiredHeadersByMethod, minHeaderQualityScore, size/count limits
Requestbody, parameter, method, and content-type limits
ProxiestrustedProxyCidrs, maxForwardedForEntries
ExemptionsexemptIps, exemptPaths, autoExemptPathPrefixes
GeoIPgeoBlockEnabled, geoAllowedCountries, geoBlockedCountries, geoIpDatabasePath, cache limits
Route manifestpathManifestEnabled, pathManifestPath, pathRules
UUID checksuuidTamperEnabled, uuidScoreWindowSeconds, uuidScoreBlockThreshold, signal weights, uuidParameterNames
AIaiEnabled, aiModelPath, aiAnomalyScoreThreshold
StoragestorageBackend, storageFilePath, storageRedisUrl, storageKeyPrefix, storageRedisFailureMode, Redis timeout and pool limits
TelemetryobservabilityEnabled

GeoIP uses the bundled MMDB through the native Java reader; it does not require the external mmdblookup executable. A valid X-Country value is accepted only from a peer in trustedProxyCidrs; otherwise AIWAF resolves the client address itself. Default blocklist calls use Python-compatible reputation scoring and temporary escalation (15 minutes, 1 hour, then 24 hours). Use BlocklistManager.blockPermanent(ip, reason) for an explicit permanent administrative block.

storageBackend="redis" makes rate-limit buckets, honeypot timing, weighted UUID scores, recent-request anomaly state, blocklist data, exemptions, geo policy, and learned keywords visible to every application instance using the same key prefix. Redis enforcement updates use atomic server-side scripts. Memory, file, CSV, and SQLite engines keep request state isolated per AiwafEngine; constructing another engine no longer replaces an existing engine's stores. The static RuntimeStorage and BlocklistManager methods remain compatibility facades for command-line and administrative code.

The Java 1.3 Redis backend supports standalone Redis only. Sentinel and Cluster configurations are rejected during validation instead of silently operating with incomplete semantics. fail_closed is the default: an unavailable Redis instance prevents startup or raises a request-path storage failure, which results in the application denying availability rather than bypassing protection. fail_open permits startup and returns non-blocking enforcement fallbacks while Redis is unavailable; failed mutations return false at the StorageBackend boundary. Connection, socket, pool size, and pool wait limits are bounded and configurable. RedisStorage.diagnostics() reports operation/failure counts, latency, and live pool utilization.

Redis keys include a state-schema version, and each base key prefix has an atomic compatibility marker. Releases using the same schema can share state during a rolling upgrade. A runtime with an incompatible schema refuses the namespace—even in fail_open mode. Use a unique key prefix per application and environment; deploy an incompatible schema with a new prefix, migrate deliberately, then retire the old namespace. Serialized persistent values remain JSON within a schema version.

New Java models use the Python-aligned six-feature schema and are written as safe JSON containing the Python aiwaf_rust.IsolationForest state. Python can restore Java-generated forest state, and Java can load Python Rust-model artifacts. Existing signed Java binary artifacts, including nine-feature models, remain readable through the restricted legacy loader. Scikit-learn object artifacts are intentionally unsupported.

Java operational commands mirror the common Python workflow:

aiwaf-cli status
aiwaf-cli diagnose --model model.json
aiwaf-cli blocked
aiwaf-cli unblock 203.0.113.10
aiwaf-cli clear
aiwaf-cli train --events events.json --out model.json --backend java
aiwaf-cli replay --cases replay.json
aiwaf-cli model status --path model.json
aiwaf-cli logs --dir aiwaf_logs

Build Java from the repository root:

cd java
mvn test
mvn -Dgpg.skip=true package

The build produces the main, source, and Javadoc JARs under java/target/. The main JAR embeds the root license as META-INF/LICENSE. FastR training is optional and falls back to the Java trainer when R or jsonlite is unavailable.

Verify the deployed adapter

  1. Build the existing application with mvn test and its normal packaging command. Resolve framework/Servlet compatibility errors before starting the server.
  2. Start the application with its existing launcher and request a known non-exempt handler through a browser.
  3. Inspect the enabled policy, observed client IP, response status, and duration. Private-address exemptions can affect local tests.
  4. Exercise a deliberate block or rate boundary on a staging route, and confirm the handler response is not returned for the rejected request.
  5. For distributed deployments, repeat across instances and verify the selected namespace, backend, and failure policy.

A successful normal request proves reachability. It does not prove that every middleware is selected or that a trained model is loaded. Use the Java architecture guide to understand request-only versus response-aware decisions.

Checkpoint You can locate the dependency, initialization/filter registration, configuration file, and operational verification for this adapter.