square/okhttp · error · IllegalStateException

Unexpected default trust managers:

Error message

Unexpected default trust managers:

What it means

Inside defaultTrustManager(), the recipe initialises a TrustManagerFactory with the default algorithm and the JVM's default KeyStore (null), then asserts the result is exactly one TrustManager and that it is an X509TrustManager. If the array length is not 1 or the element is not an X509TrustManager, it throws IllegalStateException("Unexpected default trust managers: " + Arrays.toString(trustManagers)). This is a defensive sanity check about the JRE's security provider configuration, not an OkHttp contract.

Source

Thrown at samples/guide/src/main/java/okhttp3/recipes/CustomCipherSuites.java:93

   * Returns the VM's default SSL socket factory, using {@code trustManager} for trusted root
   * certificates.
   */
  private SSLSocketFactory defaultSslSocketFactory(X509TrustManager trustManager)
      throws NoSuchAlgorithmException, KeyManagementException {
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, new TrustManager[] { trustManager }, null);

    return sslContext.getSocketFactory();
  }

  /** Returns a trust manager that trusts the VM's default certificate authorities. */
  private X509TrustManager defaultTrustManager() throws GeneralSecurityException {
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
        TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init((KeyStore) null);
    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
    if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
      throw new IllegalStateException("Unexpected default trust managers:"
          + Arrays.toString(trustManagers));
    }
    return (X509TrustManager) trustManagers[0];
  }

  private String[] javaNames(List<CipherSuite> cipherSuites) {
    String[] result = new String[cipherSuites.size()];
    for (int i = 0; i < result.length; i++) {
      result[i] = cipherSuites.get(i).javaName();
    }
    return result;
  }

  /**
   * An SSL socket factory that forwards all calls to a delegate. Override {@link #configureSocket}
   * to customize a created socket before it is returned.
   */
  static class DelegatingSSLSocketFactory extends SSLSocketFactory {

View on GitHub (pinned to 4fc0831380)

Solutions

  1. Iterate trustManagers and pick the first X509TrustManager instead of asserting exactly one.
  2. Or pass a specific KeyStore (your custom CA bundle) to trustManagerFactory.init(...) so the output is deterministic.
  3. Verify no java.security override or -Djava.security.properties file is reordering providers.
  4. On Android, use AndroidKeyStore explicitly only if intended.

Example fix

// before
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
  throw new IllegalStateException("Unexpected default trust managers:"
      + Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];

// after — find the first X509 manager
for (TrustManager tm : trustManagers) {
  if (tm instanceof X509TrustManager) return (X509TrustManager) tm;
}
throw new IllegalStateException("No X509TrustManager among defaults: "
    + Arrays.toString(trustManagers));
Defensive patterns

Strategy: validation

Validate before calling

// Robust default-trust-manager selection that tolerates multi-manager JVMs
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
X509TrustManager x509 = null;
for (TrustManager tm : tmf.getTrustManagers()) {
  if (tm instanceof X509TrustManager) { x509 = (X509TrustManager) tm; break; }
}
if (x509 == null) {
  throw new GeneralSecurityException("No X509TrustManager from " + Arrays.toString(tmf.getTrustManagers()));
}

Type guard

private static boolean isSingleX509TrustManager(TrustManager[] tms) {
  return tms != null && tms.length == 1 && tms[0] instanceof X509TrustManager;
}

Try / catch

try {
  X509TrustManager tm = defaultTrustManager();
} catch (IllegalStateException e) {
  if (e.getMessage().startsWith("Unexpected default trust managers")) {
    // fall back: scan for the first X509TrustManager instead of asserting exactly one
    throw new SecurityConfigurationException("Unsupported JVM trust-manager layout", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: Constructing the CustomCipherSuites client. Triggers when the JVM returns multiple TrustManagers (some IBM/Rocket runtimes return one per KeyStore type, or PKIX returns multiple under custom security providers), when a custom java.security file replaces the default provider, or when an agent/monitoring tool injects additional trust managers.

Common situations: Running on a non-Oracle/OpenJDK JVM (IBM J9, SapMachine with custom provider) that returns >1 manager; a corporate security agent (e.g. zscaler, fireeye) injecting TLS inspection that alters the default TrustManagerFactory output; an older Android API level where the default algorithm returned a non-X509 manager; testing with a BouncyCastle-first security configuration.

Related errors


AI-assisted analysis of square/okhttp@4fc0831380 (2026-08-04). Data as JSON: /data/errors/cd2fa4b2ae508091.json. Report an issue: GitHub.