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.
How AIWAF works: system architecture
Integrate native Java enforcement into Jakarta Servlet and verify how the adapter observes requests and responses.
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 installReturn 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
| Area | Main AiwafConfig fields |
|---|---|
| Rate limiting | rateLimitEnabled, rateLimitScope, rateLimitWindowSeconds, rateLimitMax, rateLimitFloodThreshold |
| Headers | headerValidationEnabled, requiredHeadersByMethod, minHeaderQualityScore, size/count limits |
| Request | body, parameter, method, and content-type limits |
| Proxies | trustedProxyCidrs, maxForwardedForEntries |
| Exemptions | exemptIps, exemptPaths, autoExemptPathPrefixes |
| GeoIP | geoBlockEnabled, geoAllowedCountries, geoBlockedCountries, geoIpDatabasePath, cache limits |
| Route manifest | pathManifestEnabled, pathManifestPath, pathRules |
| UUID checks | uuidTamperEnabled, uuidScoreWindowSeconds, uuidScoreBlockThreshold, signal weights, uuidParameterNames |
| AI | aiEnabled, aiModelPath, aiAnomalyScoreThreshold |
| Storage | storageBackend, storageFilePath, storageRedisUrl, storageKeyPrefix, storageRedisFailureMode, Redis timeout and pool limits |
| Telemetry | observabilityEnabled |
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
- Build the existing application with
mvn testand its normal packaging command. Resolve framework/Servlet compatibility errors before starting the server. - Start the application with its existing launcher and request a known non-exempt handler through a browser.
- Inspect the enabled policy, observed client IP, response status, and duration. Private-address exemptions can affect local tests.
- Exercise a deliberate block or rate boundary on a staging route, and confirm the handler response is not returned for the rejected request.
- 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.
