Skip to content

Repository files navigation

redis-modules-java

Java client libraries for Redis modules, built on Redisson.

build Maven Central JDK 8+ codecov license


Every module ships a synchronous and an asynchronous (RFuture) API, works on a single server, sentinel or cluster through Redisson's configuration, supports pipelining, and can share one connection pool with the other modules or with an existing Redisson instance.

Modules

Module Artifact Redis 8 built-in Status Docs
RedisBloom (Bloom, Cuckoo, Count-Min Sketch, Top-K, t-digest) redisbloom Active commands
RediSearch redisearch Active commands
RedisJSON redisjson Active commands
RedisTimeSeries redistimeseries Active commands
Vector sets (Redis 8 native type) vectorset Active commands
FalkorDB / RedisGraph redisgraph Active, tested against FalkorDB commands
RedisAI redisai Deprecated, upstream end of life commands
RedisGears redisgears Deprecated, upstream end of life commands
Spring Boot starter spring-boot-starter Active guide
Everything above all

Deprecated modules still work against the last released versions of their module and are kept for existing users; they will be removed in a future major release.

Requirements

  • Java 8 or later
  • Redis 8.x (modules built in), or Redis 7.x with the corresponding module loaded
  • Graph commands: FalkorDB (any Redis it ships with), or RedisGraph 2.x for the shared commands
  • Vector sets need Redis 8.0+ (VRANGE needs 8.4+)
  • Redisson 4.7.x (pulled in transitively)

Installation

All modules in one dependency:

<dependency>
    <groupId>io.github.dengliming.redismodule</groupId>
    <artifactId>all</artifactId>
    <version>3.0.0</version>
</dependency>

Or a single module, for example RedisTimeSeries:

<dependency>
    <groupId>io.github.dengliming.redismodule</groupId>
    <artifactId>redistimeseries</artifactId>
    <version>3.0.0</version>
</dependency>

Gradle:

implementation 'io.github.dengliming.redismodule:all:3.0.0'
Snapshots

Every push to master publishes 3.0.1-SNAPSHOT to the Central snapshot repository:

<repositories>
    <repository>
        <id>central-snapshots</id>
        <url>https://central.sonatype.com/repository/maven-snapshots/</url>
        <snapshots><enabled>true</enabled></snapshots>
    </repository>
</repositories>

Quick start

Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");

RedisJSONClient client = new RedisJSONClient(config);
RedisJSON json = client.getRedisJSON();

json.set("user:1", SetArgs.Builder.create(".", "{\"name\":\"lisi\",\"age\":30}"));
Map<String, Object> user = json.get("user:1", Map.class, new GetArgs().path("."));
long age = json.incrBy("user:1", ".age", 1);

client.shutdown();

Every synchronous method has an *Async twin returning an RFuture:

RFuture<Long> future = json.arrAppendAsync("user:1", ".tags", "vip");
future.thenAccept(size -> log.info("tags: {}", size));

Usage

RedisBloom

RedisBloomClient client = new RedisBloomClient(config);

BloomFilter bloomFilter = client.getRBloomFilter("bf");
bloomFilter.create(0.01d, 1000);
bloomFilter.madd("a", "b", "c");
List<Boolean> exists = bloomFilter.existsMulti("a", "z");   // [true, false]

CuckooFilter cuckooFilter = client.getCuckooFilter("cf");
cuckooFilter.reserve(1000);
cuckooFilter.add("a");

CountMinSketch sketch = client.getCountMinSketch("cms");
sketch.create(2000, 5);
Map<String, Integer> increments = new HashMap<>();
increments.put("a", 3);
sketch.incrby(increments);

TopKFilter topK = client.getTopKFilter("topk");
topK.reserve(3, 2000, 7, 0.925d);
topK.add("a", "b", "a");
List<String> top = topK.list();

TDigest tDigest = client.getTDigest("td");
tDigest.create(100);
tDigest.add(Arrays.asList(new AbstractMap.SimpleEntry<>(1.0, 1.0), new AbstractMap.SimpleEntry<>(2.0, 1.0)));  // (value, weight)
List<Double> quantiles = tDigest.getQuantile(0.5);

RediSearch

RediSearchClient client = new RediSearchClient(config);
RediSearch rediSearch = client.getRediSearch("idx:products");

rediSearch.createIndex(new Schema()
        .addField(new TextField("title"))
        .addField(new Field("price", FieldType.NUMERIC))
        .addField(new Field("location", FieldType.GEO)),
    new IndexOptions().definition(new IndexDefinition().setPrefixes(Arrays.asList("product:"))));

// documents are plain hashes under the configured prefix
SearchResult result = rediSearch.search("phone", new SearchOptions()
        .withScores()
        .filter(new NumericFilter("price", 100, 500))
        .filter(new GeoFilter("location", 15, 37, 200, GeoFilter.Unit.KILOMETERS))
        .page(0, 10));

AggregateResult aggregate = rediSearch.aggregate("*", new AggregateOptions()
        .groups(new Group().fields("@brand").reducers(Reducers.count().as("count"))));

List<Suggestion> suggestions = rediSearch.getSuggestion("pho", new SuggestionOptions().withScores());

Vector similarity search: declare a VectorField, store vectors as little-endian blobs and query with a KNN clause bound through PARAMS (dialect 2):

rediSearch.createIndex(new Schema()
        .addField(new TextField("title"))
        .addField(new VectorField("embedding", VectorAlgorithm.HNSW, VectorType.FLOAT32, 768, DistanceMetric.COSINE)),
    new IndexOptions().definition(new IndexDefinition().setPrefixes(Arrays.asList("doc:"))));

SearchResult nearest = rediSearch.search("*=>[KNN 10 @embedding $vec AS score]", new SearchOptions()
        .param("vec", Vectors.toFloat32Bytes(queryEmbedding))
        .dialect(2)
        .returnFields("title", "score")
        .sort(new SortBy("score", SortOrder.ASC)));

RedisJSON

RedisJSONClient client = new RedisJSONClient(config);
RedisJSON json = client.getRedisJSON();

json.set("user:1", SetArgs.Builder.create(".", "{\"name\":\"lisi\",\"tags\":[]}"));
json.arrAppend("user:1", ".tags", "vip", "beta");
long tags = json.arrLen("user:1", ".tags");                     // 2
Class type = json.getType("user:1", ".name");                   // String.class
List<Map> users = json.mget(".", Map.class, "user:1", "user:2"); // missing keys yield null

RedisJSON serializes with Gson by default. Gson is an optional dependency of the redisjson artifact (the all artifact includes it): either add com.google.code.gson:gson yourself, or plug in your own JsonCodec:

JsonCodec jackson = new JsonCodec() {
    private final ObjectMapper mapper = new ObjectMapper();
    public String toJson(Object value) { return mapper.writeValueAsString(value); }
    public <T> T fromJson(String json, Class<T> type) { return mapper.readValue(json, type); }
};
RedisJSONClient client = new RedisJSONClient(config, jackson);

RedisTimeSeries

RedisTimeSeriesClient client = new RedisTimeSeriesClient(config);
RedisTimeSeries ts = client.getRedisTimeSeries();

ts.create("temperature:2:32", new TimeSeriesOptions()
        .retentionTime(60_000L)
        .labels(new Label("sensor_id", "2"), new Label("area_id", "32")));

ts.add(new Sample("temperature:2:32", Sample.Value.of(System.currentTimeMillis(), 26.5)), null);
ts.incrBy("requests:total", 1);

List<Sample.Value> values = ts.range("temperature:2:32", 0, Long.MAX_VALUE,
        new RangeOptions().aggregationType(Aggregation.AVG, 60_000));
List<TimeSeries> byArea = ts.mrange(0, Long.MAX_VALUE, new RangeOptions().withLabels(), "area_id=32");

Vector sets

VectorSetClient client = new VectorSetClient(config);
VectorSet movies = client.getVectorSet("movies");

movies.add("matrix", new double[]{0.9, 0.1, 0.0}, new AddArgs().attributes("{\"year\":1999}"));
movies.add("amelie", new double[]{0.0, 0.2, 0.9}, new AddArgs().attributes("{\"year\":2001}"));

List<Similarity> hits = movies.similar(new double[]{1.0, 0.0, 0.0}, new SimilarArgs().withScores().withAttribs().count(5));
List<Similarity> recent = movies.similarTo("matrix", new SimilarArgs().filter(".year > 2000"));
List<Double> vector = movies.getVector("matrix");

FalkorDB / RedisGraph

RedisGraphClient client = new RedisGraphClient(config);   // FalkorDB, or RedisGraph 2.x
RedisGraph graph = client.getRedisGraph();

graph.query("social", "CREATE (:person{name:'roi',age:32})-[:knows{since:2000}]->(:person{name:'amit',age:30})", 0L);
ResultSet resultSet = graph.query("social", "MATCH (a:person)-[r:knows]->(b:person) RETURN a, r, b.name", 0L);
for (Record record : resultSet.getResults()) {
    Node a = (Node) record.getValue("a");
    Edge r = (Edge) record.getValue("r");
    // property names and relationship types are resolved, not just their indices
    System.out.println(a.getProperty("name") + " " + r.getRelationshipType() + " " + record.getString("b.name"));
}

// FalkorDB extensions
graph.createConstraint("social", ConstraintType.UNIQUE, EntityType.NODE, "person", "name");
graph.copy("social", "social_backup");
Map<String, Object> memory = graph.memoryUsage("social");

RedisAI and RedisGears (deprecated)

RedisAI redisAI = new RedisAIClient(config).getRedisAI();
redisAI.setTensor("tensor1", DataType.FLOAT, new int[]{2, 2}, null, new String[]{"1", "2", "3", "4"});

RedisGears redisGears = new RedisGearsClient(config).getRedisGears();
redisGears.pyExecute("GB().run()", false);

Pipelining

Every client has createXxxBatch(). Objects obtained from the batch queue their *Async calls and execute() sends them in a single round trip, returning the responses in call order:

RedisBloomBatch batch = redisBloomClient.createRedisBloomBatch();
BloomFilter bloomFilter = batch.getRBloomFilter("bf");
bloomFilter.createAsync(0.01d, 1000);
bloomFilter.maddAsync("a", "b", "c");
bloomFilter.existsAsync("a");

BatchResult<?> result = batch.execute();
result.getResponses();   // [true, [true, true, true], true]

Sharing one Redisson instance

Each client can wrap an existing RedissonClient, so several modules (or your own Redisson code) share a single connection pool. A wrapping client never shuts the shared instance down:

RedissonClient redisson = Redisson.create(config);

RedisJSONClient jsonClient = new RedisJSONClient(redisson);
RediSearchClient searchClient = new RediSearchClient(redisson);
RedisBloomClient bloomClient = new RedisBloomClient(redisson);

// ...
redisson.shutdown();

Spring Boot

Add the starter and enable the modules you need; they share one connection pool or use dedicated ones, and pick up your own RedissonClient and JsonCodec beans when present.

redis-module:
  config: |
    singleServerConfig:
      address: "redis://127.0.0.1:6379"
  redisjson:
    enabled: true
  redisearch:
    enabled: true
@Autowired
private RedisJSONClient redisJSONClient;

See the starter guide for every option.

Compatibility notes

  • CI runs the test suite against the official redis:8.8 image, where Search, JSON, TimeSeries and Bloom are built in. FT.CONFIG was removed in Redis 8; RediSearch.setConfig() / getConfig() fall back to CONFIG SET/GET search-* automatically.
  • RediSearch 1.x document commands (FT.ADD, FT.GET, FT.DEL, FT.DROP, ...) are still exposed but no longer exist on RediSearch 2.x / Redis 8. Index hashes or JSON documents under a prefix instead.
  • Commands are routed by their key, so cluster deployments work with Redisson's useClusterServers().
  • Redisson 4.x: JSON configuration strings are still accepted (parsed as YAML). Redisson dropped the retryInterval setting in favour of retryDelay, and singleServerConfig.password is deprecated in favour of the top-level password.

Building from source

./mvnw clean install -DskipTests -Dgpg.skip

Integration tests need a running Redis with the modules loaded; pass its location with -DREDIS_HOST and -DREDIS_PORT (see build.yml for the full matrix). More runnable snippets live in examples.

Contributing

Issues and pull requests are welcome. Please run ./mvnw verify -DskipTests before opening a PR so that Checkstyle passes.

License

Apache License 2.0