KeycloakSession get JpaConnectionProvider.class return null

My scenario is that I want to add users to h2 database. My setup for restful provider is working without a problem. I can curl and get the response from the keycloak spi rest provider successfully. However, the problems is that my code fails to acquire EntityManager.

The code looks like this

public class MyResourceProvider implements RealmResourceProvider {
    KeycloakSession session;
    public MyResourceProvider(KeycloakSession session) {
        this.session = session;
    }
    @Override
    public Object getResource() {
        return new MyResource(session);
    }
}

public class MyResource {
    KeycloakSession session;
    public MyResource() {
        this.session = session;
            final Set<JpaConnectionProvider> set = session.getAllProviders(JpaConnectionProvider.class); 
            set.forEach((e) -> log.info("Element in the set: {}", e) ); // this prints element in the set: org.keycloak.connections.jpa.DefaultJpaConnectionProvider@39118fdc
            this.em = session.getProvider(DefaultJpaConnectionProvider.class, "my-store").getEntityManager(); // this line throw NPE error and getProvider() with DefaultJpaConnectionProvider.class or JpaConnectionProvider.class all throws NPE error
    }
} 

The error message is

this throws “ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-1) Uncaught server error: java.lang.NullPointerException: Cannot invoke “org.keycloak.connections.jpa.DefaultJpaConnectionProvider.getEntityManager()” because the return value of “org.keycloak.models.KeycloakSession.getProvider(java.lang.Class, String)” is null”

It seems that jpa setting is not configured, but I have persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence ...>
    <persistence-unit name="my-store" transaction-type="JTA">
        <class>myorg.persistence.MyUserEntity</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
            <!-- Sets the name of the datasource to be the same as the datasource name in quarkus.properties-->
            <property name="hibernate.connection.datasource" value="my-store" />
            <property name="jakarta.persistence.transactionType" value="JTA" />
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="hibernate.show_sql" value="false" />
        </properties>
    </persistence-unit>
</persistence>

And quarkus.properties is copied to /opt/keycloak/conf (with or without copying this properties file to keycloak’s conf dir, the error is the same.

quarkus.datasource.my-store.db-kind=h2
quarkus.datasource.my-store.username=sa
quarkus.datasource.my-store.password=password
quarkus.datasource.my-store.jdbc.url=jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1

In Dockerfile (again with or without KC_* ENV variables, the error remains the same)

... # the entire Dockerfile actually is copied from the keycloak container setup doc, it is working fine without a problem, except getProvider(JPAConnectionProvider.class) error.
COPY --from=builder /opt/keycloak/providers /opt/keycloak/providers

ENV KC_DB=dev-file
ENV KC_DB_URL=jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1
ENV KC_DB_USERNAME=sa
ENV KC_DB_PASSWORD=password
ENV KC_HOSTNAME=localhost
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]

The docker command is

docker run -d --name $(KEYCLOAK_NAME) -p 8080:8080 \
		-e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin \
		quay.io/keycloak/keycloak:latest \
		start-dev

All setting is just for local testing at the moment. so password is not set to production purpose. Anything I miss or misconfigure? I appreciate any advice. Thanks.

Ok it looks like the EntityManager needs to be annotated with @PersistenceContext(unitName="keycloak-default"), which is the default unit name used by the entire keycloak provider.

    public class MyResource {    
        ...
        @PersistenceContext(unitName="keycloak-default")
        EntityManager em;
        ...
        public MyResource(KeycloakSession session) {
            this.em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
        }
        ...
    }