{"id":21384111,"url":"https://github.com/nbbrd/java-service-util","last_synced_at":"2025-07-13T14:31:33.562Z","repository":{"id":37824636,"uuid":"196572475","full_name":"nbbrd/java-service-util","owner":"nbbrd","description":"Java service utilities","archived":false,"fork":false,"pushed_at":"2025-06-28T08:52:12.000Z","size":751,"stargazers_count":5,"open_issues_count":17,"forks_count":1,"subscribers_count":1,"default_branch":"develop","last_synced_at":"2025-06-28T09:47:51.981Z","etag":null,"topics":["java8","library","zero-dependency"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"eupl-1.2","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nbbrd.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-07-12T12:04:54.000Z","updated_at":"2025-06-28T08:52:09.000Z","dependencies_parsed_at":"2023-09-30T09:40:38.771Z","dependency_job_id":"1a98f062-0ac6-4830-b912-8c9117eb43d7","html_url":"https://github.com/nbbrd/java-service-util","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/nbbrd/java-service-util","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nbbrd%2Fjava-service-util","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nbbrd%2Fjava-service-util/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nbbrd%2Fjava-service-util/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nbbrd%2Fjava-service-util/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nbbrd","download_url":"https://codeload.github.com/nbbrd/java-service-util/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nbbrd%2Fjava-service-util/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265155705,"owners_count":23719567,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java8","library","zero-dependency"],"created_at":"2024-11-22T11:38:11.042Z","updated_at":"2025-07-13T14:31:33.033Z","avatar_url":"https://github.com/nbbrd.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Java service utilities\n\n[![Download](https://img.shields.io/github/release/nbbrd/java-service-util.svg)](https://github.com/nbbrd/java-service-util/releases/latest)\n[![Changes](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnbbrd%2Fjava-service-util%2Fbadges%2Funreleased-changes.json)](https://github.com/nbbrd/java-service-util/blob/develop/CHANGELOG.md)\n\nThis library provides some **utilities for Java SPI** ([Service Provider Interface](https://www.baeldung.com/java-spi)).\n\nThe Java SPI is a mechanism that decouples a service from its implementation(s).\nIt allows the creation of extensible or replaceable modules/plugins.\nIt consists of four main components: a service, a service provider interface, some service providers and a service loader.\nIf the service is a single interface then it is the same as a service provider interface.\n\n**Key points:**\n- lightweight library with no dependency\n- no dependency at runtime, all the work is done at compile-time\n- Java 8 minimum requirement\n- has an automatic module name that makes it compatible with [JPMS](https://www.baeldung.com/java-9-modularity) \n\n[ [Components](#components) | [Design](#design) | [Setup](#setup) | [Developing](#developing) | [Contributing](#contributing)  | [Licensing](#licensing) | [Related work](#related-work) | [Alternatives](#alternatives) ]\n\n## Components\n\n| Annotation                               | Purpose                                                    |\n|------------------------------------------|------------------------------------------------------------|\n| [@ServiceProvider](#serviceprovider)     | registers a service provider                               |\n| [@ServiceDefinition](#servicedefinition) | defines a service usage and generates a specialized loader |\n| [@ServiceId](#serviceid)                 | specifies the method used to identify a service provider   |\n| [@ServiceFilter](#servicefilter)         | specifies the method used to filter a service provider     |\n| [@ServiceSorter](#servicesorter)         | specifies the method used to sort a service provider       |\n\n### @ServiceProvider\n\nThe `@ServiceProvider` annotation **registers a service provider** on classpath and modulepath.\n\nFeatures:\n- generates classpath files in `META-INF/services` folder\n- supports multiple registration of one class\n- can infer the service if the provider implements/extends exactly one interface/class\n- checks coherence between classpath and modulepath if `module-info.java` is available\n\nLimitations:\n- detects modulepath `public static provider()` method but doesn't generate a [workaround for classpath](https://github.com/nbbrd/java-service-util/issues/12)\n\n```java\npublic interface FooSPI {}\n\npublic interface BarSPI {}\n\n// 💡 One provider, one service\n@ServiceProvider\npublic class FooProvider implements FooSPI {}\n\n// 💡 One provider, multiple services\n@ServiceProvider ( FooSPI.class )\n@ServiceProvider ( BarSPI.class )\npublic class FooBarProvider implements FooSPI, BarSPI {}\n```\n\n### @ServiceDefinition\nThe `@ServiceDefinition` annotation **defines a service usage and generates a specialized loader** that enforces that specific usage.  \n\nFeatures:\n- generates boilerplate code, thus reducing bugs and improving code coherence\n- improves documentation by declaring services explicitly and generating javadoc\n- checks coherence of service use in modules if `module-info.java` is available\n- allows [identification](#serviceid)\n- allows [filtering](#servicefilter) and [sorting](#servicesorter)\n- allows [batch loading](#batch-type-property) \n- allows [custom backend](#backend-and-cleaner-properties)\n\nLimitations:\n- does not support [type inspection before instantiation](https://github.com/nbbrd/java-service-util/issues/13)\n\nMain properties:\n- [`#quantifier`](#quantifier-property): number of services expected at runtime\n- [`#loaderName`](#loader-name-property): custom qualified name of the loader\n- [`#fallback`](#fallback-property): fallback type for `SINGLE` quantifier\n- [`#batchType`](#batch-type-property): bridge different services and generate providers on the fly\n\nAdvanced properties:\n- [`#mutability`](#mutability-property): on-demand set and reload\n- [`#singleton`](#singleton-property): loader scope\n- [`#wrapper`](#wrapper-property): wrapper type on backend\n- [`#preprocessing`](#preprocessing-property): custom operations on backend\n- [`#backend` `#cleaner`](#backend-and-cleaner-properties): custom service loader\n\n#### Quantifier property\n\nThe `#quantifier` property specifies the **number of services expected at runtime**.\n\nValues:\n\n- `OPTIONAL`: when a service is not guaranteed to be available such as OS-specific API\n  ```java\n  @ServiceDefinition(quantifier = Quantifier.OPTIONAL)\n  public interface WinRegistry {\n  \n    String readString(int hkey, String key, String valueName);\n  \n    int HKEY_LOCAL_MACHINE = 0;\n  \n    static void main(String[] args) {\n      // 💡 Service availability not guaranteed\n      Optional\u003cWinRegistry\u003e optional = WinRegistryLoader.load();\n      optional.map(reg -\u003e reg.readString(\n              HKEY_LOCAL_MACHINE,\n              \"SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\",\n              \"ProductName\"))\n          .ifPresent(System.out::println);\n    }\n  }\n  ```\n  _Source: [nbbrd/service/examples/WinRegistry.java](java-service-examples/src/main/java/nbbrd/service/examples/WinRegistry.java)_\n\n\n- `SINGLE`: when exactly one service is guaranteed to be available\n  ```java\n  @ServiceDefinition(quantifier = Quantifier.SINGLE, fallback = LoggerFinder.FallbackLogger.class)\n  public interface LoggerFinder {\n  \n    Consumer\u003cString\u003e getLogger(String name);\n  \n    class FallbackLogger implements LoggerFinder {\n  \n      @Override\n      public Consumer\u003cString\u003e getLogger(String name) {\n        return msg -\u003e System.out.printf(Locale.ROOT, \"[%s] %s%n\", name, msg);\n      }\n    }\n  \n    static void main(String[] args) {\n      // 💡 Service availability guaranteed\n      LoggerFinder single = LoggerFinderLoader.load();\n      single.getLogger(\"MyClass\").accept(\"some message\");\n    }\n  }\n  ```\n  _Source: [nbbrd/service/examples/LoggerFinder.java](java-service-examples/src/main/java/nbbrd/service/examples/LoggerFinder.java)_\n\n\n- `MULTIPLE`: when several instances of a service could be used at the same time\n  ```java\n  @ServiceDefinition(quantifier = Quantifier.MULTIPLE)\n  public interface Translator {\n  \n    String translate(String text);\n  \n    static void main(String[] args) {\n        // 💡 Multiple services expected\n      List\u003cTranslator\u003e multiple = TranslatorLoader.load();\n      multiple.stream()\n          .map(translator -\u003e translator.translate(\"hello\"))\n          .forEach(System.out::println);\n    }\n  }\n  ```\n  _Source: [nbbrd/service/examples/Translator.java](java-service-examples/src/main/java/nbbrd/service/examples/Translator.java)_\n\n\n#### Loader name property\n\nThe `#loaderName` property specifies the **custom qualified name of the loader**.\n\n```java\n// 💡 Name without interpretation\n@ServiceDefinition(loaderName = \"internal.FooSPILoader\")\npublic interface FooSPI { }\n```\n\nAn empty value generates an automatic name.  \nA non-empty value is interpreted as a [Mustache template](https://mustache.github.io/) with the following tags:\n\n| Tag             | Description                           |\n|-----------------|---------------------------------------|\n| `packageName`   | The package name of the service class |\n| `simpleName`    | The service class name                |\n| `canonicalName` | The full service class name           |\n\n```java\n// 💡 Name with interpretation\n@ServiceDefinition(loaderName = \"internal.{{simpleName}}Loader\")\npublic interface FooSPI { }\n```\n\n#### Fallback property\n\nThe `#fallback` property specifies the **fallback class to use if no service is available**.  \nThis property is only used in conjunction with `Quantifier#SINGLE`.\n\n```java\n@ServiceDefinition(quantifier = Quantifier.SINGLE, fallback = NoOpFooProvider.class)\npublic interface FooSPI { }\n\n// 💡 Provider that does nothing except preventing NPE\npublic class NoOpFooProvider implements FooSPI { }\n```\n\n_Note that a warning is raised at compile time if the fallback is missing \nbut this warning can be disabled with the `@SupressWarning(\"SingleFallbackNotExpected\")` annotation._\n\n#### Batch type property\n\nThe `#batchType` property allows to **bridge different services** and to **generate providers on the fly**.  \nBatch providers are used alongside regular providers.\n\n```java\n@ServiceDefinition(quantifier = Quantifier.MULTIPLE, batchType = SwingColorScheme.Batch.class)\npublic interface SwingColorScheme {\n\n  List\u003cColor\u003e getColors();\n\n  static void main(String[] args) {\n    // 💡 Invisible use of RgbColorScheme\n    SwingColorSchemeLoader.load()\n        .stream()\n        .map(SwingColorScheme::getColors)\n        .forEach(System.out::println);\n  }\n\n  interface Batch {\n    Stream\u003cSwingColorScheme\u003e getProviders();\n  }\n  \n  // 💡 Bridge between SwingColorScheme and RgbColorScheme\n  @ServiceProvider(Batch.class)\n  final class RgbBridge implements Batch {\n\n    @Override\n    public Stream\u003cSwingColorScheme\u003e getProviders() {\n      return RgbColorSchemeLoader.load()\n          .stream()\n          .map(RgbAdapter::new);\n    }\n  }\n\n  // 💡 Regular provider\n  @ServiceProvider(SwingColorScheme.class)\n  final class Cyan implements SwingColorScheme {\n\n    @Override\n    public List\u003cColor\u003e getColors() {\n      return Collections.singletonList(Color.CYAN);\n    }\n  }\n}\n```\n_Source: [nbbrd/service/examples/SwingColorScheme.java](java-service-examples/src/main/java/nbbrd/service/examples/SwingColorScheme.java)_\n\nConstraints:\n1. Batch type must be an interface or an abstract class.\n2. Batch method must be unique.\n\n#### Mutability property\n\nThe `#mutability` property allows **on-demand set and reload** of a loader.\n\n_Example: [nbbrd/service/examples/Messenger.java](java-service-examples/src/main/java/nbbrd/service/examples/Messenger.java)_\n\n⚠️ _This is a complex mechanism that targets specific usages. It will be removed and/or simplified in a future release._\n\n#### Singleton property\n\nThe `#singleton` property specifies the **loader scope**.\n\n_Example: [nbbrd/service/examples/StatefulAlgorithm.java](java-service-examples/src/main/java/nbbrd/service/examples/StatefulAlgorithm.java)\nand [nbbrd/service/examples/SystemSettings.java](java-service-examples/src/main/java/nbbrd/service/examples/SystemSettings.java)_\n\n⚠️ _This is a complex mechanism that targets specific usages. It will be removed and/or simplified in a future release._\n\n#### Wrapper property\n\nThe `#wrapper` property allows **service decoration** before any map/filter/sort operation.\n\n_Example: `TODO`_\n\n⚠️ _This is a complex mechanism that targets specific usages. It will be removed and/or simplified in a future release._\n\n#### Preprocessing property\n\nThe `#preprocessor` property allows **custom operations on backend** before any map/filter/sort operation.\n\n_Example: `TODO`_\n\n⚠️ _This is a complex mechanism that targets specific usages. It will be removed and/or simplified in a future release._\n\n#### Backend and cleaner properties\n\nThe `#backend` and `#cleaner` properties allow to use a **custom service loader** such as [NetBeans Lookup](https://search.maven.org/search?q=g:org.netbeans.api%20AND%20a:org-openide-util-lookup\u0026core=gav) instead of JDK `ServiceLoader`.\n\n_Example: [nbbrd/service/examples/IconProvider.java](java-service-examples/src/main/java/nbbrd/service/examples/IconProvider.java)_\n\n⚠️ _This is a complex mechanism that targets specific usages. It will be removed and/or simplified in a future release._\n\n### @ServiceId\n\nThe `@ServiceId` annotation **specifies the method used to identify a service provider**.\n\nProperties:\n- `#pattern`: specifies the regex pattern that the ID is expected to match\n\n```java\n@ServiceDefinition(quantifier = Quantifier.MULTIPLE)\npublic interface HashAlgorithm {\n\n  // 💡 Enforce service naming\n  @ServiceId(pattern = ServiceId.SCREAMING_KEBAB_CASE)\n  String getName();\n\n  String hashToHex(byte[] input);\n\n  static void main(String[] args) {\n    // 💡 Retrieve service by name\n    HashAlgorithmLoader.load()\n      .stream()\n      .filter(algo -\u003e algo.getName().equals(\"SHA-256\"))\n      .findFirst()\n      .map(algo -\u003e algo.hashToHex(\"hello\".getBytes(UTF_8)))\n      .ifPresent(System.out::println);\n  }\n}\n```\n_Source: [nbbrd/service/examples/HashAlgorithm.java](java-service-examples/src/main/java/nbbrd/service/examples/HashAlgorithm.java)_\n\nCharacteristics:\n- The `#pattern` property is used as a filter.\n- The `#pattern` property is available as a static field in the loader.\n\nConstraints:\n1. It only applies to methods of a service.\n2. It does not apply to static methods.\n3. The annotated method must have no-args.\n4. The annotated method must return String.\n5. The annotated method must be unique.\n6. The annotated method must not throw checked exceptions.\n7. Its pattern must be valid.\n\n### @ServiceFilter\n\nThe `@ServiceFilter` annotation **specifies the method used to filter a service provider**.\n\nProperties:\n- `#position`: sets the filter ordering in case of multiple filters\n- `#negate`: applies a logical negation\n\n```java\n@ServiceDefinition\npublic interface FileSearch {\n\n  List\u003cFile\u003e searchByName(String name);\n\n  // 💡 General filter\n  @ServiceFilter(position = 1)\n  boolean isAvailableOnCurrentOS();\n\n  // 💡 Specific filter\n  @ServiceFilter(position = 2, negate = true)\n  boolean isDisabledBySystemProperty();\n\n  static void main(String[] args) {\n    FileSearchLoader.load()\n      .map(search -\u003e search.searchByName(\".xlsx\"))\n      .orElseGet(Collections::emptyList)\n      .forEach(System.out::println);\n  }\n}\n```\n_Source: [nbbrd/service/examples/FileSearch.java](java-service-examples/src/main/java/nbbrd/service/examples/FileSearch.java)_\n\nCharacteristics:\n- There is no limit to the number of annotations per service.\n- Filtering is done before sorting.\n\nConstraints:\n1. It only applies to methods of a service.\n2. It does not apply to static methods.\n3. The annotated method must have no-args.\n4. The annotated method must return boolean.\n5. The annotated method must not throw checked exceptions.\n\n### @ServiceSorter\n\nThe `@ServiceSorter` annotation **specifies the method used to sort a service provider**.\n\nProperties:\n- `#position`: sets the sorter ordering in case of multiple sorters\n- `#reverse`: applies a reverse sorting\n\n```java\n@ServiceDefinition\npublic interface LargeLanguageModel {\n\n  String summarize(String text);\n\n  // 💡 Maximize quality\n  @ServiceSorter(position = 1, reverse = true)\n  int getQuality();\n\n  // 💡 Minimize cost\n  @ServiceSorter(position = 2)\n  int getCost();\n\n  static void main(String[] args) {\n    LargeLanguageModelLoader.load()\n      .map(search -\u003e search.summarize(\"bla bla bla\"))\n      .ifPresent(System.out::println);\n  }\n}\n```\n_Source: [nbbrd/service/examples/LargeLanguageModel.java](java-service-examples/src/main/java/nbbrd/service/examples/LargeLanguageModel.java)_\n\nCharacteristics:\n- There is no limit to the number of annotations per service.\n- Sorting is done after filtering.\n\nConstraints:\n1. It only applies to methods of a service.\n2. It does not apply to static methods.\n3. The annotated method must have no-args.\n4. The annotated method must return double, int, long or comparable.\n5. The annotated method must not throw checked exceptions.\n\n## Design\n\n### API vs SPI\n\nIn some cases it is better to have a clear separation between API and SPI.\n\nAn API is designed to be called and used. It should be simple and foolproof.  \nAn SPI is designed to be extended and implemented. It can be complex but should be performant.\n\nHere is an example on how to do it:\n\n```java\npublic final class FileType {\n\n  // 💡 API: designed to be called and used\n  public static Optional\u003cString\u003e probeContentType(Path file) throws IOException {\n    for (FileTypeSpi probe : FileTypeSpiLoader.get()) {\n      String result = probe.getContentTypeOrNull(file);\n      if (result != null) return Optional.of(result);\n    }\n    return Optional.empty();\n  }\n\n  public static void main(String[] args) throws IOException {\n    for (String file : Arrays.asList(\"hello.csv\", \"stuff.txt\")) {\n      System.out.println(file + \": \" + FileType.probeContentType(Paths.get(file)).orElse(\"?\"));\n    }\n  }\n\n  // 💡 SPI: designed to be extended and implemented\n  @ServiceDefinition(\n      quantifier = Quantifier.MULTIPLE,\n      loaderName = \"internal.{{canonicalName}}Loader\",\n      singleton = true\n  )\n  public interface FileTypeSpi {\n\n    enum Accuracy {HIGH, LOW}\n\n    String getContentTypeOrNull(Path file) throws IOException;\n\n    @ServiceSorter\n    Accuracy getAccuracy();\n  }\n}\n```\n_Source: [nbbrd/service/examples/FileType.java](java-service-examples/src/main/java/nbbrd/service/examples/FileType.java)_\n\n## Setup\n\n```xml\n\u003cdependencies\u003e\n  \u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.nbbrd.java-service-util\u003c/groupId\u003e\n  \u003cartifactId\u003ejava-service-annotation\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003cscope\u003eprovided\u003c/scope\u003e\n  \u003c/dependency\u003e\n\u003c/dependencies\u003e\n\n\u003cbuild\u003e\n  \u003cplugins\u003e\n  \u003cplugin\u003e\n    \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n    \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n    \u003cconfiguration\u003e\n    \u003cannotationProcessorPaths\u003e\n      \u003cpath\u003e\n      \u003cgroupId\u003ecom.github.nbbrd.java-service-util\u003c/groupId\u003e\n      \u003cartifactId\u003ejava-service-processor\u003c/artifactId\u003e\n      \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n      \u003c/path\u003e\n    \u003c/annotationProcessorPaths\u003e\n    \u003c/configuration\u003e\n  \u003c/plugin\u003e\n  \u003c/plugins\u003e\n\u003c/build\u003e\n\n```\nAlternate setup if the IDE doesn't detect the processor:\n```xml\n\u003cdependencies\u003e\n  \u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.nbbrd.java-service-util\u003c/groupId\u003e\n  \u003cartifactId\u003ejava-service-processor\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003cscope\u003eprovided\u003c/scope\u003e\n  \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\n## Developing\n\nThis project is written in Java and uses [Apache Maven](https://maven.apache.org/) as a build tool.  \nIt requires [Java 8 as minimum version](https://whichjdk.com/) and all its dependencies are hosted on [Maven Central](https://search.maven.org/).\n\nThe code can be build using any IDE or by just type-in the following commands in a terminal:\n\n```shell\ngit clone https://github.com/nbbrd/java-service-util.git\ncd java-service-util\nmvn clean install\n```\n\n## Contributing\n\nAny contribution is welcome and should be done through pull requests and/or issues.\n\n## Licensing\n\nThe code of this project is licensed under the [European Union Public Licence (EUPL)](https://joinup.ec.europa.eu/page/eupl-text-11-12).\n\n## Related work\n\nThis project is not the only one using with the SPI mechanism.  \nHere is a non-exhaustive list of related work:\n- [NetBeans Lookup](https://search.maven.org/search?q=g:org.netbeans.api%20AND%20a:org-openide-util-lookup\u0026core=gav)\n- [Google AutoService](https://www.baeldung.com/google-autoservice)\n- [TOOListicon SPI-Annotation-Processor](https://github.com/toolisticon/SPI-Annotation-Processor)\n- [Kordamp Jipsy](https://github.com/kordamp/jipsy)\n\n## Alternatives\n\nThe SPI mechanism is not suitable for all use cases. Here are some alternatives:\n- [Dependency injection](https://github.com/akullpp/awesome-java?tab=readme-ov-file#dependency-injection)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnbbrd%2Fjava-service-util","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnbbrd%2Fjava-service-util","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnbbrd%2Fjava-service-util/lists"}