Zero-dependency Java SDK for PeekAPI. Jakarta Servlet Filter for any servlet container, with Spring Boot auto-configuration via application.properties.
Gradle (Kotlin DSL):
dependencies {
implementation("dev.peekapi:peekapi:0.1.0")
}Maven:
<dependency>
<groupId>dev.peekapi</groupId>
<artifactId>peekapi</artifactId>
<version>0.1.0</version>
</dependency>Add your API key to application.properties — the filter registers automatically on all routes:
peekapi.api-key=ak_live_xxxOptional properties:
peekapi.endpoint=https://ingest.peekapi.dev/v1/events
peekapi.debug=false
peekapi.collect-query-string=false
peekapi.flush-interval-seconds=15
peekapi.batch-size=250Register the filter bean manually for full control over options:
import dev.peekapi.PeekApiClient;
import dev.peekapi.PeekApiOptions;
import dev.peekapi.middleware.PeekApiFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class PeekApiConfig {
@Bean
public PeekApiClient peekApiClient() {
PeekApiOptions options = PeekApiOptions.builder("ak_live_xxx")
.debug(true)
.build();
return new PeekApiClient(options);
}
@Bean
public FilterRegistrationBean<PeekApiFilter> peekApiFilter(PeekApiClient client) {
PeekApiOptions options = PeekApiOptions.builder("ak_live_xxx").build();
FilterRegistrationBean<PeekApiFilter> reg = new FilterRegistrationBean<>();
reg.setFilter(new PeekApiFilter(client, options));
reg.addUrlPatterns("/*");
return reg;
}
}<filter>
<filter-name>peekapi</filter-name>
<filter-class>dev.peekapi.middleware.PeekApiFilter</filter-class>
<init-param>
<param-name>apiKey</param-name>
<param-value>ak_live_xxx</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>peekapi</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>import dev.peekapi.PeekApiClient;
import dev.peekapi.PeekApiOptions;
import dev.peekapi.RequestEvent;
PeekApiOptions options = PeekApiOptions.builder("ak_live_xxx").build();
PeekApiClient client = new PeekApiClient(options);
client.track(new RequestEvent("GET", "/api/users", 200, 42.0, 0, 1024, "consumer_123", null));
// Graceful shutdown (flushes remaining events)
client.shutdown();| Builder method | Type | Default | Description |
|---|---|---|---|
apiKey (required) |
String |
— | Your PeekAPI API key |
endpoint |
String |
https://ingest.peekapi.dev/v1/events |
Ingestion endpoint URL |
flushInterval |
Duration |
15s |
Time between automatic flushes |
batchSize |
int |
250 |
Events per batch (triggers flush when reached) |
maxBufferSize |
int |
10,000 |
Maximum events held in memory |
maxStorageBytes |
long |
5 MB |
Maximum disk fallback file size |
maxEventBytes |
int |
64 KB |
Per-event size limit (drops if exceeded) |
storagePath |
String |
temp dir | Path for JSONL fallback file |
debug |
boolean |
false |
Enable debug logging to stderr |
collectQueryString |
boolean |
false |
Include sorted query params in path |
identifyConsumer |
Function<HttpServletRequest, String> |
auto | Custom consumer identification callback |
onError |
Consumer<Exception> |
null |
Callback for background flush errors |
sslContext |
SSLContext |
null |
Custom TLS/SSL configuration |
- Your application handles requests through the servlet filter (or you call
client.track()directly) - The filter captures method, path, status code, response time, request/response size, and consumer ID
- Events are buffered in memory and flushed every 15 seconds (or when
batchSizeis reached) via a daemon thread backed byScheduledExecutorService - Batches are sent as JSON arrays to the PeekAPI ingest endpoint using
java.net.http.HttpClient - On failure, events are re-inserted into the buffer with exponential backoff (up to 5 retries)
- After max retries, events are persisted to a JSONL file on disk and recovered on the next flush cycle
- On JVM shutdown, a shutdown hook flushes remaining events and persists any leftovers to disk
By default, consumers are identified by:
X-API-Keyheader — stored as-isAuthorizationheader — hashed with SHA-256 (stored ashash_<hex>)
Override with the identifyConsumer option to use any header or request attribute:
PeekApiOptions options = PeekApiOptions.builder("ak_live_xxx")
.identifyConsumer(req -> req.getHeader("X-Tenant-ID"))
.build();The callback receives an HttpServletRequest and should return a consumer ID string or null.
- Zero runtime dependencies — built on
java.net.httpandjava.security(JDK 17+) - Jakarta Servlet Filter — drop-in for any servlet container (Tomcat, Jetty, Undertow)
- Spring Boot auto-configuration — just set
peekapi.api-keyinapplication.properties - Background flush — daemon thread via
ScheduledExecutorService, never blocks your requests - Disk persistence — undelivered events saved to JSONL, recovered automatically on next startup
- Exponential backoff — retries with jitter on transient failures, gives up after 5 consecutive errors
- SSRF protection — validates endpoint host against private IP ranges at construction time
- Input sanitization — path (2048), method (16), consumer ID (256) character limits enforced
- Per-event size limit — events exceeding
maxEventBytesare dropped (metadata stripped first) - Graceful shutdown — JVM shutdown hook flushes buffer and persists remaining events to disk
- Java >= 17
- Jakarta Servlet API >= 6.0 (provided by your servlet container or Spring Boot 3.x)
Bug reports and feature requests: peekapi-dev/community
- Fork & clone the repo
- Run tests —
./gradlew test - Lint —
./gradlew spotlessCheck - Format —
./gradlew spotlessApply - Submit a PR
If you find PeekAPI useful, give this repo a star — it helps others discover the project.
Show that your API is monitored by PeekAPI:
[](https://peekapi.dev)MIT