'Keycloak and RESTEasy client: ClassNotFoundException: org.jboss.resteasy.client.jaxrs.internal.proxy.ProxyBuilderImpl

I need to add a RESTEasy client to keycloak (WildFly based distribution).

  1. I create a new class by extending org.keycloak.broker.provider.AbstractIdentityProviderMapper
  2. I define the following code in its constructor
Client client = ClientBuilder.newBuilder().build();
ClientWebTarget target = (ClientWebTarget) client.target(UriBuilder.fromPath(URL_PATH));
CustomFetcher fetcher = target.proxy(CustomFetcher.class);
  1. I add the class to src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper

When I copy the jar to keycloak's standalone/deployments directory I get

20:55:01,522 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-2) MSC000001: Failed to start service jboss.deployment.unit."keycloak-user-mapper-0.0.1-SNAPSHOT.jar".POST_MODULE: org.jboss.msc.service.StartException in service jboss.deployment.unit."keycloak-user-mapper-0.0.1-SNAPSHOT.jar".POST_MODULE: WFLYSRV0153: Failed to process phase POST_MODULE of deployment "keycloak-user-mapper-0.0.1-SNAPSHOT.jar"
        at [email protected]//org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:189)
        at [email protected]//org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1739)
        at [email protected]//org.jboss.msc.service.ServiceControllerImpl$StartTask.execute(ServiceControllerImpl.java:1701)
        at [email protected]//org.jboss.msc.service.ServiceControllerImpl$ControllerTask.run(ServiceControllerImpl.java:1559)
        at [email protected]//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
        at [email protected]//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
        at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
        at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
        at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.util.ServiceConfigurationError: org.keycloak.broker.provider.IdentityProviderMapper: Provider org.myorg.UserAttributeMapper could not be instantiated
        at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:582)
        at java.base/java.util.ServiceLoader$ProviderImpl.newInstance(ServiceLoader.java:804)
        at java.base/java.util.ServiceLoader$ProviderImpl.get(ServiceLoader.java:722)
        at java.base/java.util.ServiceLoader$3.next(ServiceLoader.java:1395)
        at [email protected]//org.keycloak.provider.DefaultProviderLoader.load(DefaultProviderLoader.java:60)
        at [email protected]//org.keycloak.provider.ProviderManager.load(ProviderManager.java:94)
        at [email protected]//org.keycloak.services.DefaultKeycloakSessionFactory.loadFactories(DefaultKeycloakSessionFactory.java:294)
        at [email protected]//org.keycloak.services.DefaultKeycloakSessionFactory.deploy(DefaultKeycloakSessionFactory.java:154)
        at [email protected]//org.keycloak.provider.ProviderManagerRegistry.deploy(ProviderManagerRegistry.java:42)
        at [email protected]//org.keycloak.subsystem.server.extension.KeycloakProviderDeploymentProcessor.deploy(KeycloakProviderDeploymentProcessor.java:58)
        at [email protected]//org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:182)
        ... 8 more
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: org.jboss.resteasy.client.jaxrs.internal.proxy.ProxyBuilderImpl from [Module "org.jboss.as.server" version 18.0.4.Final from local module loader @66c92293 (finder: local module finder @332796d3 (roots: /opt/jboss/keycloak/modules,/opt/jboss/keycloak/modules/system/layers/keycloak,/opt/jboss/keycloak/modules/system/layers/base))]
        at [email protected]//org.jboss.resteasy.client.jaxrs.ProxyBuilder.builder(ProxyBuilder.java:41)
        at [email protected]//org.jboss.resteasy.client.jaxrs.internal.ClientWebTarget.createProxyBuilder(ClientWebTarget.java:107)
        at [email protected]//org.jboss.resteasy.client.jaxrs.internal.ClientWebTarget.proxy(ClientWebTarget.java:94)
        at deployment.keycloak-user-mapper-0.0.1-SNAPSHOT.jar//org.myorg.UserAttributeMapper.<init>(UserAttributeMapper.java:55)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
        at java.base/java.util.ServiceLoader$ProviderImpl.newInstance(ServiceLoader.java:780)
        ... 17 more
Caused by: java.lang.ClassNotFoundException: org.jboss.resteasy.client.jaxrs.internal.proxy.ProxyBuilderImpl from [Module "org.jboss.as.server" version 18.0.4.Final from local module loader @66c92293 (finder: local module finder @332796d3 (roots: /opt/jboss/keycloak/modules,/opt/jboss/keycloak/modules/system/layers/keycloak,/opt/jboss/keycloak/modules/system/layers/base))]
        at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:200)
        at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:410)
        at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:398)
        at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:116)
        at [email protected]//org.jboss.resteasy.client.jaxrs.ProxyBuilder.builder(ProxyBuilder.java:35)
        ... 25 more

Apparently Wildfly's own org.jboss.as.server module is not configured properly, so it doesn't have org.jboss.resteasy.client.jaxrs.internal.proxy.ProxyBuilderImpl on its CLASSPATH, but it exists on the my jar's CLASSPATH, because I can successfully execute

Class.forName("org.jboss.resteasy.client.jaxrs.internal.proxy.ProxyBuilderImpl")

in the constructor of my class.

WORKAROUND I added <module name="org.jboss.resteasy.resteasy-client"/> to modules/system/layers/base/org/jboss/as/server/main/module.xml and it works now, but it doesn't seem like a good solution, how do I approach this configuration?

UPDATE I suspect the problem might be with hot redeploy, if I copy the jar to keycloak first and after it I start keycloak, no problems with the CLASSPATH exist. But if I copy the jar into standalone/deployments when keycloak is running, then ClassNotFoundException is thrown. I have tried keycloak-17.0.0.zip, keycloak-17.0.1.zip, keycloak-18.0.0.zip (WildFly based)

Here is a script to reproduce the issue. Invoke it with:

bash script.sh [17.0.0 | 17.0.1 | 18.0.0]

to deploy the jar to local keycloak (the script will download keycloak's zip archive automatically if it hasn't been downloaded previously).

(*) Parameters in square brackets are optional, default version is 18.0.0

#! /bin/bash

set -x

mkdir -p so72280865 && cd so72280865 || {
    echo "Unable to create a working directory"
    exit 1
}

cat <<EOF > pom.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.myorg</groupId>
    <artifactId>keycloak-so-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.keycloak</groupId>
                <artifactId>keycloak-parent</artifactId>
                <version>17.0.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-core</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi-private</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-services</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jaxrs</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-client</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jackson2-provider</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>
EOF

mkdir -p src/main/java/org/myorg/

cat <<EOF > src/main/java/org/myorg/App.java
package org.myorg;

import org.jboss.resteasy.client.jaxrs.internal.ClientWebTarget;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.provider.ProviderConfigProperty;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.UriBuilder;
import java.util.Collections;
import java.util.List;

public class App extends AbstractIdentityProviderMapper {

    private static final String URL_PATH = "https://api.myip.com";
    private final CustomFetcher fetcher;

    public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};

    protected static final List<ProviderConfigProperty> configProperties = Collections.emptyList();

    public App() {
        final Client client = ClientBuilder.newBuilder()
                .register(new org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider())
                .build();
        ClientWebTarget target = (ClientWebTarget) client.target(UriBuilder.fromPath(URL_PATH));
        fetcher = target.proxy(CustomFetcher.class);
    System.out.println(fetcher.get().toString());
    }

    @Path("/")
    public interface CustomFetcher {
        @GET String get();
    }

    @Override
    public String[] getCompatibleProviders() {
        return COMPATIBLE_PROVIDERS;
    }

    @Override
    public String getDisplayCategory() {
        return "Attribute Importer";
    }

    @Override
    public String getDisplayType() {
        return "Custom Fetcher";
    }

    @Override
    public String getHelpText() {
        return "Custom Fetcher";
    }

    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        return configProperties;
    }

    @Override
    public String getId() {
        return "custom-fetcher-idp-mapper";
    }
}
EOF

mkdir -p src/main/resources/META-INF/services
cat <<EOF > src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
org.myorg.App
EOF

mvn package

if [ "$?" -ne "0" ] ; then
    exit 1
fi



KEYCLOAK_VERSION=${1:-18.0.0}
[ -f "keycloak-legacy-${KEYCLOAK_VERSION}.zip" ] || {
    curl -LO "https://github.com/keycloak/keycloak/releases/download/${KEYCLOAK_VERSION}/keycloak-legacy-${KEYCLOAK_VERSION}.zip"
}

[ -d "keycloak-${KEYCLOAK_VERSION}" ] || {
    unzip "keycloak-legacy-${KEYCLOAK_VERSION}.zip"
} && cp target/keycloak-so-demo-0.0.1-SNAPSHOT.jar "keycloak-${KEYCLOAK_VERSION}"/standalone/deployments &&
    ./"keycloak-${KEYCLOAK_VERSION}"/bin/standalone.sh &

trap "./keycloak-${KEYCLOAK_VERSION}/bin/jboss-cli.sh --connect --command=:shutdown" EXIT

TIMEOUT=180
while true ; do
    started=$(ss -lt | grep 9990 | wc -l)
    [ "$started" -eq "1" ] && break

    [ "$TIMEOUT" -le "0" ] && {
        echo "Timeout reached starting keycloak"
        exit 1
    }
    TIMEOUT=$((TIMEOUT - 10))
    sleep 10
done

cp target/keycloak-so-demo-0.0.1-SNAPSHOT.jar "keycloak-${KEYCLOAK_VERSION}"/standalone/deployments 
sleep 10


Solution 1:[1]

The org.jboss.as.server should not have a dependency on the RESTEasy module. You need your deployment to have a dependency on it. This should happen by default if the jaxrs subsystem is defined.

If the jaxrs subsystem is not present, you can add the dependencies to your deployment with either a jboss-deployment-structure.xml or with a manifest entry. For the manifest entry simply add Dependencies: org.jboss.resteasy.resteasy-client services to your META-INF/MANIFEST.MF in your JAR. Note these are comma separated entries so if you need something like the JSON provider you'd need something like:

Dependencies: org.jboss.resteasy.resteasy-client services, org.jboss.resteasy.resteasy-json-p-provider services

The "services" part just imports the services. This may not be required in some cases.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 James R. Perkins