Why Does Keycloak PublicKey Retrieval Hang in Spring Boot? Timeout Fixes Explained

This article analyzes intermittent page failures caused by blocked Keycloak public‑key retrieval in a Spring Boot application, explains how default HTTP client timeouts of –1 lead to indefinite waits, and provides a filter‑based solution to set proper timeouts and adjust internal/external URLs.

GuanYuan Data Tech Team
GuanYuan Data Tech Team
GuanYuan Data Tech Team
Why Does Keycloak PublicKey Retrieval Hang in Spring Boot? Timeout Fixes Explained

Problem Description

The project uses Keycloak for unified authentication with a Spring Boot backend. Occasionally the page becomes inaccessible, then recovers after a while. Thread dump shows a blocked thread waiting on

org.keycloak.adapters.rotation.JWKPublicKeyLocator.getPublicKey

:

"http-nio-8081-exec-9" #61 daemon prio=5 os_prio=0 tid=0x00007efc702c1000 nid=0x4c waiting for monitor entry
   java.lang.Thread.State: BLOCKED (on object monitor)
        at org.keycloak.adapters.rotation.JWKPublicKeyLocator.getPublicKey(JWKPublicKeyLocator.java:60)
        - waiting to lock <0x00000003f1888968> (a org.keycloak.adapters.rotation.JWKPublicKeyLocator)
        ...

The blockage occurs when the singleton JWKPublicKeyLocator holds a synchronized lock while fetching the public key from Keycloak.

Root Cause Analysis

The JWKPublicKeyLocator synchronizes the request, and the underlying HTTP client uses Apache HttpClient with default RequestConfig values of -1 for connectionRequestTimeout, connectTimeout, and socketTimeout. A value of -1 means no timeout, so a slow or failed network call blocks the lock indefinitely, causing other threads to wait.

Keycloak configuration in application.properties provides only the base URL:

keycloak.realm=realmId
keycloak.resource=clientId
keycloak.auth-server-url=http://127.0.0.1:8180/auth

When the external network is unstable, DNS resolution and load‑balancing add latency, and the default timeout settings prevent the request from failing fast.

Solution

Two main actions are required:

Set explicit timeouts for the HTTP client. A custom Spring filter modifies the global KeycloakDeployment client parameters:

@Component
public class ChangeTimeOutFilter implements Filter {
    @Resource
    private AdapterDeploymentContext deploymentContext;
    private volatile boolean deploymentChanged = false;
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpFacade facade = new SimpleHttpFacade((HttpServletRequest) request, (HttpServletResponse) response);
        KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
        if (deployment == null) { chain.doFilter(request, response); return; }
        if (deploymentChanged) { chain.doFilter(request, response); return; }
        HttpParams params = deployment.getClient().getParams();
        params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 10000);
        params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000);
        params.setLongParameter(ClientPNames.CONN_MANAGER_TIMEOUT, 10000L);
        deploymentChanged = true;
        chain.doFilter(request, response);
    }
}

Testing with a very short timeout produces a clear SocketTimeoutException, confirming the configuration works.

Use internal IP for Keycloak calls while returning the external URL to the front‑end. Change keycloak.auth-server-url to the internal address (e.g., http://172.17.0.1:8180/auth) and add a separate property for the external URL that is only sent to the browser.

If the front‑end receives a different realm URL than the one stored in the deployment, token verification fails with a RealmUrlCheck error. To align them, a second filter rewrites the realmInfoUrl field via reflection:

@Component
public class ChangeTimeOutFilter implements Filter {
    @Resource
    private AdapterDeploymentContext deploymentContext;
    @Resource
    private KeyCloakConfig keyCloakConfig;
    private static Field realmInfoUrlFd;
    static {
        try { realmInfoUrlFd = KeycloakDeployment.class.getDeclaredField("realmInfoUrl"); realmInfoUrlFd.setAccessible(true); }
        catch (Exception ignored) {}
    }
    private volatile boolean deploymentChanged = false;
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpFacade facade = new SimpleHttpFacade((HttpServletRequest) request, (HttpServletResponse) response);
        KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
        if (deployment == null) { chain.doFilter(request, response); return; }
        if (!deploymentChanged) {
            String realmInfoUrl = deployment.getRealmInfoUrl();
            if (!StringUtils.isBlank(realmInfoUrl)) {
                realmInfoUrl = realmInfoUrl.replaceAll(keyCloakConfig.getInnerUrl(), keyCloakConfig.getAuthUrl());
                try { realmInfoUrlFd.set(deployment, realmInfoUrl); } catch (Exception ignored) {}
            }
            HttpParams params = deployment.getClient().getParams();
            params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 10000);
            params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000);
            params.setLongParameter(ClientPNames.CONN_MANAGER_TIMEOUT, 10000L);
            deploymentChanged = true;
        }
        chain.doFilter(request, response);
    }
}

After applying these changes, the intermittent “page cannot open” issue disappears.

Keycloak request flow diagram
Keycloak request flow diagram
Token verification error screenshot
Token verification error screenshot

Future posts will continue to share similar troubleshooting experiences from the Guandata technical team.

JavaSpring BootAuthenticationTimeoutFilterKeycloak
GuanYuan Data Tech Team
Written by

GuanYuan Data Tech Team

Practical insights from the GuanYuan Data Tech Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.