////
This guide is maintained in the main Quarkus repository
and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
////
= Using the Redis Client
:extension-status: preview
include::./attributes.adoc[]

This guide demonstrates how your Quarkus application can connect to a Redis server using the Redis Client extension.

include::./status-include.adoc[]

== Prerequisites

include::includes/devtools/prerequisites.adoc[]
* A running Redis server, or Docker Compose to start one

== Architecture

In this guide, we are going to expose a simple Rest API to increment numbers by using the https://redis.io/commands/incrby[`INCRBY`] command.
Along the way, we'll see how to use other Redis commands like `GET`, `SET`, `DEL` and `KEYS`.

We'll be using the Quarkus Redis Client extension to connect to our Redis Server. The extension is implemented on top of the https://vertx.io/docs/vertx-redis-client/java/[Vert.x Redis Client],
providing an asynchronous and non-blocking way to connect to Redis.

== Solution

We recommend that you follow the instructions in the next sections and create the application step by step.
However, you can go right to the completed example.

Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive].

The solution is located in the `redis-quickstart` {quickstarts-tree-url}/redis-quickstart[directory].

== Creating the Maven Project

First, we need a new project. Create a new project with the following command:

:create-app-artifact-id: redis-quickstart
:create-app-extensions: redis-client,resteasy-jackson,resteasy-mutiny
include::includes/devtools/create-app.adoc[]

This command generates a new project, importing the Redis extension.


If you already have your Quarkus project configured, you can add the `redis-client` extension
to your project by running the following command in your project base directory:

:add-extension-extensions: redis-client
include::includes/devtools/extension-add.adoc[]

This will add the following to your build file:

[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"]
.pom.xml
----
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-redis-client</artifactId>
</dependency>
----

[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"]
.build.gradle
----
implementation("io.quarkus:quarkus-redis-client")
----

== Starting the Redis server

Then, we need to start a Redis instance (if you do not have one already) using the following command:

[source, bash]
----
docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name redis_quarkus_test -p 6379:6379 redis:5.0.6
----

[NOTE]
====
If you use xref:redis-dev-services.adoc[Dev Services for Redis], launching the container manually is not necessary!
====

== Configuring Redis properties

Once we have the Redis server running, we need to configure the Redis connection properties.
This is done in the `application.properties` configuration file. Edit it to the following content:

[source,properties]
----
quarkus.redis.hosts=redis://localhost:6379 <1>
----

<1> Configure Redis hosts to connect to. Here we connect to the Redis server we started in the previous section

[NOTE]
====
This is needed if you are not using xref:redis-dev-services.adoc[Dev Services for Redis]
====


== Creating the Increment POJO

We are going to model our increments using the `Increment` POJO.
Create the `src/main/java/org/acme/redis/Increment.java` file, with the following content:

[source, java]
----
package org.acme.redis;

public class Increment {
    public String key; // <1>
    public int value; // <2>

    public Increment(String key, int value) {
        this.key = key;
        this.value = value;
    }

    public Increment() {
    }
}
----
<1> The key that will be used as the Redis key
<2> The value held by the Redis key


== Creating the Increment Service

We are going to create an `IncrementService` class which will play the role of a Redis client.
With this class, we'll be able to perform the `SET`, `GET` , `DELET`, `KEYS` and `INCRBY` Redis commands.

Create the `src/main/java/org/acme/redis/IncrementService.java` file, with the following content:

[source, java]
----
package org.acme.redis;

import io.quarkus.redis.client.RedisClient;
import io.quarkus.redis.client.reactive.ReactiveRedisClient;
import io.smallrye.mutiny.Uni;

import io.vertx.mutiny.redis.client.Response;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
class IncrementService {

    @Inject
    RedisClient redisClient; // <1>

    @Inject
    ReactiveRedisClient reactiveRedisClient; // <2>

    Uni<Void> del(String key) {
        return reactiveRedisClient.del(Arrays.asList(key))
                .map(response -> null);
    }

    String get(String key) {
        return redisClient.get(key).toString();
    }

    void set(String key, Integer value) {
        redisClient.set(Arrays.asList(key, value.toString()));
    }

    void increment(String key, Integer incrementBy) {
        redisClient.incrby(key, incrementBy.toString());
    }

    Uni<List<String>> keys() {
        return reactiveRedisClient
                .keys("*")
                .map(response -> {
                    List<String> result = new ArrayList<>();
                    for (Response r : response) {
                        result.add(r.toString());
                    }
                    return result;
                });
    }
}
----
<1> Inject the Redis synchronous client
<2> Inject the Reactive Redis client

== Creating the Increment Resource

Create the `src/main/java/org/acme/redis/IncrementResource.java` file, with the following content:

[source, java]
----
package org.acme.redis;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.PathParam;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.POST;
import javax.ws.rs.DELETE;
import java.util.List;

import io.smallrye.mutiny.Uni;

@Path("/increments")
public class IncrementResource {

    @Inject
    IncrementService service;

    @GET
    public Uni<List<String>> keys() {
        return service.keys();
    }

    @POST
    public Increment create(Increment increment) {
        service.set(increment.key, increment.value);
        return increment;
    }

    @GET
    @Path("/{key}")
    public Increment get(@PathParam("key") String key) {
        return new Increment(key, Integer.valueOf(service.get(key)));
    }

    @PUT
    @Path("/{key}")
    public void increment(@PathParam("key") String key, Integer value) {
        service.increment(key, value);
    }

    @DELETE
    @Path("/{key}")
    public Uni<Void> delete(@PathParam("key") String key) {
        return service.del(key);
    }
}
----

== Creating the test class

Create the `src/test/java/org/acme/redis/IncrementResourceTest.java` file with the following content:

[source, java]
----
package org.acme.redis;

import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;

import static io.restassured.RestAssured.given;

import io.restassured.http.ContentType;

@QuarkusTest
public class IncrementResourceTest {

    @Test
    public void testRedisOperations() {
        // verify that we have nothing
        given()
                .accept(ContentType.JSON)
                .when()
                .get("/increments")
                .then()
                .statusCode(200)
                .body("size()", is(0));

        // create a first increment key with an initial value of 0
        given()
                .contentType(ContentType.JSON)
                .accept(ContentType.JSON)
                .body("{\"key\":\"first-key\",\"value\":0}")
                .when()
                .post("/increments")
                .then()
                .statusCode(200)
                .body("key", is("first-key"))
                .body("value", is(0));

        // create a second increment key with an initial value of 10
        given()
                .contentType(ContentType.JSON)
                .accept(ContentType.JSON)
                .body("{\"key\":\"second-key\",\"value\":10}")
                .when()
                .post("/increments")
                .then()
                .statusCode(200)
                .body("key", is("second-key"))
                .body("value", is(10));

        // increment first key by 1
        given()
                .contentType(ContentType.JSON)
                .body("1")
                .when()
                .put("/increments/first-key")
                .then()
                .statusCode(204);

        // verify that key has been incremented
        given()
                .accept(ContentType.JSON)
                .when()
                .get("/increments/first-key")
                .then()
                .statusCode(200)
                .body("key", is("first-key"))
                .body("value", is(1));

        // increment second key by 1000
        given()
                .contentType(ContentType.JSON)
                .body("1000")
                .when()
                .put("/increments/second-key")
                .then()
                .statusCode(204);

        // verify that key has been incremented
        given()
                .accept(ContentType.JSON)
                .when()
                .get("/increments/second-key")
                .then()
                .statusCode(200)
                .body("key", is("second-key"))
                .body("value", is(1010));

        // verify that we have two keys in registered
        given()
                .accept(ContentType.JSON)
                .when()
                .get("/increments")
                .then()
                .statusCode(200)
                .body("size()", is(2));

        // delete first key
        given()
                .accept(ContentType.JSON)
                .when()
                .delete("/increments/first-key")
                .then()
                .statusCode(204);

        // verify that we have one key left after deletion
        given()
                .accept(ContentType.JSON)
                .when()
                .get("/increments")
                .then()
                .statusCode(200)
                .body("size()", is(1));

        // delete second key
        given()
                .accept(ContentType.JSON)
                .when()
                .delete("/increments/second-key")
                .then()
                .statusCode(204);

        // verify that there is no key left
        given()
                .accept(ContentType.JSON)
                .when()
                .get("/increments")
                .then()
                .statusCode(200)
                .body("size()", is(0));
    }
}
----

== Get it running

If you followed the instructions, you should have the Redis server running.
Then, you just need to run the application using:

include::includes/devtools/dev.adoc[]

Open another terminal and run the `curl http://localhost:8080/increments` command.

== Interacting with the application
As we have seen above, the API exposes five Rest endpoints.
In this section we are going to see how to initialise an increment, see the list of current increments,
incrementing a value given its key, retrieving the current value of an increment, and finally deleting
a key.

=== Creating a new increment

[source,bash]
----
curl -X POST -H "Content-Type: application/json" -d '{"key":"first","value":10}' http://localhost:8080/increments <1>
----
<1> We create the first increment, with the key `first` and an initial value of `10`.

Running the above command should return the result below:

[source, json]
-----
{
  "key": "first",
  "value": 10
}
-----

=== See current increments keys

To see the list of current increments keys, run the following command:

[source,bash]
----
curl http://localhost:8080/increments
----

The above command should return  `["first"]` indicating that we have only one increment thus far.

=== Retrieve a new increment

To retrieve an increment using its key, we will have to run the below command:

[source,bash]
----
curl http://localhost:8080/increments/first <1>
----
<1> Running this command, should return the following result:

[source, json]
----
{
  "key": "first",
  "value": 10
}
----

=== Increment a value given its key

To increment a value, run the following command:

[source,bash]
----
curl -X PUT -H "Content-Type: application/json" -d '27' http://localhost:8080/increments/first <1>
----
<1> Increment the `first` value by 27.

Now, running the command `curl http://localhost:8080/increments/first` should return the following result:

[source, json]
----
{
  "key": "first",
  "value": 37 <1>
}
----
<1> We see that the value of the `first` key is now `37` which is exactly the result of `10 + 27`, quick maths.

=== Deleting a key

Use the command below, to delete an increment given its key.

[source,bash]
----
curl -X DELETE  http://localhost:8080/increments/first <1>
----
<1> Delete the `first` increment.

Now, running the command `curl http://localhost:8080/increments` should return an empty list `[]`

== Packaging and running in JVM mode

You can run the application as a conventional jar file.

First, we will need to package it:

include::includes/devtools/build.adoc[]

NOTE: This command will start a Redis instance to execute the tests. Thus your Redis containers need to be stopped.

Then run it:

[source,bash]
----
java -jar target/quarkus-app/quarkus-run.jar
----

== Running Native

You can also create a native executable from this application without making any
source code changes. A native executable removes the dependency on the JVM:
everything needed to run the application on the target platform is included in
the executable, allowing the application to run with minimal resource overhead.

Compiling a native executable takes a bit longer, as GraalVM performs additional
steps to remove unnecessary codepaths. Use the  `native` profile to compile a
native executable:

include::includes/devtools/build-native.adoc[]

Once the build is finished, you can run the executable with:

[source,bash]
----
./target/redis-quickstart-1.0.0-SNAPSHOT-runner
----

== Connection Health Check

If you are using the `quarkus-smallrye-health` extension, `quarkus-vertx-redis` will automatically add a readiness health check
to validate the connection to the Redis server.

So when you access the `/q/health/ready` endpoint of your application you will have information about the connection validation status.

This behavior can be disabled by setting the `quarkus.redis.health.enabled` property to `false` in your `application.properties`.

[[multiple-clients-configuration]]
== Multiple Redis Clients

The Redis extension allows you to configure multiple clients.
Using several clients works the same way as having a single client.

[source,properties]
----
quarkus.redis.hosts=redis://localhost:6379
quarkus.redis.second.hosts=redis://localhost:6379
----

Notice there's an extra bit in the key (the `second` segment).
The syntax is as follows: `quarkus.redis.[optional name.][redis configuration property]`.
If the name is omitted, it configures the default client.

== Named Redis Client Injection

When using multiple clients, you can select the client to inject using the `io.quarkus.redis.client.RedisClientName` qualifier.
Using the above properties to configure three different clients, you can also inject each one as follows:

[source,java,indent=0]
----
@Inject
RedisClient defaultRedisClient;

@Inject
@RedisClientName("second")
RedisClient redisClient2;

@Inject
@RedisClientName("second")
ReactiveRedisClient reactiveClient2;
----

== Providing Redis Hosts Programmatically

The `RedisHostsProvider` programmatically provides redis hosts. This allows for configuration of properties like redis connection password coming from other sources.

[NOTE]
====
This is useful as it removes the need to store sensitive data in application.properties.
====

[source,java,indent=0]
----
@ApplicationScoped
@Named("hosts-provider") // the name of the host provider
public class ExampleRedisHostProvider implements RedisHostsProvider {
    @Override
    public Set<URI> getHosts() {
        // do stuff to get the host
        String host = "redis://localhost:6379/3"
        return Collections.singleton(URI.create(host));
    }
}
----

The host provider can be used to configure the redis client like shown below
[source,properties,indent=0]
----
quarkus.redis.hosts-provider-name=hosts-provider
----

== Creating Clients Programmatically

The `RedisClient` and `ReactiveRedisClient` provide factory methods to create clients programmatically.
The client to be created are configured using the usual <<config-reference,Redis configuration>>.

[NOTE]
====
This is useful to create a client dynamically in a non-CDI bean e.g a xref:hibernate-orm-panache.adoc[Panache entity].
Or to create a different client when running in pub/sub mode. This mode requires two different connections
because once a connection invokes a subscriber mode then it cannot be used for running other commands
than the command to leave that mode.
====

The below code snippet shows how we can create dynamic clients using the configurations in <<multiple-clients-configuration>>.
[source,java,indent=0]
----
// creating default redis client
RedisClient defaultRedisClient = RedisClient.createClient();

// creating named redis client whose configuration name is "second"
RedisClient namedRedisClient = RedisClient.createClient("second");

// creating a default reactive redis client
ReactiveRedisClient defaultReactiveRedisClient = ReactiveRedisClient.createClient();

// creating a named reactive redis client whose configuration name is "second"
ReactiveRedisClient namedReactiveRedisClient = ReactiveRedisClient.createClient("second");
----

Please see also <<redis-reference.adoc#custom_redis_commands,How to use custom Redis Commands>>.

[[config-reference]]
== Configuration Reference

include::{generated-dir}/config/quarkus-redis-client.adoc[opts=optional, leveloffset=+1]
