The official Java SDK for ReqKey API-key validation, atomic credit metering, consumer rate limits, and correlated request analytics.
The SDK targets Java 17 and keeps all protocol and middleware behavior in one framework-neutral core. Framework modules only translate native request and response objects into that core model.
| Artifact | Use it for |
|---|---|
com.reqkey:reqkey-core |
Plain Java, the reusable JDK HTTP client, and custom integrations |
com.reqkey:reqkey-servlet |
Jakarta Servlet 6 filters |
com.reqkey:reqkey-jaxrs |
JAX-RS / Jakarta REST 3.1 request and response filters |
com.reqkey:reqkey-spring-boot-starter |
Spring Boot 3.5 servlet auto-configuration |
com.reqkey:reqkey-examples |
Compilable examples; not intended as an application dependency |
Quarkus and Micronaut do not have separate modules in this first release. Their JAX-RS or Servlet support can use the existing adapters without adding another copy of the ReqKey lifecycle.
The project is ready to produce normal Maven source, Javadoc, and binary JARs, but the artifacts have not been uploaded to Maven Central yet. Install the current snapshot into your local Maven repository:
git clone https://github.com/Req-Key/reqkey-java.git
cd reqkey-java
./mvnw installThen add only the module your application needs:
<dependency>
<groupId>com.reqkey</groupId>
<artifactId>reqkey-core</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>Replace reqkey-core with reqkey-servlet, reqkey-jaxrs, or
reqkey-spring-boot-starter as appropriate. Framework APIs are kept out of the
core artifact.
HttpReqKeyClient is thread-safe and should normally be created once and
reused for the life of the process. It uses java.net.http.HttpClient; the only
runtime dependency in core is Jackson for JSON.
import com.reqkey.HttpReqKeyClient;
import com.reqkey.ReqKeyConfig;
import com.reqkey.ValidationRequest;
import com.reqkey.VerificationResult;
ReqKeyConfig config = ReqKeyConfig.builder(System.getenv("REQKEY_PROJECT_KEY"))
.build();
try (HttpReqKeyClient reqKey = new HttpReqKeyClient(config)) {
VerificationResult decision = reqKey.verify(
ValidationRequest.builder("consumer_key_...")
.apiId("api_payments")
.credits(2)
.resource("/payments")
.build()
);
if (!decision.isAllowed()) {
System.out.println("Denied: " + decision.getReason().value());
}
}ReqKeyConfig.fromEnvironment() reads REQKEY_PROJECT_KEY and falls back to
the legacy REQKEY_ROOT_KEY variable. Use
ReqKeyConfig.builderFromEnvironment() when environment credentials should be
combined with custom timeouts, base URLs, or retry settings.
Direct ingestion accepts request and response metadata:
reqKey.ingest(AnalyticsEvent.builder()
.requestId(decision.getRequestId())
.apiId("api_payments")
.method("POST")
.endpoint("/payments")
.path("/payments?currency=USD")
.statusCode(201)
.latencyMs(38L)
.consumerId("consumer_123")
.build());Ingestion requires a request ID, an API ID, or both. Request and response bodies are truncated to 1,000 Unicode characters before transmission.
Add reqkey-spring-boot-starter and configure application.yml:
reqkey:
project-key: ${REQKEY_PROJECT_KEY}
api-id: api_payments
mode: both
enabled: true
key-location: header
key-name: X-StartupName-Key
key-scheme: raw
credits: 1
exclude-paths:
- /health
- /docs/*
skip-methods:
- OPTIONS
failure-mode: closed
timeout: 2s
connect-timeout: 2s
capture-query-parameters: false
capture-request-headers: false
capture-response-headers: false
capture-response-body: false
capture-client-ip: falseThe starter registers one Servlet filter. A controller can access a successful decision through request attributes:
@PostMapping("/payments")
Map<String, Object> create(HttpServletRequest request) {
VerificationResult decision = (VerificationResult)
request.getAttribute(ReqKeyAttributes.DECISION);
return Map.of(
"created", true,
"creditsRemaining", decision.getCreditsRemaining()
);
}The related attribute names are:
ReqKeyAttributes.DECISIONReqKeyAttributes.REQUEST_IDReqKeyAttributes.ERRORfor a fail-open validation service error
The starter backs off when reqkey.enabled=false or reqkey.api-id is absent.
It also backs off from any user-provided ReqKeyClient,
ReqKeyMiddlewareConfig, ReqKeyProcessor, or named filter registration,
making advanced customization possible through ordinary Spring beans.
Register the filter programmatically or through your container's normal filter registration mechanism:
ReqKeyMiddlewareConfig middleware = ReqKeyMiddlewareConfig.builder("api_payments")
.keyName("X-StartupName-Key")
.credits(request -> request.getPath().equals("/reports") ? 5 : 1)
.excludePath("/health")
.build();
ReqKeyFilter filter = new ReqKeyFilter(
ReqKeyConfig.fromEnvironment(),
middleware
);
FilterRegistration.Dynamic registration = servletContext.addFilter("reqkey", filter);
registration.addMappingForUrlPatterns(
EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC),
false,
"/*"
);The filter supports synchronous and asynchronous Servlet requests. Analytics for async requests runs from the completion listener rather than when the initial dispatcher thread returns. Text response capture is bounded and streamed through to the real response; compressed response bodies are not captured.
Register one feature through your JAX-RS runtime. A portable provider wrapper
works with classpath scanning and Application.getClasses():
@Provider
public final class PaymentsReqKeyFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
ReqKeyMiddlewareConfig middleware = ReqKeyMiddlewareConfig.builder("api_payments")
.keyName("Authorization")
.keyScheme(KeyScheme.BEARER)
.excludePath("/health")
.build();
return new ReqKeyFeature(ReqKeyConfig.fromEnvironment(), middleware)
.configure(context);
}
}The feature installs an authentication-priority request filter and a response
header/analytics filter. Portable JAX-RS response filters run before entity
serialization, so response-body analytics are captured only when the response
entity is already a String. Status, latency, headers, request ID, API ID,
method, endpoint, path, user agent, and consumer identity remain available for
all response entity types.
Every adapter uses ReqKeyMiddlewareConfig.
| Builder option | Default | Purpose |
|---|---|---|
mode(Mode) |
BOTH |
VALIDATE, INGEST, or correlated BOTH |
enabled(boolean) |
true |
Bypass all ReqKey work when false |
keyLocation(KeyLocation) |
HEADER |
HEADER, QUERY, or COOKIE |
keyName(String) |
X-API-Key |
Incoming header, query parameter, or cookie name |
keyScheme(KeyScheme) |
RAW |
Raw value or an Authorization-style Bearer value |
apiKeyExtractor(...) |
— | Custom extraction for nonstandard request sources |
credits(int) |
1 |
Static credit cost |
credits(CreditResolver) |
— | Request-aware dynamic credit cost |
excludePath(String) |
none | Exact path or trailing-* prefix pattern |
skipMethods(Collection) |
OPTIONS |
Methods excluded from validation and analytics |
protectWhen(Predicate) |
all selected paths | Complete request-selection override |
endpointResolver(Function) |
request path | Resource/endpoint identifier sent to ReqKey |
requestIdResolver(Function) |
— | Correlation ID for ingest-only mode |
consumerNameResolver(Function) |
— | Optional trusted display name for analytics |
clientIpResolver(Function) |
peer/fallback | Trusted proxy-specific client IP override |
onError(MiddlewareErrorHandler) |
no-op | Safe validation/ingestion service failure callback |
ingestDeniedRequests(boolean) |
true |
Record missing, invalid, exhausted, forbidden, and rate-limited requests in BOTH mode |
failureMode(FailureMode) |
CLOSED |
Deny or allow when ReqKey itself is unavailable |
errorMessage(...) |
built in | Customer-facing denial message override |
captureQueryParameters(boolean) |
false |
Include sanitized query values and path query string |
captureRequestHeaders(boolean) |
false |
Include sanitized request headers |
captureResponseHeaders(boolean) |
false |
Include sanitized response headers |
captureResponseBody(boolean) |
false |
Include up to 1,000 textual characters where supported |
captureClientIp(boolean) |
false |
Include peer/client IP metadata |
captureUserAgent(boolean) |
true |
Include the User-Agent value |
excludedHeader(s) |
sensitive defaults | Add headers that must never be captured |
loggingEnabled(boolean) |
true |
Enable SDK diagnostic logging |
logger(ReqKeyLogger) |
java.util.logging |
Route SDK diagnostics into another logging system |
logSuccessfulValidations(boolean) |
false |
Emit debug-level success decisions |
Query and cookie locations intentionally accept only KeyScheme.RAW.
ReqKeyMiddlewareConfig config = ReqKeyMiddlewareConfig.builder("api_images")
.apiKeyExtractor(request -> request.firstHeader("X-Custom-Key"))
.credits(request -> {
if (request.getMethod().equals("POST") && request.getPath().equals("/images")) {
return 5;
}
return request.getPath().startsWith("/reports/") ? 2 : 1;
})
.endpointResolver(request -> request.getPath())
.build();Credit costs must be non-negative. Invalid static or dynamic values fail before the network call.
In BOTH mode the adapters:
- Select the request and extract the consumer key.
- Call
/key/validateand atomically deduct the configured credits. - Stop missing, invalid, exhausted, forbidden, or rate-limited requests.
- Expose an allowed decision to the application.
- Collect bounded response metadata and call
/ingestwith the validation request ID.
Validation fails closed by default. FailureMode.OPEN allows the application
to run only when ReqKey has a transport, timeout, authentication, or service
failure. Explicit access decisions are always enforced.
Ingestion failures never replace a completed application response. The
MiddlewareErrorHandler receives a provider-neutral event containing the
operation, method, path, request ID, response status, and typed error. It never
contains project credentials, consumer API keys, query values, headers, or
bodies.
RetryPolicy retry = RetryPolicy.builder()
.maxAttempts(3)
.initialBackoff(Duration.ofMillis(100))
.maxBackoff(Duration.ofSeconds(1))
.retryIngestion(true)
.retryValidation(false)
.retryServerErrors(true)
.build();
ReqKeyConfig clientConfig = ReqKeyConfig.builder(projectKey)
.timeout(Duration.ofSeconds(2))
.connectTimeout(Duration.ofSeconds(2))
.retryPolicy(retry)
.build();The default is one attempt. Ingestion may use the configured retry count. Validation retries remain disabled unless explicitly enabled because a validation request can deduct credits and safe replay requires server-side idempotency. Retries are bounded, use exponential backoff, and apply to transport failures, timeouts, and optionally HTTP 5xx responses.
Spring Boot exposes the same settings under reqkey.retry:
reqkey:
retry:
max-attempts: 3
initial-backoff: 100ms
max-backoff: 1s
ingestion: true
validation: false
server-errors: trueDirect client service failures use typed unchecked exceptions:
ReqKeyConfigurationExceptionReqKeyTransportExceptionReqKeyTimeoutExceptionReqKeyApiExceptionReqKeyAuthenticationException
Normal consumer denials are VerificationResult values rather than
exceptions. Stable reasons are VALID, INVALID_KEY,
INSUFFICIENT_CREDITS, FORBIDDEN, RATE_LIMITED, and DENIED.
Framework denial responses contain a stable JSON error identifier and a
customizable message. Rate-limited responses preserve Retry-After.
Successful framework responses include available validation metadata:
X-ReqKey-Request-IDX-ReqKey-Credits-LimitX-ReqKey-Credits-RemainingX-ReqKey-Validation-Time-Ms
The default event contains the request ID, API ID, method, normalized endpoint,
response status, latency, user agent, and consumer key in the dedicated
apiKey identity field. Query values, captured headers, response bodies, and
client IPs are opt-in.
Authorization, cookies, Set-Cookie, proxy authorization, common API-key
headers, and the configured consumer-key header are always removed from
captured headers. When a key comes from the query string, that parameter is
removed from both queryParams and the recorded path.
Only trust forwarding headers through clientIpResolver when your deployment
proxy overwrites them. Without an explicit resolver, framework-provided peer
addresses take precedence and common proxy headers are only a fallback when no
peer address exists.
Run all unit and framework integration tests:
./mvnw verifyRun one module and its dependencies:
./mvnw -pl reqkey-servlet -am test
./mvnw -pl reqkey-jaxrs -am test
./mvnw -pl reqkey-spring-boot-starter -am testThe test suite covers the live HTTP contract through an in-process server, header/query/cookie and Bearer extraction, dynamic credit costs, decision and error mapping, body limits, timeout behavior, retry safety, fail-open and fail-closed handling, analytics redaction, and each framework adapter.
The parent POM includes project metadata, SCM coordinates, license details,
and a release profile that attaches source and Javadoc JARs:
./mvnw -Prelease clean verifyRepository credentials, signing keys, and a Maven Central publishing plugin are intentionally not embedded. Those organization-specific settings can be added when the first public release is ready without changing SDK consumers.
See the complete examples in reqkey-examples.