JRedis is a small educational, Redis-compatible in-memory key-value server written in Java 21. It implements a useful slice of Redis over RESP2 so you can study request framing, command validation, expiration, concurrency, and append-only recovery. It is not a complete Redis implementation and does not claim full Redis compatibility.
- Java 21 or newer
- Maven 3.9+ (or use the included Maven Wrapper)
redis-cliis optional and only needed for the example client session
From this directory:
./mvnw test
./mvnw package
java -jar target/jredis-1.0.0.jarOn Windows PowerShell, run .\mvnw.cmd test, .\mvnw.cmd package, and then java -jar target\jredis-1.0.0.jar.
The server listens on TCP port 6379 by default. It prints the bound port when it starts. Example options:
java -jar target/jredis-1.0.0.jar --port 6380
java -jar target/jredis-1.0.0.jar --aof
java -jar target/jredis-1.0.0.jar --aof data/jredis.aof --port 6380
java -jar target/jredis-1.0.0.jar --no-aof--aof enables append-only persistence and accepts an optional file path; the default is appendonly.aof in the current working directory. AOF is disabled by default. --no-aof disables it. Port 0 asks the operating system for an available port, which is useful in embedded use; the selected port is printed at startup.
Start JRedis in one terminal, then connect from another:
redis-cli -p 6379
127.0.0.1:6379> PING
PONG
127.0.0.1:6379> SET greeting "hello"
OK
127.0.0.1:6379> GET greeting
"hello"
127.0.0.1:6379> SET short lived PX 15000
OK
127.0.0.1:6379> PTTL short
(integer) 14999The PTTL number decreases while you read the example. You can also issue one command without entering the interactive prompt, for example redis-cli -p 6379 SET count 1.
Command names are case-insensitive. Keys and values are UTF-8 strings.
| Command | Behavior |
|---|---|
PING [message] |
Returns PONG, or the supplied message as a bulk string. |
ECHO message |
Returns the message. |
| `SET key value [NX | XX] [EX seconds |
GET key |
Returns the string or a null bulk string. |
DEL key [key ...] |
Removes keys; returns how many existed. |
EXISTS key [key ...] |
Returns the number of supplied keys that exist. |
INCR key, DECR key |
Increments/decrements a signed 64-bit decimal integer; a missing key starts at zero. Replies are RESP integers. |
EXPIRE key seconds, PEXPIRE key milliseconds |
Sets a relative expiry; a non-positive duration deletes an existing key. |
TTL key, PTTL key |
Returns remaining seconds/milliseconds. |
PERSIST key |
Removes expiry from an existing expiring key. |
TYPE key |
Returns string or none. |
FLUSHDB |
Removes all keys in the only database. |
Expiry deadlines are measured using Java's monotonic clock, so wall-clock adjustments do not change an in-process timeout. Expired keys behave as missing keys and are removed when accessed. TTL and PTTL round remaining time down; -1 means the key exists without an expiry and -2 means it does not exist. INCR and DECR reject non-decimal values and signed 64-bit overflow with an error, and leave the value unchanged on those failures.
When enabled, successful state-changing commands are appended as RESP arrays to the configured AOF. Read-only commands and commands rejected by validation/conditions are not recorded. The file is replayed before the listener starts. JRedis flushes Java's buffered stream at each command boundary, but does not call fsync; the operating system may still have data in its cache. An abrupt process termination during an append can leave a truncated final frame, which JRedis currently reports as a malformed AOF instead of silently ignoring or repairing it. A power loss can lose recently acknowledged writes. Expiration commands are logged in their relative form, so restarting replays their duration from startup and may extend a key's life by the downtime. AOF is a teaching implementation, not a production durability guarantee; there is no rewrite, snapshot, replication, or repair tool.
protocol: bounded RESP2 reader and response writercommand: command arity/options validation and executionstorage: synchronized in-memory string map and monotonic expirypersistence: RESP command append logserver: socket listener, client workers, startup replay, and configuration
Each command executes through a synchronized processor, which gives the small implementation a clear atomic ordering across clients and in the AOF. Client connections run concurrently, but commands are serialized globally. This favors understandable semantics over high throughput.
JRedis currently supports only RESP2 and the commands listed above. It has one logical database, stores only strings, and has no authentication, ACLs, TLS, Pub/Sub, transactions, Lua, streams, lists, sets, hashes, sorted sets, cluster/sentinel support, replication, SELECT, MSET, MGET, INCRBY, DECRBY, EXPIREAT, or broad Redis option/command coverage. It does not implement Redis's complete error wording, inline protocol, RESP3, client tracking, eviction policies, memory limits, or production AOF rewrite/fsync behavior. A single global command lock limits parallel command execution. Treat compatibility as limited to the documented MVP commands and their common RESP2 interactions.