Server SPI: Provider, ProviderFactory & KeycloakSession
The server-spi module defines Keycloak's plugin contract: a Provider is a per-session capability instance with a single close() method, a ProviderFactory is a server-wide singleton that is discovered at boot, initialized once, and knows how to create() a Provider per session, and KeycloakSession is the request-scoped hub every subsystem calls through to obtain and cache Provider instances. Nearly every feature area in Keycloak (admin REST, model persistence, user federation, quarkus bootstrap) either implements this Provider/ProviderFactory pair or calls session.getProvider(...), so understanding the create/init/postInit/close lifecycle and the session's lazy caching is the prerequisite for reading anything else in the codebase.
public interface Provider { void close();}
server-spi/src/main/java/org/keycloak/provider/Provider.java · lines 23–27ProviderFactory: the singleton that manufactures Providers
server-spi/src/main/java/org/keycloak/provider/ProviderFactory.java · lines 36–83create(session) is the factory method: given a request-scoped KeycloakSession, it produces one Provider instance for that session. This is the only place a Provider is ever instantiated.
init(config) runs exactly once per server, right after the factory is discovered, and postInit(factory) runs once all factories have been init'd — this two-phase boot lets a factory look up other providers via the session factory only after everyone is initialized.
dependsOn() lets a factory declare a hard ordering requirement: the boot loader guarantees dependency factories finish postInit() (and are closed later) relative to this one, preventing use of an uninitialized provider.
public interface ProviderFactory<T extends Provider> { T create(KeycloakSession session); void init(Config.Scope config); void postInit(KeycloakSessionFactory factory); void close(); String getId(); default int order() { return 0; } default Set<Class<? extends Provider>> dependsOn() { return Collections.emptySet(); }}
/** * Get dedicated provider instance of provider type clazz that was created for this session. If one hasn't been created yet, * find the factory and allocate by calling ProviderFactory.create(KeycloakSession). The provider to use is determined * by the "provider" config entry in keycloak-server boot configuration. See the <a href="https://www.keycloak.org/docs/latest/server_development/index.html#_use_available_providers">Server developer guide</a> for the details. * * * * @param clazz * @param <T> * @return */ <T extends Provider> T getProvider(Class<T> clazz);
server-spi/src/main/java/org/keycloak/models/KeycloakSession.java · lines 41–52How a session actually resolves and caches a provider
services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java · lines 171–190The session builds a cache key from the provider's Class and delegates to the server-wide KeycloakSessionFactory to find which ProviderFactory is configured for that class.
The provider is only created the first time it's asked for in this session — a per-session map (keyed by class/id) makes every subsequent getProvider() call for the same type return the identical instance.
This is the one call site in the whole codebase where ProviderFactory.create(session) actually gets invoked for the plain getProvider(Class) path — everything else is a wrapper around this pattern.
public <T extends Provider> T getProvider(Class<T> clazz) { List<String> key = List.of(clazz.getName()); return getOrCreateProvider(key, () -> factory.getProviderFactory(clazz)); } private <T extends Provider> T getOrCreateProvider(List<String> key, Supplier<ProviderFactory<T>> supplier) { @SuppressWarnings("unchecked") T provider = (T) providers.get(key); if (provider == null) { ProviderFactory<T> providerFactory = supplier.get(); if (providerFactory != null) { provider = providerFactory.create(DefaultKeycloakSession.this); providers.put(key, provider); } } return provider; }