[RESOLVED] 'Unable to get public no-arg constructor' on extension loading (User Storage SPI)

Hello :wave:

I am using the latest keycloak-x docker image. (Quarkus distribution)
I am trying to create a custom user spi, to try it basically.

I don’t know if it’s a build issue or a code issue, but when Keycloak loads the extension, I got this stacktrace:

my-app-auth-1  | ERROR: Failed to run 'build' command.
my-app-auth-1  | ERROR: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
my-app-auth-1  |        [error]: Build step org.keycloak.quarkus.deployment.KeycloakProcessor#configureProviders threw an exception: java.util.ServiceConfigurationError: org.keycloak.storage.UserStorageProviderFactory: org.example.keycloakuserstore.CustomUserStorageProviderFactory Unable to get public no-arg constructor
my-app-auth-1  |        at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:582)
my-app-auth-1  |        at java.base/java.util.ServiceLoader.getConstructor(ServiceLoader.java:673)
my-app-auth-1  |        at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1233)
my-app-auth-1  |        at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1265)
my-app-auth-1  |        at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1300)
my-app-auth-1  |        at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1385)
my-app-auth-1  |        at org.keycloak.provider.DefaultProviderLoader.load(DefaultProviderLoader.java:60)
my-app-auth-1  |        at org.keycloak.provider.ProviderManager.load(ProviderManager.java:94)
my-app-auth-1  |        at org.keycloak.quarkus.deployment.KeycloakProcessor.loadFactories(KeycloakProcessor.java:404)
my-app-auth-1  |        at org.keycloak.quarkus.deployment.KeycloakProcessor.configureProviders(KeycloakProcessor.java:247)
my-app-auth-1  |        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
my-app-auth-1  |        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
my-app-auth-1  |        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
my-app-auth-1  |        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
my-app-auth-1  |        at io.quarkus.deployment.ExtensionLoader$2.execute(ExtensionLoader.java:887)
my-app-auth-1  |        at io.quarkus.builder.BuildContext.run(BuildContext.java:277)
my-app-auth-1  |        at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
my-app-auth-1  |        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
my-app-auth-1  |        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
my-app-auth-1  |        at java.base/java.lang.Thread.run(Thread.java:829)
my-app-auth-1  |        at org.jboss.threads.JBossThread.run(JBossThread.java:501)
my-app-auth-1  | Caused by: java.lang.NoSuchMethodException: org.example.keycloakuserstore.CustomUserStorageProviderFactory.<init>()
my-app-auth-1  |        at java.base/java.lang.Class.getConstructor0(Class.java:3349)
my-app-auth-1  |        at java.base/java.lang.Class.getConstructor(Class.java:2151)
my-app-auth-1  |        at java.base/java.util.ServiceLoader$1.run(ServiceLoader.java:660)
my-app-auth-1  |        at java.base/java.util.ServiceLoader$1.run(ServiceLoader.java:657)
my-app-auth-1  |        at java.base/java.security.AccessController.doPrivileged(Native Method)
my-app-auth-1  |        at java.base/java.util.ServiceLoader.getConstructor(ServiceLoader.java:668)
my-app-auth-1  |        ... 19 more

Here is the content of the CustomUserStorageProviderFactory:

class CustomUserStorageProviderFactory implements UserStorageProviderFactory<CustomUserStorageProvider> {
    @Override
    public CustomUserStorageProvider create(KeycloakSession session, ComponentModel model) {
        // here you can setup the user storage provider, initiate some connections, etc.

        CustomRepository repository = new CustomRepository();

        return new CustomUserStorageProvider(session, model, repository);
    }

    @Override
    public String getId() {
        return "demo-user-provider";
    }

    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        return ProviderConfigurationBuilder.create()
                .property("myParam", "My Param", "Some Description", ProviderConfigProperty.STRING_TYPE, "some value", null)
                .build();
    }
}

I didn’t overrided the init method because I want the default behavior. But this stack trace line is weird : Caused by: java.lang.NoSuchMethodException: org.example.keycloakuserstore.CustomUserStorageProviderFactory.<init>(). So I tried to add an init method, but it doesn’t change anything.

Here is the content of my gradle build (I’m new to this Java world):

plugins {
    id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.keycloak:keycloak-core:16.0.0'
    implementation 'org.keycloak:keycloak-services:16.0.0'
    implementation 'org.keycloak:keycloak-server-spi:16.0.0'

    compileOnly 'org.projectlombok:lombok:1.18.22'
    annotationProcessor 'org.projectlombok:lombok:1.18.22'
}

test {
    useJUnitPlatform()
}

I do have a META-INF as the doc state, META-INF/services/org.keycloak.storage.UserStorageProviderFactory:

org.example.keycloakuserstore.CustomUserStorageProviderFactory

Can you help me figure it out why it is not loading?

Thanks in advance, and if you need any other informations, please, let me know.

EDIT: I tried to:

  • use Java 17, 11 and 1.8
  • use Maven instead of Gradle with a pom.xml taken from keycloak-quickstarts
  • use other keycloak x version (15 and 16)
    Nothing have worked so far. I’m pretty sure it’s not a big thing, but, eh, I don’t know what it is.

I feel so dumb: adding public before class in CustomUserStorageProviderFactory fixed my issue…