Java
One dependency, one connection. Microsecond reads from L1 cache.
Install
<dependency>
<groupId>com.goldlapel</groupId>
<artifactId>goldlapel</artifactId>
<version>0.2.0</version>
</dependency>
<!-- PostgreSQL JDBC driver (hardcoded by the wrapper, listed here for clarity) -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency> Gold Lapel's Java wrapper hardcodes the PostgreSQL JDBC driver — it's the only one JDBC speaks natively to Postgres, and the wrapper's transactional helpers need a real JDBC Connection. You're free to use any higher-level layer (JPA, Hibernate, jOOQ, Spring Data) on top.
Quick Start
import com.goldlapel.GoldLapel;
import com.goldlapel.GoldLapelOptions;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Map;
import java.util.Properties;
// try-with-resources auto-stops the proxy on scope exit
try (GoldLapel gl = GoldLapel.start("postgresql://user:pass@localhost:5432/mydb", opts -> {
opts.setProxyPort(7932);
opts.setLogLevel("info"); // trace | debug | info | warn | error
})) {
// gl.getUrl() is the driver-agnostic proxy URL.
// Java's JDBC driver rejects inline userinfo, so for JDBC use these helpers:
Properties props = new Properties();
props.setProperty("user", gl.getJdbcUser());
props.setProperty("password", gl.getJdbcPassword());
Connection conn = DriverManager.getConnection(gl.getJdbcUrl(), props);
// Wrapper methods — no connection argument needed
var hits = gl.search("articles", "body", "postgres tuning");
gl.docInsert("events", Map.of("type", "signup"));
} Repeated reads serve in microseconds from the built-in L1 cache.
After startup, Gold Lapel prints a one-line summary to stderr and serves a dashboard:
gl.getDashboardUrl()
// => "http://127.0.0.1:7933" (or null if not running / dashboard disabled) Banner goes to stderr so it never pollutes stdout-piped application output. Pass opts.setSilent(true) to suppress it entirely — useful for daemons and structured-log pipelines that inspect both streams.
gl.getUrl() is the driver-agnostic proxy URL (e.g. for passing to other tools). For JDBC itself, use gl.getJdbcUrl() along with gl.getJdbcUser() and gl.getJdbcPassword() — JDBC rejects inline userinfo in connection strings, so credentials go in Properties.
Transactional coordination
When you want wrapper methods to run inside your own transaction, pass your connection via gl.using(conn, Runnable) (scoped) or as the last argument to any wrapper method (per call):
// Scoped transactional coordination — all wrapper calls in the lambda use conn.
// Backed by a ThreadLocal, so nested helper methods on the same thread pick it up.
try (Connection conn = DriverManager.getConnection(gl.getJdbcUrl(), props)) {
conn.setAutoCommit(false);
gl.using(conn, () -> {
gl.docInsert("events", Map.of("type", "order.created"));
gl.incr("counters", "orders");
});
conn.commit();
} For a single call, use the Connection overload:
// Every wrapper method has an overload taking Connection as the last argument
gl.docInsert("events", Map.of("type", "x"), conn); Scope is ThreadLocal-backed for the core sync API. For reactive code, use the goldlapel-reactor Maven artifact (Project Reactor / R2DBC with ContextView) or goldlapel-rxjava3 for RxJava3.
Footgun — using() is ThreadLocal-scoped. The scope attaches to the thread calling using(...) and does not propagate across thread boundaries. Wrapper calls that run on a different thread will not see the scoped connection — this includes ExecutorService.submit(...), CompletableFuture.supplyAsync(...), parallelStream(), and any framework task scheduler that hops threads. If your work fans out to a worker pool, pass conn explicitly as the last argument to each wrapper call instead of relying on using(...).
// DON'T — the supplyAsync body runs on a ForkJoin worker that
// never entered the using() scope.
gl.using(tx, () -> {
CompletableFuture.supplyAsync(() -> gl.docInsert("events", "{}"));
});
// DO — pass tx explicitly.
CompletableFuture.supplyAsync(() -> gl.docInsert("events", "{}", tx)); gl.script(String luaCode, String... args) has no Connection overload — the trailing String... varargs would swallow a Connection passed as the last argument. To run script against a specific connection, wrap it in using:
gl.using(conn, () -> gl.script("return 1", "x")); API
GoldLapel.start(upstream, Consumer<GoldLapelOptions>)
Static factory that spawns the proxy and returns a GoldLapel instance. Eagerly opens the wrapper's internal JDBC connection so wrapper methods are fast from the first call. GoldLapel implements AutoCloseable — use with try-with-resources for deterministic cleanup.
upstream— your Postgres connection string (e.g.postgresql://user:pass@localhost:5432/mydb)- Options consumer (all setters on
GoldLapelOptions):setProxyPort(int)— proxy port (default: 7932)setDashboardPort(Integer)— dashboard port (default:proxyPort + 1; set to0to disable)setInvalidationPort(Integer)— cache-invalidation port (default:proxyPort + 2)setLogLevel(String)—trace/debug/info/warn/error(string-based; translated to the binary's verbose flags internally)setMode(String)— operating mode (waiter,bellhop)setLicense(String)— path to the signed license filesetClient(String)— client identifier for telemetry tagging (defaults to"java")setConfigFile(String)— path to a TOML config file read by the Rust binarysetConfig(Map)— camelCase map of structured tuning knobs (see Configuration)setExtraArgs(String...)— raw CLI flags passed to the binarysetSilent(boolean)— suppress the stderr startup banner (default:false)setMesh(boolean)— opt into the mesh at startup (HQ enforces the license; denial is non-fatal)setMeshTag(String)— optional mesh tag; instances sharing a tag cluster together
gl.getUrl()
Driver-agnostic proxy URL string (postgresql://user:pass@127.0.0.1:7932/mydb).
gl.getJdbcUrl() / gl.getJdbcUser() / gl.getJdbcPassword()
JDBC-specific helpers — getJdbcUrl() returns a jdbc:postgresql://... URL without inline userinfo, and the two accessor methods return the username and password for a Properties bag. Required because the JDBC driver rejects userinfo embedded in the URL.
gl.getDashboardUrl()
Dashboard URL (e.g. http://127.0.0.1:7933), or null if the dashboard is disabled or the proxy is not running.
gl.using(conn, Runnable)
Runs the given Runnable with conn as the implicit connection for any wrapper method invoked inside. Backed by a ThreadLocal. Every wrapper method also has an overload that accepts Connection as the last argument for one-off overrides.
gl.close() / gl.stop()
Stops the proxy and closes the internal JDBC connection. Idempotent. close() is the AutoCloseable method — stop() is an alias. Also runs automatically on JVM shutdown.
GoldLapel.configKeys()
Returns the set of all valid config key names.
System.out.println(GoldLapel.configKeys()); Multiple instances
Each start() call spawns its own proxy subprocess and returns a fresh instance. Use different ports to run several side by side.
// Each start() call spawns its own proxy subprocess and returns a fresh instance
GoldLapel gl1 = GoldLapel.start("postgresql://user:pass@localhost:5432/app_db",
opts -> opts.setProxyPort(7932));
GoldLapel gl2 = GoldLapel.start("postgresql://user:pass@localhost:5432/analytics_db",
opts -> opts.setProxyPort(7942));
// Each instance manages its own proxy and cache
gl1.close();
gl2.close(); Configuration
Pass a config Map via opts.setConfig(...). Keys use camelCase and map to CLI flags (poolSize → --pool-size). The setLogLevel option accepts string levels and translates to the binary's verbose flags internally.
GoldLapel gl = GoldLapel.start("postgresql://user:pass@localhost/mydb", opts -> {
opts.setProxyPort(7932);
opts.setLogLevel("info"); // top-level
opts.setMode("waiter"); // top-level (promoted out of config)
opts.setMesh(true); // top-level: opt into the mesh at startup
opts.setMeshTag("prod-east"); // top-level: optional mesh tag
opts.setConfig(Map.of( // structured tuning knobs only
"poolSize", 50,
"disableMatviews", true,
"replica", java.util.List.of("postgresql://user:pass@replica1/mydb")
));
}); Unknown keys throw IllegalArgumentException immediately. See the configuration reference for every available key.
Raw CLI flags
You can also pass raw CLI flags via extraArgs:
GoldLapel gl = GoldLapel.start("postgresql://user:pass@localhost:5432/mydb", opts -> {
opts.setExtraArgs("--threshold-duration-ms", "200", "--refresh-interval-secs", "30");
}); Environment variables
The binary also reads GOLDLAPEL_PROXY_PORT, GOLDLAPEL_UPSTREAM, and all other GOLDLAPEL_* env vars automatically. Set GOLDLAPEL_BINARY to override the binary location.
Framework integrations
The Java wrapper speaks plain JDBC, so any framework that wraps JDBC (Hibernate, JPA, jOOQ, Spring Data, MyBatis) works against gl.getJdbcUrl() with matching Properties.
- Spring Boot — install the
goldlapel-spring-bootartifact alongsidegoldlapelfor auto-configuration (@Bean GoldLapel,DataSourcewiring, property-driven config). See the Spring Boot guide.
<dependency>
<groupId>com.goldlapel</groupId>
<artifactId>goldlapel-spring-boot</artifactId>
<version>0.2.0</version>
</dependency> Reactive — Project Reactor / R2DBC
The goldlapel-reactor artifact exposes a Project Reactor + R2DBC flavour — full parity with the sync API, returning Mono<T> / Flux<T> instead of blocking values. Use it in Spring WebFlux, reactor-based apps, or anywhere you need non-blocking I/O. The sync goldlapel artifact is untouched — reactive users pull R2DBC + Reactor through this additional dependency.
<dependency>
<groupId>com.goldlapel</groupId>
<artifactId>goldlapel-reactor</artifactId>
<version>0.2.0</version>
</dependency> Quick start
import com.goldlapel.reactor.ReactiveGoldLapel;
import reactor.core.publisher.Mono;
Mono<Long> pipeline = ReactiveGoldLapel.start("postgresql://user:pass@db/mydb")
.flatMap(gl ->
gl.docInsert("events", "{\"type\":\"signup\"}")
.then(gl.docCount("events", "{}"))
.flatMap(count -> gl.stop().thenReturn(count))
);
pipeline.subscribe(count -> System.out.println("Total: " + count)); Cancelling the Mono<ReactiveGoldLapel> mid-spawn tears the subprocess down — no leaks.
using(conn) — scope a connection over a Mono chain
The reactive using() propagates a JDBC Connection through Reactor Context (not ThreadLocal), so it survives flatMap boundaries and scheduler hops. Nested using blocks restore the outer connection on the way out — standard Context semantics.
try (java.sql.Connection myConn = dataSource.getConnection()) {
gl.using(myConn, g ->
g.docInsert("events", json)
.then(g.docCount("events", "{}"))
).block();
} Raw R2DBC queries against the proxy
For your own non-blocking queries, gl.connectionFactory() returns an io.r2dbc.spi.ConnectionFactory wired to the proxy — plug it into Spring Data R2DBC's DatabaseClient, jOOQ-R2DBC, or raw R2DBC:
Flux<Long> ids = Mono.from(gl.connectionFactory().create())
.flatMapMany(conn ->
Flux.from(conn.createStatement("SELECT id FROM users WHERE active").execute())
.flatMap(r -> r.map((row, meta) -> row.get(0, Long.class)))
.concatWith(Mono.from(conn.close()).cast(Long.class))
); The ~61 wrapper helpers (search, docFind, incr, etc.) bridge through JDBC on Schedulers.boundedElastic() — Reactor's canonical pattern for running blocking code inside a reactive pipeline. That keeps the wire semantics identical to the sync API. The reactive value is in the R2DBC ConnectionFactory above: your own queries go end-to-end non-blocking.
Reactive — RxJava 3
The goldlapel-rxjava3 artifact exposes the same reactive surface with RxJava 3 return types — Single / Flowable / Completable / Maybe — for codebases already standardised on RxJava. Pulls in goldlapel-reactor + io.reactivex.rxjava3:rxjava transitively; no conflict with a plain sync or plain reactive install, but typically you'll depend on only one of the three flavours.
<dependency>
<groupId>com.goldlapel</groupId>
<artifactId>goldlapel-rxjava3</artifactId>
<version>0.2.0</version>
</dependency> Quick start
import com.goldlapel.rxjava3.RxJavaGoldLapel;
import io.reactivex.rxjava3.core.Single;
Single<Long> pipeline = RxJavaGoldLapel.start("postgresql://user:pass@db/mydb")
.flatMap(gl ->
gl.docInsert("events", "{\"type\":\"signup\"}")
.flatMap(ignored -> gl.docCount("events", "{}"))
.flatMap(count -> gl.stop().toSingleDefault(count))
);
pipeline.subscribe(count -> System.out.println("Total: " + count)); Type mapping
Reactor (goldlapel-reactor) | RxJava 3 (goldlapel-rxjava3) |
|---|---|
Mono<T> (always emits) | Single<T> |
Mono<T> (may be empty — hget, docFindOne, zscore, etc.) | Maybe<T> |
Mono<Void> | Completable |
Flux<T> | Flowable<T> |
Nullable lookups return Maybe<T> so "not found" is a clean onComplete with no value, rather than a NoSuchElementException.
Scoping connections
RxJava has no ContextView equivalent of Reactor, so there's no using(conn, body) method on RxJavaGoldLapel. Two options:
1. Per-call explicit Connection — every wrapper method has an overload taking a final Connection conn argument:
gl.docInsert("events", json, myConn)
.flatMap(ignored -> gl.docCount("events", "{}", myConn))
.subscribe(); 2. Drop into the Reactor API for a chain — gl.reactor() returns the underlying ReactiveGoldLapel, which supports Reactor-Context scoping:
gl.reactor().using(myConn, g ->
g.docInsert("events", json).then(g.docCount("events", "{}"))
).subscribe(); Raw R2DBC queries
Same as the Reactor module — gl.connectionFactory() returns an R2DBC ConnectionFactory pointing at the proxy. Mix with io.reactivex.rxjava3.core.Flowable.fromPublisher to pull rows back into RxJava types.
Upgrading from v0.1
v0.2 is a breaking redesign. The class-based new GoldLapel(url).start() shape is replaced by a GoldLapel.start(url, opts -> ...) static factory that returns a GoldLapel instance implementing AutoCloseable. Bring your own JDBC driver and point it at gl.getJdbcUrl().
// v0.1.x (old) — constructor + start()/stop() returned a Connection
GoldLapel gl = new GoldLapel("postgresql://...");
Connection conn = gl.start();
conn.createStatement().executeQuery("SELECT 1");
gl.stop();
// v0.2 (new) — static factory, bring your own driver via gl.getJdbcUrl()
try (GoldLapel gl = GoldLapel.start("postgresql://...", opts -> opts.setProxyPort(7932))) {
Properties props = new Properties();
props.setProperty("user", gl.getJdbcUser());
props.setProperty("password", gl.getJdbcPassword());
Connection conn = DriverManager.getConnection(gl.getJdbcUrl(), props);
conn.createStatement().executeQuery("SELECT 1");
} GoldLapel.start(url, opts -> ...)returns aGoldLapelinstance (previously the constructor returned the instance andstart()returned aConnection).GoldLapelimplementsAutoCloseable— prefer try-with-resources.- New
gl.getJdbcUrl()/gl.getJdbcUser()/gl.getJdbcPassword()helpers for JDBC (which rejects inline userinfo). - All wrapper methods now have a
Connection-less overload and aConnection-taking overload, andgl.using(conn, Runnable)provides scoped override. - Multiple instances are first-class — each
start()call spawns its own proxy. - Sync only in v0.2; reactive Java support is deferred.
Redis Replacement
Gold Lapel includes a Redis Replacement API — common Redis patterns backed by PostgreSQL. Every method hangs directly off gl and has a Connection-taking overload for transactional coordination.
Pub/Sub
// Pub/Sub
gl.publish("orders", "new order");
gl.subscribe("orders", msg -> System.out.println(msg)); Queues
// Queues
gl.enqueue("jobs", Map.of("task", "send_email"));
var job = gl.dequeue("jobs"); Counters
// Counters
gl.incr("page_views", "home");
long count = gl.getCounter("page_views", "home"); Sorted Sets
// Sorted Sets
gl.zadd("leaderboard", "player1", 100);
var top = gl.zrange("leaderboard", 0, 9); See the Redis Replacement docs for the complete API — including geospatial, rate limiting, sessions, and scripting.
Document Store
docFind, docInsert, docUpdate, docDelete and friends operate on JSONB-backed collections. Tables are auto-created on first use.
Filter operators
docFind supports the MongoDB filter operators you'd reach for — $elemMatch, $text, $gt, $in, and more.
// $elemMatch — scope multi-condition filters to a single array element
var orders = gl.docFind("orders", Map.of(
"items", Map.of("$elemMatch",
Map.of("sku", "ABC-123", "qty", Map.of("$gte", 2)))
)); // $text — full-text search, document-wide or field-scoped
var hits = gl.docFind("articles", Map.of(
"$text", Map.of("$search", "postgres tuning")
)); See Appendix D: Filter Operator Reference for the full list, Postgres translations, and index notes.
Search
Full-text search utilities backed by PostgreSQL tsvector/tsquery. No extensions required.
Facets
// Facets — value counts, optionally filtered by a search query
var results = gl.facets("articles", "category");
var filtered = gl.facets("articles", "category",
new FacetsOptions().query("machine learning").queryColumn("body")); Aggregations
// Aggregations — count, sum, avg, min, max with optional grouping
var byRegion = gl.aggregate("orders", "total", "avg",
new AggregateOptions().groupBy("region")); Custom Search Config
// Custom search config
gl.createSearchConfig("my_english", "english"); Percolator
Store queries, then match documents against them. Like Elasticsearch percolate.
// Percolator — store queries, match documents against them
gl.percolateAdd("alerts", "breaking-news",
"breaking news earthquake", Map.of("notify", "slack"));
var matches = gl.percolate("alerts",
"A 6.2 magnitude earthquake struck the coast.");
gl.percolateDelete("alerts", "breaking-news"); Analyze
// Analyze — show tokenization pipeline
var tokens = gl.analyze("The quick brown foxes"); Explain Score
// Explain score — score breakdown for a specific document
var result = gl.explainScore("articles", "body",
"machine learning", "id", 42); See the API reference for full parameter details on all search methods.