How to use quarkus-rest-client-reactive-jackson in a Keycloak custom SPI

I’m building a Keycloak custom provider (SPI) that adds claims to the access tokens generated by Keycloak. These claims should be obtained by performing external REST API calls. For the REST client I would like to use quarkus-rest-client-reactive-jackson because it allows creating REST clients with interfaces (using the @RegisterRestClient annotation), which is very fast and easy (actually, this comes from Eclipse Microprofiles). The problem is that when I call some REST endpoint I get the error Uncaught server error: java.lang.NoClassDefFoundError: org/eclipse/microprofile/rest/client/RestClientBuilder. I have done some research and it seems that the dependency quarkus-rest-client-reactive-jackson is not present in the Keycloak server (that is a Quarkus)… so I have to include it somehow.

The dependencies part of my pom.xml is the following:

    <dependencies>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-core</artifactId>
            <scope>provided</scope>
            <version>${keycloak.version}</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi</artifactId>
            <scope>provided</scope>
            <version>${keycloak.version}</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi-private</artifactId>
            <scope>provided</scope>
            <version>${keycloak.version}</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-services</artifactId>
            <scope>provided</scope>
            <version>${keycloak.version}</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-model-jpa</artifactId>
            <scope>provided</scope>
            <version>${keycloak.version}</version>
        </dependency>

        <dependency>
            <groupId>jakarta.persistence</groupId>
            <artifactId>jakarta.persistence-api</artifactId>
            <scope>provided</scope>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>jakarta.json.bind</groupId>
            <artifactId>jakarta.json.bind-api</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>jakarta.json</groupId>
            <artifactId>jakarta.json-api</artifactId>
            <version>2.1.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.quarkus/quarkus-rest-client-reactive-jackson -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-rest-client-reactive-jackson</artifactId>
            <version>3.6.3</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

Focus on the last part, the quarkus-rest-client-reactive-jackson dependency.

The REST client I’m using (implemented as an interface) is this one:

@RegisterRestClient()
@Path("/my-path")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public interface TokenRestInterface{
    @POST
    @Path("/token")
    TokenDtoResponse getToken(@HeaderParam("User-Id") Integer userId, @FormParam("client_id") String clientId, @FormParam("client_secret") String clientSecret, @FormParam("grant_type") String grantType);
}

The part of the code where I call the REST client is the following:

TokenRestInterface tokenRestInterface = null;
try {
    tokenRestInterface = RestClientBuilder.newBuilder().baseUrl(new URL("http://localhost:8080/")).build(TokenRestInterface.class);
} catch (MalformedURLException e) {
    throw new RuntimeException(e);
}
TokenDtoResponse tokenDtoResponse = keycloakRestInterface.getToken(userId, CLIENT_ID, CLIENT_SECRET, "client_credentials");
return tokenDtoResponse.getAccess_token();

The error I get when the previous code gets executed is:
2023-12-19 11:47:43,443 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-1) Uncaught server error: java.lang.NoClassDefFoundError: org/eclipse/microprofile/rest/client/RestClientBuilder.

Based on this, I suppose the exception is thrown in the line: RestClientBuilder.newBuilder()....

Note that I included both jars (my provider and the dependency jar) in the providers folder of Keycloak.

folder of my Keycloak server

(Note that metacontratas-keycloak-provider is the name of my project, my custom Keycloak provider)

Could you please explain to me how to solve this error? This is, do you know how to add correctly the dependency I’m using into the Keycloak service?
Thanks!

I filed a bug, and the issue is discussed here:

There is a workaround, but it is not ideal.

1 Like

But the issue you are mentioning is related to RestEasy Classic (not reactive), right? I’m referring to RestEasy Reactive which is contained by quarkus-rest-client-reactive-jackson (Quarkus RESTEasy Reactive’s REST Client Jackson).

Yes, however, I think both are effected in the same way. You have to custom build it in order to get it loaded. I’d suggest trying to add the dependency into quarkus/deployment/pom.xml and quarkus/runtime/pom.xml as indicated in the discussion and seeing if that works for your use case.

1 Like