LanguageComponentsLoader { public static Map<String, List<LanguageComponentsProvider>> loadProvidersFromSpi( ClassLoader... classloaders) { Map<String, List<LanguageComponentsProvider>> providers = new TreeMap<>(); BiPredicate<LanguageComponentsProvider, LanguageComponentsProvider> sameProvider = (p1, p2) -> { return Objects.equals(p1.name(), p2.name()) && Objects.equals(p1.getClass(), p2.getClass()) && Objects.equals(p1.componentTypes(), p2.componentTypes()) && Objects.equals(p1.languages(), p2.languages()); }; for (ClassLoader classLoader : classloaders) { for (LanguageComponentsProvider candidate : ServiceLoader.load(LanguageComponentsProvider.class, classLoader)) { for (String language : candidate.languages()) { List<LanguageComponentsProvider> existingProviders = providers.computeIfAbsent(language, key -> new ArrayList<>()); if (existingProviders.stream() .noneMatch(existing -> sameProvider.test(existing, candidate))) { existingProviders.add(candidate); } } } } return providers; }  LoadedLanguages load(); LoadedLanguages load(Map<String, List<LanguageComponentsProvider>> languageProviders); LanguageComponentsLoader limitToLanguages(String... languages); LanguageComponentsLoader limitToAlgorithms(ClusteringAlgorithm... algorithms); LanguageComponentsLoader withResourceLookup(
      Function<LanguageComponentsProvider, ResourceLookup> resourceLookupModifier); static Map<String, List<LanguageComponentsProvider>> loadProvidersFromSpi(
      ClassLoader... classloaders);  }
@Test public void testMultipleSpiDeduplicatesProviders() { Map<String, List<LanguageComponentsProvider>> langs = LanguageComponentsLoader.loadProvidersFromSpi( getClass().getClassLoader(), getClass().getClassLoader()); for (List<LanguageComponentsProvider> perLangProviders : langs.values()) { perLangProviders.stream() .collect(Collectors.groupingBy(LanguageComponentsProvider::getClass)) .values() .forEach( providersByClass -> { Assertions.assertThat(providersByClass).hasSize(1); }); } }