Skip to content

[Enhancement] Add Fallback ClassLoader Strategy for ClientServiceProvider SPI Loading #1207

@alwaysmil

Description

@alwaysmil

Before Creating the Enhancement Request

  • I have confirmed that this should be classified as an enhancement rather than a bug/feature.

Programming Language of the Client

Java

Summary

This is a fallback mechanism in ClientServiceProvider.doLoad() to enhance service provider loading reliability across different classloader environments. When the default ServiceLoader fails to locate an implementation, we attempt loading with an explicit ClassLoader as a degradation strategy.

Motivation

In certain deployment scenarios (e.g., application servers, OSGi containers, or complex modular applications), the Java SPI mechanism using ServiceLoader.load() may fail to discover service implementations due to classloader hierarchy issues. The current implementation throws UnsupportedOperationException immediately when the first loading attempt fails, which doesn't account for scenarios where:

  1. The context classloader differs from the classloader that loaded the ClientServiceProvider interface
  2. Service provider configuration files (META-INF/services) are visible to a different classloader
  3. Thread context classloader is not properly set in containerized environments

This leads to unnecessary runtime failures in otherwise valid configurations.

Describe the Solution You'd Like

Add a fallback loading strategy in the doLoad() method:
static ClientServiceProvider doLoad() { final ServiceLoader<ClientServiceProvider> loaders = ServiceLoader.load(ClientServiceProvider.class); final Iterator<ClientServiceProvider> iterators = loaders.iterator(); if (iterators.hasNext()) { return iterators.next(); } // Fallback: explicitly use the classloader that loaded ClientServiceProvider final ServiceLoader<ClientServiceProvider> fallbackLoaders = ServiceLoader.load(ClientServiceProvider.class, ClientServiceProvider.class.getClassLoader()); final Iterator<ClientServiceProvider> fallbackIterators = fallbackLoaders.iterator(); if (fallbackIterators.hasNext()) { return fallbackIterators.next(); } throw new UnsupportedOperationException("Client service provider not found"); }

Key Changes:

  1. Add fallback ServiceLoader invocation with explicit ClassLoader
  2. Maintain backward compatibility - primary loading strategy remains unchanged
  3. No performance impact on successful first-attempt loads

Describe Alternatives You've Considered

  1. Always use explicit ClassLoader: We considered always using ServiceLoader.load(Class, ClassLoader) with ClientServiceProvider.class.getClassLoader(). However, this might break existing deployments that rely on thread context classloader behavior.
  2. Configurable ClassLoader strategy: Allow users to specify a custom ClassLoader through system properties or configuration. This adds complexity and requires user intervention.
  3. Thread context classloader fallback: Use Thread.currentThread().getContextClassLoader() as fallback. While viable, this is less predictable than using the interface's own classloader.
  4. Service loader caching optimization: Implement aggressive caching with manual invalidation. This addresses a different concern and doesn't solve the classloader visibility issue.

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions