You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by ud...@apache.org on 2020/07/02 18:10:49 UTC

[geode] 13/29: GEODE-8190 - Completed GeodeJarModuleFinder to register and load Modules. (#5234)

This is an automated email from the ASF dual-hosted git repository.

udo pushed a commit to branch feature/GEODE-8294
in repository https://gitbox.apache.org/repos/asf/geode.git

commit fe41cb52152eb14d19043d09c28632e62723a429
Author: Patrick Johnson <pj...@pivotal.io>
AuthorDate: Wed Jun 10 19:51:34 2020 -0700

    GEODE-8190 - Completed GeodeJarModuleFinder to register and load Modules. (#5234)
    
    JBossModuleServiceImpl can now register and load Modules using ModuleDescriptor. The ModuleDescriptor
    can now contain source paths to be included in the module as code sources or
    have dependencies on other modules.
    
    The same functionality can now be represented/retrieved from the Manifest file
    contained within the jar files.
    
    Amended gradle scripts for geode-common-services and geode-common to now generate
    manifest files containing "Class-Path" dependencies and project module dependencies
    described in the "Dependent-Modules" attribute in the manifest file.
---
 .../integrationTest/resources/assembly_content.txt |  11 +-
 geode-common-services/build.gradle                 |  38 +-
 .../services/management/ManagementService.java     |   5 +-
 .../geode/services/module/ModuleDescriptor.java    |  93 ++-
 .../geode/services/module/ModuleService.java       |  86 ++-
 .../ModuleServiceResult.java}                      |  39 +-
 .../org/apache/geode/services/result/Result.java   |  87 +++
 .../src/test/resources/expected-pom.xml            |  10 +-
 geode-common/build.gradle                          |  37 +
 geode-modules/build.gradle                         | 174 +++--
 .../services/module/impl/GeodeModuleLoader.java    |  61 --
 .../module/impl/JBossModuleServiceImpl.java        | 270 ++++---
 .../internal/finder/DelegatingModuleFinder.java    |  67 ++
 .../module/internal/loader/GeodeModuleLoader.java  | 129 ++++
 .../apache/geode/services/result/impl/Failure.java |  90 +++
 .../apache/geode/services/result/impl/Success.java |  91 +++
 .../org/jboss/modules/GeodeJarModuleFinder.java    | 293 ++++++++
 .../module/impl/JBossModuleServiceImplTest.java    | 616 ----------------
 ...erviceImplWithoutPopulatedManifestFileTest.java | 819 +++++++++++++++++++++
 ...esServiceImplWithPopulatedManifestFileTest.java | 144 ++++
 .../jboss/modules/GeodeJarModuleFinderTest.java    | 231 ++++++
 geode-modules/src/test/resources/expected-pom.xml  |  28 +-
 .../META-INF/services/org.apache.geode.TestService |   1 -
 .../java/org/apache/geode/ModuleService1.java}     |   2 +-
 .../META-INF/services/org.apache.geode.TestService |   1 +
 .../java/org/apache/geode/ModuleService1.java}     |   2 +-
 .../META-INF/services/org.apache.geode.TestService |   1 +
 .../META-INF/services/org.apache.geode.TestService |   1 -
 .../java/org/apache/geode/ModuleService2.java}     |   2 +-
 .../META-INF/services/org.apache.geode.TestService |   1 +
 .../java/org/apache/geode/ModuleService2.java}     |   2 +-
 .../META-INF/services/org.apache.geode.TestService |   1 +
 .../META-INF/services/org.apache.geode.TestService |   1 -
 .../java/org/apache/geode/ModuleService3.java}     |   2 +-
 .../META-INF/services/org.apache.geode.TestService |   1 +
 .../java/org/apache/geode/ModuleService3.java}     |   2 +-
 .../META-INF/services/org.apache.geode.TestService |   1 +
 .../java/org/apache/geode/ModuleService4.java}     |   2 +-
 .../java/org/apache/geode/ModuleService4.java}     |   2 +-
 .../java/org/apache/geode/ModuleService5.java}     |   2 +-
 40 files changed, 2510 insertions(+), 936 deletions(-)

diff --git a/geode-assembly/src/integrationTest/resources/assembly_content.txt b/geode-assembly/src/integrationTest/resources/assembly_content.txt
index 8b92c69..e05c753 100644
--- a/geode-assembly/src/integrationTest/resources/assembly_content.txt
+++ b/geode-assembly/src/integrationTest/resources/assembly_content.txt
@@ -974,7 +974,6 @@ javadoc/org/apache/geode/services/management/package-tree.html
 javadoc/org/apache/geode/services/module/ModuleDescriptor.Builder.html
 javadoc/org/apache/geode/services/module/ModuleDescriptor.html
 javadoc/org/apache/geode/services/module/ModuleService.html
-javadoc/org/apache/geode/services/module/impl/GeodeModuleLoader.html
 javadoc/org/apache/geode/services/module/impl/JBossModuleServiceImpl.html
 javadoc/org/apache/geode/services/module/impl/package-frame.html
 javadoc/org/apache/geode/services/module/impl/package-summary.html
@@ -982,6 +981,16 @@ javadoc/org/apache/geode/services/module/impl/package-tree.html
 javadoc/org/apache/geode/services/module/package-frame.html
 javadoc/org/apache/geode/services/module/package-summary.html
 javadoc/org/apache/geode/services/module/package-tree.html
+javadoc/org/apache/geode/services/result/ModuleServiceResult.html
+javadoc/org/apache/geode/services/result/Result.html
+javadoc/org/apache/geode/services/result/impl/Failure.html
+javadoc/org/apache/geode/services/result/impl/Success.html
+javadoc/org/apache/geode/services/result/impl/package-frame.html
+javadoc/org/apache/geode/services/result/impl/package-summary.html
+javadoc/org/apache/geode/services/result/impl/package-tree.html
+javadoc/org/apache/geode/services/result/package-frame.html
+javadoc/org/apache/geode/services/result/package-summary.html
+javadoc/org/apache/geode/services/result/package-tree.html
 javadoc/overview-frame.html
 javadoc/overview-summary.html
 javadoc/overview-tree.html
diff --git a/geode-common-services/build.gradle b/geode-common-services/build.gradle
index da983cf..6225974 100644
--- a/geode-common-services/build.gradle
+++ b/geode-common-services/build.gradle
@@ -27,7 +27,43 @@ repositories {
 dependencies {
     compile(platform(project(':boms:geode-all-bom')))
 
+    compile('org.apache.commons:commons-lang3')
+
     compile(project(':geode-common'))
+}
+
+def geodeProjects = ['geode-common-services', 'geode-common']
+
+jar {
+    doFirst {
+        def projectDependencies = []
+        def runtimeList = []
+
+        configurations.runtimeClasspath
+                .collect { it.name - ".jar" }
+                .each { dependency ->
+                    if (dependency.startsWith("geode-")) {
+                        projectDependencies.add(dependency)
+                    } else {
+                        runtimeList.add(dependency)
+                    }
+                }
 
-    compile(project(':geode-core'))
+        projectDependencies.each { projectDependency ->
+            geodeProjects.each { geodeProject ->
+                if (projectDependency.contains(geodeProject)) {
+                    def parentProject = project(":$geodeProject")
+                    if (parentProject != null) {
+                        def collect = parentProject.configurations.runtimeClasspath.collect { it.name - ".jar" }
+                        runtimeList.removeAll(collect)
+                        projectDependencies.removeAll(collect)
+                    }
+                }
+            }
+        }
+        manifest {
+            attributes("Class-Path": runtimeList.join(' '))
+            attributes("Dependent-Modules": projectDependencies.join(' '))
+        }
+    }
 }
diff --git a/geode-common-services/src/main/java/org/apache/geode/services/management/ManagementService.java b/geode-common-services/src/main/java/org/apache/geode/services/management/ManagementService.java
index 3e9ca47..5ae23c5 100644
--- a/geode-common-services/src/main/java/org/apache/geode/services/management/ManagementService.java
+++ b/geode-common-services/src/main/java/org/apache/geode/services/management/ManagementService.java
@@ -15,10 +15,7 @@
 
 package org.apache.geode.services.management;
 
-import java.util.Properties;
-
 import org.apache.geode.annotations.Experimental;
-import org.apache.geode.cache.Cache;
 
 /**
  * Entry point for creating a cache and bootstrapping Geode using the BootstrappingService
@@ -35,5 +32,5 @@ public interface ManagementService {
    *
    * @throws Exception is Cache cannot be created.
    */
-  Cache createCache(Properties properties) throws Exception;
+  // Cache createCache(Properties properties) throws Exception;
 }
diff --git a/geode-common-services/src/main/java/org/apache/geode/services/module/ModuleDescriptor.java b/geode-common-services/src/main/java/org/apache/geode/services/module/ModuleDescriptor.java
index 3b84130..dc8182d 100644
--- a/geode-common-services/src/main/java/org/apache/geode/services/module/ModuleDescriptor.java
+++ b/geode-common-services/src/main/java/org/apache/geode/services/module/ModuleDescriptor.java
@@ -16,56 +16,70 @@
 package org.apache.geode.services.module;
 
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
 
 import org.apache.geode.annotations.Experimental;
 
 /**
- * Holds information to describe a classloader-isolated module including how to create it.
+ * Holds configuration information to describe a classloader-isolated module.
  *
  * @see Builder
  * @see ModuleService
- *
- * @since Geode 1.13.0
+ * @since Geode 1.14.0
  */
 @Experimental
 public class ModuleDescriptor {
 
-  private String name;
+  // The name of the module
+  private final String name;
 
-  private String version;
+  // The version module. Maybe be null
+  private final String version;
 
-  private List<String> sources;
+  // A collection of source paths for the module
+  private final Set<String> resourceJarPaths;
 
-  private List<String> dependencies;
+  // A collection of module names on which this module depends on
+  private final Set<String> dependencies;
 
-  private ModuleDescriptor(String name, String version, List<String> sources,
-      List<String> dependencies) {
+  private ModuleDescriptor(String name, String version, Set<String> resourceJarPaths,
+      Set<String> dependencies) {
     this.name = name;
     this.version = version;
-    this.sources = sources;
+    this.resourceJarPaths = resourceJarPaths;
     this.dependencies = dependencies;
   }
 
-  public String getName() {
-    return name;
-  }
-
-  public String getVersion() {
-    return version;
-  }
-
-  public List<String> getSources() {
-    return sources;
+  /**
+   * A collection of resource paths that are to be loaded by the module
+   *
+   * @return a collection of resource paths for the module
+   */
+  public Set<String> getResourceJarPaths() {
+    return resourceJarPaths;
   }
 
-  public List<String> getDependedOnModules() {
+  /**
+   * A collection of module names on which this module is dependent on
+   *
+   * @return A collection of module names on which this module is dependent on
+   */
+  public Set<String> getDependedOnModules() {
     return dependencies;
   }
 
-  public String getVersionedName() {
-    return name + ":" + version;
+  /**
+   * The name of the module concatenated with the version provided. In the case of the version being
+   * null
+   * the name does not contain the version.
+   *
+   */
+  public String getName() {
+    return name + (version != null ? "-" + version : "");
   }
 
   /**
@@ -75,21 +89,38 @@ public class ModuleDescriptor {
 
     private final String name;
     private final String version;
-    private List<String> dependencies = Collections.emptyList();
-    private List<String> sources = Collections.emptyList();
+    private final Set<String> dependencies = new HashSet<>();
+    private final Set<String> sources = new HashSet<>();
+
+    public Builder(String name) {
+      this(name, null);
+    }
 
     public Builder(String name, String version) {
-      this.name = name;
+      if (!StringUtils.isEmpty(name)) {
+        this.name = name;
+      } else {
+        throw new IllegalArgumentException(
+            "Name in the ModuleDescriptor.Builder cannot be null or empty");
+      }
       this.version = version;
     }
 
-    public Builder fromSources(String... sources) {
-      this.sources = Arrays.asList(sources);
+    public Builder fromResourcePaths(String... resourcePaths) {
+      return fromResourcePaths(Arrays.asList(resourcePaths));
+    }
+
+    public Builder fromResourcePaths(Collection<String> resourcePaths) {
+      this.sources.addAll(resourcePaths);
       return this;
     }
 
     public Builder dependsOnModules(String... dependencies) {
-      this.dependencies = Arrays.asList(dependencies);
+      return this.dependsOnModules(Arrays.asList(dependencies));
+    }
+
+    public Builder dependsOnModules(Collection<String> dependencies) {
+      this.dependencies.addAll(dependencies);
       return this;
     }
 
diff --git a/geode-common-services/src/main/java/org/apache/geode/services/module/ModuleService.java b/geode-common-services/src/main/java/org/apache/geode/services/module/ModuleService.java
index 436398d..e6c693c 100644
--- a/geode-common-services/src/main/java/org/apache/geode/services/module/ModuleService.java
+++ b/geode-common-services/src/main/java/org/apache/geode/services/module/ModuleService.java
@@ -15,14 +15,16 @@
 
 package org.apache.geode.services.module;
 
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import org.apache.geode.annotations.Experimental;
+import org.apache.geode.services.result.ModuleServiceResult;
 
 /**
  * Loads and unloads modules and services in a classloader-isolated manner.
  *
- * @since Geode 1.13.0
+ * @since Geode 1.14.0
  */
 @Experimental
 public interface ModuleService {
@@ -32,9 +34,30 @@ public interface ModuleService {
    *
    * @param moduleDescriptor description of the module to be loaded and information necessary to
    *        load it.
-   * @return true on success, false if the module could not be loaded.
+   * @return {@link ModuleServiceResult}. This type represents either Success or Failure. Using
+   *         {@link ModuleServiceResult#isSuccessful()} returns a {@literal Boolean} in the case of
+   *         success.
+   *         Upon success use {@link ModuleServiceResult#getMessage()} to get the result and upon
+   *         failure
+   *         used {@link ModuleServiceResult#getErrorMessage()} to get the error message of the
+   *         failure.
    */
-  boolean loadModule(ModuleDescriptor moduleDescriptor);
+  ModuleServiceResult<Boolean> loadModule(ModuleDescriptor moduleDescriptor);
+
+  /**
+   * Registers a module from the {@link ModuleDescriptor}.
+   *
+   * @param moduleDescriptor description of the module to be loaded and information necessary to
+   *        register it.
+   * @return {@link ModuleServiceResult}. This type represents either Success or Failure. Using
+   *         {@link ModuleServiceResult#isSuccessful()} returns a {@literal Boolean} in the case of
+   *         success.
+   *         Upon success use {@link ModuleServiceResult#getMessage()} to get the result and upon
+   *         failure
+   *         used {@link ModuleServiceResult#getErrorMessage()} to get the error message of the
+   *         failure.
+   */
+  ModuleServiceResult<Boolean> registerModule(ModuleDescriptor moduleDescriptor);
 
   /**
    * Unloads a previously loaded module.
@@ -42,15 +65,60 @@ public interface ModuleService {
    * modules that are not dependencies of other modules.
    *
    * @param moduleName name of the module to be unloaded.
-   * @return true on success, false if the module could not be unloaded.
+   * @return {@link ModuleServiceResult}. This type represents either Success or Failure. Using
+   *         {@link ModuleServiceResult#isSuccessful()} returns a {@literal Boolean} in the case of
+   *         success.
+   *         Upon success use {@link ModuleServiceResult#getMessage()} to get the result and upon
+   *         failure
+   *         used {@link ModuleServiceResult#getErrorMessage()} to get the error message of the
+   *         failure.
    */
-  boolean unloadModule(String moduleName);
+  ModuleServiceResult<Boolean> unloadModule(String moduleName);
 
   /**
-   * Loads and returns a service instance for an interface.
+   * Loads and returns a service instance from any loaded module.
    *
    * @param service interface type to load and instantiate an implementation of.
-   * @return An instance of an implementation of service
+   * @return {@link ModuleServiceResult}. This type represents either Success or Failure. Using
+   *         {@link ModuleServiceResult#isSuccessful()} returns a {@literal Map<String,Set<T>>} in
+   *         the case of success.
+   *         The result is a Map of {@code <ModuleName,Set<ServiceInstance>>}. Where moduleName
+   *         corresponds
+   *         to the serviceInstance from the module.
+   *         Upon success use {@link ModuleServiceResult#getMessage()} to get the result and upon
+   *         failure
+   *         used {@link ModuleServiceResult#getErrorMessage()} to get the error message of the
+   *         failure.
+   */
+  <T> ModuleServiceResult<Map<String, Set<T>>> loadService(Class<T> service);
+
+  /**
+   * Returns the Class for the provided name for a specific module.
+   *
+   * @param className the classname that is to be loaded
+   * @param moduleDescriptor the ${@link ModuleDescriptor} used to lookup the module in question
+   * @return {@link ModuleServiceResult}. This type represents either Success or Failure. Using
+   *         {@link ModuleServiceResult#isSuccessful()} returns a {@literal Class<T>} in the case of
+   *         success.
+   *         Upon success use {@link ModuleServiceResult#getMessage()} to get the result and upon
+   *         failure
+   *         used {@link ModuleServiceResult#getErrorMessage()} to get the error message of the
+   *         failure.
+   */
+  ModuleServiceResult<Class<?>> loadClass(String className, ModuleDescriptor moduleDescriptor);
+
+  /**
+   * Returns the Class for the provided name for all loaded module.
+   *
+   * @param className the classname that is to be loaded
+   * @return {@link ModuleServiceResult}. This type represents either Success or Failure. Using
+   *         {@link ModuleServiceResult#isSuccessful()} returns a {@literal Map<String,Class<T>>} in
+   *         the case of success.
+   *         The resultant map returns a list of modules where the class can be loaded from.
+   *         Upon success use {@link ModuleServiceResult#getMessage()} to get the result and upon
+   *         failure
+   *         used {@link ModuleServiceResult#getErrorMessage()} to get the error message of the
+   *         failure.
    */
-  <T> List<T> loadService(Class<T> service);
+  ModuleServiceResult<Map<String, Class<?>>> loadClass(String className);
 }
diff --git a/geode-common-services/src/main/java/org/apache/geode/services/management/ManagementService.java b/geode-common-services/src/main/java/org/apache/geode/services/result/ModuleServiceResult.java
similarity index 50%
copy from geode-common-services/src/main/java/org/apache/geode/services/management/ManagementService.java
copy to geode-common-services/src/main/java/org/apache/geode/services/result/ModuleServiceResult.java
index 3e9ca47..f516c44 100644
--- a/geode-common-services/src/main/java/org/apache/geode/services/management/ManagementService.java
+++ b/geode-common-services/src/main/java/org/apache/geode/services/result/ModuleServiceResult.java
@@ -13,27 +13,36 @@
  * the License.
  */
 
-package org.apache.geode.services.management;
+package org.apache.geode.services.result;
 
-import java.util.Properties;
-
-import org.apache.geode.annotations.Experimental;
-import org.apache.geode.cache.Cache;
+import java.util.function.Function;
 
 /**
- * Entry point for creating a cache and bootstrapping Geode using the BootstrappingService
+ * The {@link ModuleServiceResult} type is an attempt at the function construct of
+ * <a href="https://www.vavr.io/vavr-docs/#_either">Either</a>. In this implementation a
+ * {@link ModuleServiceResult}
+ * can define either success or failure (error) using the same type.
+ *
+ * @param <SuccessType> the return type for a successful operation.
  *
- * @since Geode 1.13.0
+ * @since 1.14.0
  */
-@Experimental
-public interface ManagementService {
+public interface ModuleServiceResult<SuccessType> extends Result<SuccessType, String> {
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  <T> T map(Function<SuccessType, T> successFunction, Function<String, T> errorFunction);
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  SuccessType getMessage();
 
   /**
-   * Creates a Geode Cache given some configuration.
-   *
-   * @param properties system properties to use when creating the Cache.
-   *
-   * @throws Exception is Cache cannot be created.
+   * {@inheritDoc}
    */
-  Cache createCache(Properties properties) throws Exception;
+  @Override
+  String getErrorMessage();
 }
diff --git a/geode-common-services/src/main/java/org/apache/geode/services/result/Result.java b/geode-common-services/src/main/java/org/apache/geode/services/result/Result.java
new file mode 100644
index 0000000..b20109c
--- /dev/null
+++ b/geode-common-services/src/main/java/org/apache/geode/services/result/Result.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.services.result;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.apache.geode.annotations.Experimental;
+
+/**
+ * The {@link Result} type is an attempt at the function construct of
+ * * <a href="https://www.vavr.io/vavr-docs/#_either">Either</a>. In this implementation a
+ * {@link Result}
+ * * can define either success or failure (error) using the same type.
+ *
+ * @param <SuccessType> the return type in the event of operational success
+ * @param <FailureType> the return type in the event of operational failure
+ */
+@Experimental
+public interface Result<SuccessType, FailureType> {
+  /**
+   * A mapping function that maps to either <SuccessType> or <FailureType> depending on success or
+   * failure of the operation.
+   *
+   * @param successFunction the mapping function to map the SuccessType to the resultant type
+   * @param errorFunction the mapping function to map the FailureType to the resultant error type
+   * @param <T> the resultant type
+   * @return result of type <T>
+   */
+  <T> T map(Function<SuccessType, T> successFunction,
+      Function<FailureType, T> errorFunction);
+
+  /**
+   * The return message of a successful operation. The return type is of type <SuccessType>
+   *
+   * @return the result of the operation
+   */
+  SuccessType getMessage();
+
+  /**
+   * The return message of a failed operation. The return type is of type <FailureType>
+   *
+   * @return the failure message of why the operation did not succeed.
+   */
+  FailureType getErrorMessage();
+
+  /**
+   * Returns a boolean to indicate the success or failure of the operation
+   *
+   * @return {@literal true} or {@literal false} indicating success or failure of the operation
+   */
+  default boolean isSuccessful() {
+    return false;
+  }
+
+  /**
+   * If the result of the operation is successful, invoke the specified consumer with the value,
+   * otherwise do nothing.
+   *
+   * @param consumer block to be executed if a value is present
+   * @throws NullPointerException if value is present and {@code consumer} is
+   *         null
+   */
+  default void ifSuccessful(Consumer<? super SuccessType> consumer) {}
+
+  /**
+   * If the result of the operation has failed, invoke the specified consumer with the value,
+   * otherwise do nothing.
+   *
+   * @param consumer block to be executed if a value is present
+   * @throws NullPointerException if value is present and {@code consumer} is
+   *         null
+   */
+  default void ifFailure(Consumer<? super String> consumer) {}
+}
diff --git a/geode-common-services/src/test/resources/expected-pom.xml b/geode-common-services/src/test/resources/expected-pom.xml
index 26f1cdd..dbc47ae 100644
--- a/geode-common-services/src/test/resources/expected-pom.xml
+++ b/geode-common-services/src/test/resources/expected-pom.xml
@@ -19,7 +19,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>org.apache.geode</groupId>
   <artifactId>geode-common-services</artifactId>
-  <version>1.13.0-SNAPSHOT</version>
+  <version>${version}</version>
   <name>Apache Geode</name>
   <description>Apache Geode provides a database-like consistency model, reliable transaction processing and a shared-nothing architecture to maintain very low latency performance with high concurrency processing</description>
   <url>http://geode.apache.org</url>
@@ -39,7 +39,7 @@
       <dependency>
         <groupId>org.apache.geode</groupId>
         <artifactId>geode-all-bom</artifactId>
-        <version>1.13.0-SNAPSHOT</version>
+        <version>${version}</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
@@ -47,13 +47,13 @@
   </dependencyManagement>
   <dependencies>
     <dependency>
-      <groupId>org.apache.geode</groupId>
-      <artifactId>geode-common</artifactId>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.geode</groupId>
-      <artifactId>geode-core</artifactId>
+      <artifactId>geode-common</artifactId>
       <scope>compile</scope>
     </dependency>
   </dependencies>
diff --git a/geode-common/build.gradle b/geode-common/build.gradle
index 52007bd..a8b59b3 100755
--- a/geode-common/build.gradle
+++ b/geode-common/build.gradle
@@ -26,3 +26,40 @@ dependencies {
   testImplementation('junit:junit')
   testImplementation('org.assertj:assertj-core')
 }
+
+def geodeProjects = ['geode-common-services','geode-common']
+
+jar {
+  doFirst {
+    def projectDependencies = []
+    def runtimeList = []
+
+    configurations.runtimeClasspath
+            .collect { it.name - ".jar" }
+            .each { dependency ->
+              if (dependency.startsWith("geode-")) {
+                projectDependencies.add(dependency)
+              } else {
+                runtimeList.add(dependency)
+              }
+            }
+
+    projectDependencies.each { projectDependency ->
+      geodeProjects.each { geodeProject ->
+        if (projectDependency.contains(geodeProject)) {
+          def childProject = project(":$geodeProject")
+
+          if (childProject != null) {
+            def collect = childProject.configurations.runtimeClasspath.collect { it.name - ".jar" }
+            runtimeList.removeAll(collect)
+            projectDependencies.removeAll(collect)
+          }
+        }
+      }
+    }
+    manifest {
+      attributes("Class-Path": runtimeList.join(' '))
+      attributes("Dependent-Modules": projectDependencies.join(' '))
+    }
+  }
+}
diff --git a/geode-modules/build.gradle b/geode-modules/build.gradle
index 7fa6b14..853c638 100644
--- a/geode-modules/build.gradle
+++ b/geode-modules/build.gradle
@@ -20,82 +20,148 @@ apply from: "${rootDir}/${scriptDir}/standard-subproject-configuration.gradle"
 apply from: "${project.projectDir}/../gradle/publish-java.gradle"
 apply from: "${project.projectDir}/../gradle/warnings.gradle"
 
+repositories {
+    mavenCentral()
+}
+
+apply plugin: 'nebula.facet'
+facets {
+    module1WithManifest {}
+    module2WithManifest {}
+    module3WithManifest {}
+    module4WithManifest {}
+    module1WithoutManifest {}
+    module2WithoutManifest {}
+    module3WithoutManifest {}
+    module4WithoutManifest {}
+}
+
 sourceSets {
-    module1 {
-        java.srcDir "src/testModules/module1/java"
-        resources.srcDir "src/testModules/module1/resources"
-        compileClasspath += configurations.compileClasspath
-        runtimeClasspath += configurations.runtimeClasspath
-    }
-    module2 {
-        java.srcDir "src/testModules/module2/java"
-        resources.srcDir "src/testModules/module2/resources"
-        compileClasspath += configurations.compileClasspath
-        runtimeClasspath += configurations.runtimeClasspath
-    }
-    module3 {
-        java.srcDir "src/testModules/module3/java"
-        resources.srcDir "src/testModules/module3/resources"
-        compileClasspath += configurations.compileClasspath
-        runtimeClasspath += configurations.runtimeClasspath
-    }
-    module4 {
-        java.srcDir "src/testModules/module4/java"
-        resources.srcDir "src/testModules/module4/resources"
+    module5WithManifest {
+        java.srcDir "src/testModules/module5WithManifest/java"
+        resources.srcDir "src/testModules/module5WithManifest/resources"
         compileClasspath += configurations.compileClasspath
         runtimeClasspath += configurations.runtimeClasspath
     }
 }
 
-task module1Jar(type: Jar, dependsOn: classes) {
-    description 'Assembles the jar archive that contains the module1 classes'
-    from sourceSets.module1.output
-    archiveName 'module1.jar'
-}
+def moduleTasks = []
+def moduleCopyTasks = []
+def geodeProjects = ['geode-common-services','geode-common']
 
-task module2Jar(type: Jar, dependsOn: classes) {
-    description 'Assembles the jar archive that contains the module2 classes'
-    from sourceSets.module2.output
-    archiveName 'module2.jar'
-}
+facets.each { thisConfig ->
+    if (thisConfig.name.contains("WithoutManifest")) {
+        tasks.create("${thisConfig.name}CopyRuntimeLibs", Copy) {
+            into "$buildDir/libs"
+            from configurations.getByName("${thisConfig.name}RuntimeClasspath")
+            moduleCopyTasks.add(it.name - ".jar")
+        }
+        tasks.create("${thisConfig.name}Jar", Jar) {
+            description 'Assembles the jar archive that contains the $it classes without a manifest file'
+            from sourceSets.getByName(thisConfig.name).output
 
-task module3Jar(type: Jar, dependsOn: classes) {
-    description 'Assembles the jar archive that contains the module3 classes'
-    from sourceSets.module3.output
-    archiveName 'module3.jar'
-}
+            def ss = sourceSets.getByName(thisConfig.name)
+            ss.java.source().setSrcDirs(["src/testModules/${thisConfig.name}/java"])
+            ss.resources.source().setSrcDirs(["src/testModules/${thisConfig.name}/resources"])
+
+            archiveName "${thisConfig.name}-1.0".concat(".jar")
+            moduleTasks.add(it.name)
+        }
+
+
+    }
+    if (thisConfig.name.contains("WithManifest")) {
+        tasks.create("${thisConfig.name}CopyRuntimeLibs", Copy) {
+            into "$buildDir/libs"
+            from configurations.getByName("${thisConfig.name}RuntimeClasspath")
+            moduleCopyTasks.add(it.name)
+        }
+        tasks.create("${thisConfig.name}Jar", Jar) {
+            description 'Assembles the jar archive that contains the $it classes with a manifest file'
+            from sourceSets.getByName(thisConfig.name).output
+
+            def sourceSet = sourceSets.getByName(thisConfig.name)
+            sourceSet.java.source().setSrcDirs(["src/testModules/${thisConfig.name}/java"])
+            sourceSet.resources.source().setSrcDirs(["src/testModules/${thisConfig.name}/resources"])
+
+            doFirst {
+                def projectDependencies = []
+                def runtimeList = []
+
+                configurations.getByName("${thisConfig.name}RuntimeClasspath")
+                        .collect { it.name - ".jar" }
+                        .each { dependency ->
+                            if (dependency.startsWith("geode-")) {
+                                projectDependencies.add(dependency)
+                            } else {
+                                runtimeList.add(dependency)
+                            }
+                        }
+
+                projectDependencies.each { projectDependency ->
+                    geodeProjects.each { geodeProject ->
+                        if (projectDependency.contains(geodeProject)) {
+                            def childProject = project(":$geodeProject")
 
-task module4Jar(type: Jar, dependsOn: classes) {
-    description 'Assembles the jar archive that contains the module4 classes'
-    from sourceSets.module4.output
-    archiveName 'module4.jar'
+                            if (childProject != null) {
+                                def collect = childProject.configurations.runtimeClasspath.collect { it.name - ".jar" }
+                                runtimeList.removeAll(collect)
+                                projectDependencies.removeAll(collect)
+                            }
+                        }
+                    }
+                }
+                manifest {
+                    attributes("Class-Path": runtimeList.join(' '))
+                    attributes("Dependent-Modules": projectDependencies.join(' '))
+                }
+            }
+            archiveName "${thisConfig.name}-1.0".concat(".jar")
+            moduleTasks.add(it.name)
+        }
+    }
 }
 
-tasks.test.dependsOn("module1Jar")
-tasks.test.dependsOn("module2Jar")
-tasks.test.dependsOn("module3Jar")
-tasks.test.dependsOn("module4Jar")
+task module5WithManifestJar(type: Jar, dependsOn: classes) {
+    description 'Assembles the jar archive that contains a manifest file wih an incorrect classpath location'
+    from sourceSets.module5WithManifest.output
+    doFirst {
+        manifest {
+            attributes("Class-Path": "invalidjar.jar")
+        }
+    }
+    archiveName 'module5WithManifest-1.0.jar'
+}
 
-repositories {
-    mavenCentral()
+task copyRuntimeLibs(type: Copy) {
+    into "$buildDir/libs"
+    from configurations.module1WithoutManifestRuntime
 }
 
+tasks.test.dependsOn("module5WithManifestJar")
+tasks.test.dependsOn(moduleTasks)
+tasks.test.dependsOn(moduleCopyTasks)
+
 dependencies {
     testCompile(group: 'junit', name: 'junit', version: '4.12')
     testImplementation('org.assertj:assertj-core')
 
-    compile(platform(project(':boms:geode-all-bom')))
-
-    compile(project(':geode-common'))
-    implementation(project(':geode-logging'))
-    implementation(project(':geode-log4j'))
+    testImplementation('org.mockito:mockito-core')
 
     compile(project(':geode-common-services'))
 
     implementation('org.apache.logging.log4j:log4j-core')
     compile('org.jboss.modules:jboss-modules')
 
-    module1Compile(sourceSets.test.output)
-    module2Compile(sourceSets.test.output)
-    module3Compile(sourceSets.test.output)
+    module1WithoutManifestCompileOnly(sourceSets.test.output)
+    module2WithoutManifestCompileOnly(sourceSets.test.output)
+    module3WithoutManifestCompileOnly(sourceSets.test.output)
+    module4WithoutManifestCompileOnly(sourceSets.test.output)
+    module1WithManifestCompileOnly(sourceSets.test.output)
+    module2WithManifestCompileOnly(sourceSets.test.output)
+    module3WithManifestCompileOnly(sourceSets.test.output)
+    module4WithManifestCompileOnly(sourceSets.test.output)
+    module5WithManifestCompileOnly(sourceSets.test.output)
+
+    module1WithManifestCompile('org.springframework:spring-core')
 }
diff --git a/geode-modules/src/main/java/org/apache/geode/services/module/impl/GeodeModuleLoader.java b/geode-modules/src/main/java/org/apache/geode/services/module/impl/GeodeModuleLoader.java
deleted file mode 100644
index df06614..0000000
--- a/geode-modules/src/main/java/org/apache/geode/services/module/impl/GeodeModuleLoader.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License. You may obtain a
- * copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package org.apache.geode.services.module.impl;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.jboss.modules.DelegatingModuleLoader;
-import org.jboss.modules.Module;
-import org.jboss.modules.ModuleLoadException;
-import org.jboss.modules.ModuleLoader;
-import org.jboss.modules.ModuleSpec;
-
-import org.apache.geode.annotations.Experimental;
-
-/**
- * {@link ModuleLoader} for use by {@link JBossModuleServiceImpl}.
- */
-@Experimental
-public class GeodeModuleLoader extends DelegatingModuleLoader {
-  private Map<String, ModuleSpec> moduleSpecs = new HashMap<>();
-
-  public GeodeModuleLoader() {
-    super(Module.getSystemModuleLoader(), ModuleLoader.NO_FINDERS);
-  }
-
-  public void addModuleSpec(ModuleSpec moduleSpec) {
-    moduleSpecs.put(moduleSpec.getName(), moduleSpec);
-  }
-
-  public boolean unloadModule(Module module) {
-    if (module != null && unloadModuleLocal(module.getName(), module)) {
-      moduleSpecs.remove(module.getName());
-      return true;
-    }
-    return false;
-  }
-
-  @Override
-  protected ModuleSpec findModule(String name) throws ModuleLoadException {
-    ModuleSpec moduleSpec = moduleSpecs.get(name);
-    if (moduleSpec == null) {
-      throw new ModuleLoadException(
-          String.format("ModuleSpec for Module %s could not be found", name));
-    }
-    return moduleSpecs.get(name);
-  }
-}
diff --git a/geode-modules/src/main/java/org/apache/geode/services/module/impl/JBossModuleServiceImpl.java b/geode-modules/src/main/java/org/apache/geode/services/module/impl/JBossModuleServiceImpl.java
index 14a088a..bf0b347 100644
--- a/geode-modules/src/main/java/org/apache/geode/services/module/impl/JBossModuleServiceImpl.java
+++ b/geode-modules/src/main/java/org/apache/geode/services/module/impl/JBossModuleServiceImpl.java
@@ -15,162 +15,232 @@
 
 package org.apache.geode.services.module.impl;
 
-import java.io.IOException;
 import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
-import java.util.jar.JarFile;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.logging.log4j.Logger;
-import org.jboss.modules.DependencySpec;
-import org.jboss.modules.LocalDependencySpecBuilder;
 import org.jboss.modules.Module;
-import org.jboss.modules.ModuleDependencySpecBuilder;
+import org.jboss.modules.ModuleClassLoader;
 import org.jboss.modules.ModuleLoadException;
-import org.jboss.modules.ModuleSpec;
-import org.jboss.modules.PathUtils;
-import org.jboss.modules.ResourceLoader;
-import org.jboss.modules.ResourceLoaderSpec;
-import org.jboss.modules.ResourceLoaders;
-import org.jboss.modules.Version;
 
 import org.apache.geode.annotations.Experimental;
-import org.apache.geode.logging.internal.log4j.api.LogService;
 import org.apache.geode.services.module.ModuleDescriptor;
 import org.apache.geode.services.module.ModuleService;
+import org.apache.geode.services.module.internal.loader.GeodeModuleLoader;
+import org.apache.geode.services.result.ModuleServiceResult;
+import org.apache.geode.services.result.impl.Failure;
+import org.apache.geode.services.result.impl.Success;
 
 /**
- * Implementation of {@link ModuleService} using JBoss-Modules.
+ * Implementation of {@link ModuleService} using JBoss-Modules. This implementation uses
+ * JBossModules
+ * to load classes in a ClassLoader isolated manner.
+ *
+ * @see <a href="https://github.com/jboss-modules/jboss-modules">JBoss Modules</a>
+ *
+ * @since 1.14.0
  */
 @Experimental
 public class JBossModuleServiceImpl implements ModuleService {
 
-  private final Map<String, Module> modules = new HashMap<>();
+  private final Map<String, Module> modules = new ConcurrentHashMap<>();
 
-  private final GeodeModuleLoader moduleLoader = new GeodeModuleLoader();
+  private final GeodeModuleLoader moduleLoader;
 
   private final Logger logger;
 
-  public JBossModuleServiceImpl() {
-    this(LogService.getLogger());
-  }
-
   public JBossModuleServiceImpl(Logger logger) {
     this.logger = logger;
+    this.moduleLoader = new GeodeModuleLoader(logger);
   }
 
-  public Module getModule(String name) {
-    return modules.get(name);
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ModuleServiceResult<Boolean> registerModule(ModuleDescriptor moduleDescriptor) {
+    return moduleLoader.registerModuleDescriptor(moduleDescriptor);
   }
 
+  /**
+   * {@inheritDoc}
+   */
   @Override
-  public boolean loadModule(ModuleDescriptor moduleDescriptor) {
-    logger.debug(String.format("Beginning to load module %s", moduleDescriptor.getVersionedName()));
-
-    if (modules.containsKey(moduleDescriptor.getVersionedName())) {
-      logger
-          .warn(String.format("Module %s is already loaded.", moduleDescriptor.getVersionedName()));
-      return false;
+  public ModuleServiceResult<Boolean> loadModule(ModuleDescriptor moduleDescriptor) {
+    if (moduleDescriptor == null) {
+      return Failure.of("Load module failed due to moduleDescriptor being null");
     }
 
-    // Setting up new module.
-    ModuleSpec.Builder builder = ModuleSpec.build(moduleDescriptor.getVersionedName());
-    builder.setVersion(Version.parse(moduleDescriptor.getVersion()));
-    builder.addDependency(new LocalDependencySpecBuilder()
-        .setImportServices(true)
-        .setExport(true)
-        .build());
-
-    // Add dependencies to the module.
-    moduleDescriptor.getDependedOnModules().forEach(dependency -> {
-      logger.debug(String.format("Adding dependency on module %s", dependency));
-      builder.addDependency(new ModuleDependencySpecBuilder()
-          .setExport(true)
-          .setImportServices(true)
-          .setName(dependency)
-          .build());
-    });
+    String versionedName = moduleDescriptor.getName();
+    logger.debug(String.format("Beginning to load module %s", versionedName));
 
-    // Add resources to the module.
-    try {
-      for (String source : moduleDescriptor.getSources()) {
-        logger.debug(String.format("Adding resource %s to module", source));
-        ResourceLoader resourceLoader =
-            ResourceLoaders.createJarResourceLoader(new JarFile(source));
-        builder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(resourceLoader));
-      }
-    } catch (IOException e) {
-      logger.error(e.getMessage(), e);
-      return false;
+    if (modules.containsKey(versionedName)) {
+      String errorMessage = String.format("Module %s is already loaded.", versionedName);
+      logger.warn(errorMessage);
+      return Failure.of(errorMessage);
     }
 
-    // Add dependency on the system classloader so modules can access classes that aren't in
-    // modules.
-    builder.addDependency(DependencySpec.createSystemDependencySpec(PathUtils.getPathSet(null)));
-
-    // Build and register the ModuleSpec
-    ModuleSpec moduleSpec = builder.create();
-    moduleLoader.addModuleSpec(moduleSpec);
+    return loadRegisteredModule(moduleDescriptor);
+  }
 
-    // Load the module and add it to the modules map.
+  /**
+   * Tries to load a module.
+   *
+   * @param moduleDescriptor the {@link ModuleDescriptor} describing the module.
+   * @return {@link Success} in the event of success and {@link Failure} in case of failure. Failure
+   *         to load a module usually happens due to the {@link ModuleDescriptor} not having been
+   *         registered.
+   *         {@link ModuleDescriptor} can be registered using
+   *         {@link ModuleService#registerModule(ModuleDescriptor)}
+   */
+  private ModuleServiceResult<Boolean> loadRegisteredModule(ModuleDescriptor moduleDescriptor) {
+    String versionedName = moduleDescriptor.getName();
     try {
-      modules.put(moduleDescriptor.getVersionedName(),
-          moduleLoader.loadModule(moduleSpec.getName()));
+      modules.put(versionedName, moduleLoader.loadModule(versionedName));
+      return Success.of(true);
     } catch (ModuleLoadException e) {
       logger.error(e.getMessage(), e);
-      return false;
+      return Failure.of(e.getMessage());
     }
-
-    logger
-        .debug(String.format("Module %s successfully loaded", moduleDescriptor.getVersionedName()));
-
-    return true;
   }
 
+  /**
+   * {@inheritDoc}
+   */
   @Override
-  public boolean unloadModule(String moduleName) {
+  public ModuleServiceResult<Boolean> unloadModule(String moduleName) {
     logger.debug(String.format("Unloading module %s", moduleName));
     if (!modules.containsKey(moduleName)) {
-      logger.warn(
-          String.format("Module %s could not be unloaded because it is not loaded", moduleName));
-      return false;
+      String errorMessage =
+          String.format("Module %s could not be unloaded because it is not loaded", moduleName);
+      logger.warn(errorMessage);
+      return Failure.of(errorMessage);
     }
 
-    if (moduleLoader.unloadModule(modules.get(moduleName))) {
+    ModuleServiceResult<Boolean> unloadModuleResult =
+        moduleLoader.unloadModule(modules.get(moduleName));
+    if (unloadModuleResult.isSuccessful()) {
       modules.remove(moduleName);
       logger.debug(String.format("Module %s was successfully unloaded", moduleName));
-      return true;
     }
 
-    logger.debug(String.format("Module %s could not be unloaded", moduleName));
-    return false;
+    return unloadModuleResult;
   }
 
+  /**
+   * {@inheritDoc}
+   */
   @Override
-  public <T> List<T> loadService(Class<T> service) {
-    List<T> serviceImpls = new LinkedList<>();
+  public <T> ModuleServiceResult<Map<String, Set<T>>> loadService(Class<T> service) {
+    Map<String, Set<T>> serviceImpls = new HashMap<>();
 
     // Iterate over all the modules looking for implementations of service.
     modules.values().forEach((module) -> {
-      module.loadService(service).forEach((impl) -> {
-        // Check if class is already loaded.
-        // Modules with dependencies can cause duplicates without this check.
-        boolean duplicate = false;
-        for (T serviceImpl : serviceImpls) {
-          if (serviceImpl.getClass() == impl.getClass()) {
-            duplicate = true;
-            break;
-          }
-        }
-
-        // If impl is not a duplicate, add it to the list to return.
-        if (!duplicate) {
-          serviceImpls.add(impl);
-        }
+      module.loadService(service).forEach((serviceImpl) -> {
+        String moduleName = ((ModuleClassLoader) serviceImpl.getClass().getClassLoader()).getName();
+        Set<T> listOfServices = Optional.ofNullable(serviceImpls.get(moduleName))
+            .orElseGet(() -> createTreeSetWithClassLoaderComparator());
+        listOfServices.add(serviceImpl);
+        serviceImpls.put(moduleName, listOfServices);
       });
     });
 
-    return serviceImpls;
+    return Success.of(serviceImpls);
+  }
+
+  /**
+   *
+   * Create a Set<T> with a comparator on classname and ClassLoader to remove duplicates.
+   *
+   * @return empty set of type T
+   */
+  private <T> Set<T> createTreeSetWithClassLoaderComparator() {
+    return new TreeSet<>((o1, o2) -> {
+      if (o1 == null && o2 == null) {
+        return 0;
+      }
+      if (o1 == null) {
+        return 1;
+      }
+      if (o2 == null) {
+        return -1;
+      }
+      int classLoaderCompare = ((ModuleClassLoader) o1.getClass().getClassLoader()).getName()
+          .compareTo(((ModuleClassLoader) o2.getClass().getClassLoader()).getName());
+      if (classLoaderCompare == 0) {
+        return o1.getClass().getName().compareTo(o2.getClass().getName());
+      }
+      return classLoaderCompare;
+    });
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ModuleServiceResult<Class<?>> loadClass(String className,
+      ModuleDescriptor moduleDescriptor) {
+    Module module = modules.get(moduleDescriptor.getName());
+
+    if (module != null) {
+      return loadClassFromModule(className, module);
+    }
+    if (moduleLoader.hasRegisteredModule(moduleDescriptor.getName())) {
+      try {
+        module = moduleLoader.loadModule(moduleDescriptor.getName());
+        return loadClassFromModule(className, module);
+      } catch (ModuleLoadException e) {
+        logger.error(e);
+        return Failure.of(e.getMessage());
+      }
+    }
+    return Failure
+        .of(String.format("Module named: %s could be found. Please ensure it is registered",
+            moduleDescriptor.getName()));
+  }
+
+  /**
+   * Tries to load a class for name from a given module.
+   *
+   * @param className the classname that is to be loaded
+   * @param module the module from which the class is to be loaded from.
+   * @return {@link Success} with result type {@literal Class} or {@link Failure} with
+   *         {@literal String}
+   *         error message describing the reason for failure.
+   */
+  private ModuleServiceResult<Class<?>> loadClassFromModule(String className, Module module) {
+    try {
+      Class<?> loadedClass = module.getClassLoader().loadClass(className);
+      return Success.of(loadedClass);
+    } catch (ClassNotFoundException e) {
+      String errorMessage =
+          String.format("Could not find class for name: %s in module: %s", className,
+              module.getName());
+      logger.debug(errorMessage);
+      return Failure.of(errorMessage);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ModuleServiceResult<Map<String, Class<?>>> loadClass(String className) {
+    Map<String, Class<?>> classes = new HashMap<>();
+    modules.values().forEach((module) -> {
+      try {
+        Class<?> loadedClass = module.getClassLoader().loadClass(className);
+        classes.put(module.getName(), loadedClass);
+      } catch (ClassNotFoundException e) {
+        logger.debug(String.format("Could not find class for name: %s in module: %s", className,
+            module.getName()));
+      }
+    });
+
+    return Success.of(classes);
   }
 }
diff --git a/geode-modules/src/main/java/org/apache/geode/services/module/internal/finder/DelegatingModuleFinder.java b/geode-modules/src/main/java/org/apache/geode/services/module/internal/finder/DelegatingModuleFinder.java
new file mode 100644
index 0000000..ef727aa
--- /dev/null
+++ b/geode-modules/src/main/java/org/apache/geode/services/module/internal/finder/DelegatingModuleFinder.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.services.module.internal.finder;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.logging.log4j.Logger;
+import org.jboss.modules.ModuleFinder;
+import org.jboss.modules.ModuleLoadException;
+import org.jboss.modules.ModuleLoader;
+import org.jboss.modules.ModuleSpec;
+
+/**
+ * This {@link ModuleFinder} delegates the finding of modules to an internal List of
+ * {@link ModuleFinder}.
+ * This {@link ModuleFinder} is used to overcome the restriction from the {@link ModuleLoader} that
+ * restricts the registered
+ * {@link ModuleFinder}s at construction time, which does not allow for a dynamic set of
+ * {@link ModuleFinder}s to be created at
+ * runtime.
+ *
+ * @see ModuleFinder
+ * @see ModuleLoader
+ * @see ModuleSpec
+ */
+public class DelegatingModuleFinder implements ModuleFinder {
+  private final Logger logger;
+
+  private final List<ModuleFinder> finders = new CopyOnWriteArrayList<>();
+
+  public DelegatingModuleFinder(Logger logger) {
+    this.logger = logger;
+  }
+
+  public void addModuleFinder(ModuleFinder finder) {
+    finders.add(finder);
+    logger.debug("Added finder " + finder);
+  }
+
+  @Override
+  public ModuleSpec findModule(String name, ModuleLoader delegateLoader)
+      throws ModuleLoadException {
+    for (ModuleFinder finder : finders) {
+      ModuleSpec moduleSpec = finder.findModule(name, delegateLoader);
+      if (moduleSpec != null) {
+        logger.debug(String.format("Found module specification for module named: %s ", name));
+        return moduleSpec;
+      }
+    }
+    logger.debug(String.format("No module specification for module named: %s found", name));
+    return null;
+  }
+}
diff --git a/geode-modules/src/main/java/org/apache/geode/services/module/internal/loader/GeodeModuleLoader.java b/geode-modules/src/main/java/org/apache/geode/services/module/internal/loader/GeodeModuleLoader.java
new file mode 100644
index 0000000..9ea0668
--- /dev/null
+++ b/geode-modules/src/main/java/org/apache/geode/services/module/internal/loader/GeodeModuleLoader.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.services.module.internal.loader;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.Logger;
+import org.jboss.modules.DelegatingModuleLoader;
+import org.jboss.modules.GeodeJarModuleFinder;
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleFinder;
+import org.jboss.modules.ModuleLoadException;
+import org.jboss.modules.ModuleLoader;
+import org.jboss.modules.ModuleSpec;
+
+import org.apache.geode.annotations.Experimental;
+import org.apache.geode.services.module.ModuleDescriptor;
+import org.apache.geode.services.module.ModuleService;
+import org.apache.geode.services.module.impl.JBossModuleServiceImpl;
+import org.apache.geode.services.module.internal.finder.DelegatingModuleFinder;
+import org.apache.geode.services.result.ModuleServiceResult;
+import org.apache.geode.services.result.impl.Failure;
+import org.apache.geode.services.result.impl.Success;
+
+/**
+ * A custom implementation of {@link ModuleLoader} for use by {@link JBossModuleServiceImpl}.
+ * This implementation has reference to a {@link DelegatingModuleFinder} of type
+ * {@link ModuleFinder}
+ * to store a dynamically sized list of {@link ModuleFinder}.
+ *
+ * It also keeps track of registered {@link ModuleSpec} which have been created from
+ * {@link ModuleDescriptor}
+ * on a per module basis.
+ *
+ * @see ModuleLoader
+ * @see JBossModuleServiceImpl
+ * @see ModuleService
+ * @see ModuleServiceResult
+ * @see ModuleSpec
+ *
+ * @since 1.14.0
+ */
+@Experimental
+public class GeodeModuleLoader extends DelegatingModuleLoader {
+  private final DelegatingModuleFinder moduleFinder;
+  private final Map<String, ModuleSpec> moduleSpecs = new ConcurrentHashMap<>();
+  private final Logger logger;
+
+  public GeodeModuleLoader(Logger logger) {
+    this(new DelegatingModuleFinder(logger), logger);
+  }
+
+  private GeodeModuleLoader(DelegatingModuleFinder moduleFinder, Logger logger) {
+    super(Module.getSystemModuleLoader(), moduleFinder);
+    this.moduleFinder = moduleFinder;
+    this.logger = logger;
+  }
+
+  public ModuleServiceResult<Boolean> unloadModule(Module module) {
+    if (module != null && !StringUtils.isEmpty(module.getName())) {
+      return unloadModuleAndRemoveSpec(module);
+    } else {
+      String errorMessage =
+          "Module could not be unloaded because either the module or module name is null";
+      logger.debug(errorMessage);
+      return Failure.of(errorMessage);
+    }
+  }
+
+  private ModuleServiceResult<Boolean> unloadModuleAndRemoveSpec(Module module) {
+    try {
+      if (unloadModuleLocal(module.getName(), module)) {
+        moduleSpecs.remove(module.getName());
+        return Success.of(true);
+      } else {
+        String errorMessage = "Module could not be unloaded because it does not exist";
+        logger.debug(errorMessage);
+        return Failure.of(errorMessage);
+      }
+    } catch (SecurityException e) {
+      logger.error(e);
+      return Failure.of(String.format("Unloading of module: %s  failed due to exception: %s",
+          module.getName(), e.getMessage()));
+    }
+  }
+
+  public ModuleServiceResult<Boolean> registerModuleDescriptor(ModuleDescriptor descriptor) {
+    try {
+      moduleFinder.addModuleFinder(new GeodeJarModuleFinder(logger, descriptor));
+    } catch (IOException e) {
+      logger.error(e);
+      return Failure.of(String.format("Registering module: %s failed with error: %s",
+          descriptor.getName(), e.getMessage()));
+    }
+    return Success.of(true);
+  }
+
+  @Override
+  protected ModuleSpec findModule(String name) throws ModuleLoadException {
+    ModuleSpec moduleSpec = moduleSpecs.get(name);
+    if (moduleSpec == null) {
+      moduleSpec = moduleFinder.findModule(name, this);
+      if (moduleSpec != null) {
+        moduleSpecs.put(name, moduleSpec);
+      }
+    }
+    return moduleSpec;
+  }
+
+  public boolean hasRegisteredModule(String moduleName) {
+    return moduleSpecs.get(moduleName) != null;
+  }
+}
diff --git a/geode-modules/src/main/java/org/apache/geode/services/result/impl/Failure.java b/geode-modules/src/main/java/org/apache/geode/services/result/impl/Failure.java
new file mode 100644
index 0000000..8fe0602
--- /dev/null
+++ b/geode-modules/src/main/java/org/apache/geode/services/result/impl/Failure.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.services.result.impl;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.apache.commons.lang3.StringUtils;
+
+import org.apache.geode.services.result.ModuleServiceResult;
+
+/**
+ * This type of {@link ModuleServiceResult} represents a failed operation. It contains the
+ * errorMessage
+ * for the failure.
+ *
+ * @param <SuccessType> the result type for a successful operation. Not used by the {@link Failure}
+ *        type
+ *        but required by the {@link ModuleServiceResult}
+ *
+ * @since 1.14.0
+ */
+public class Failure<SuccessType> implements ModuleServiceResult<SuccessType> {
+
+  private final String errorMessage;
+
+  private Failure(String errorMessage) {
+    this.errorMessage = errorMessage;
+  }
+
+  /**
+   * Creates a {@link Failure} object containing the errorMessage
+   *
+   * @param errorMessage the error message describing the reason for failure.
+   * @return an {@link Failure} instance containing the errorMessage
+   */
+  public static <T> Failure<T> of(String errorMessage) {
+    if (StringUtils.isEmpty(errorMessage)) {
+      throw new IllegalArgumentException("Error message cannot be null or empty");
+    }
+    return new Failure<>(errorMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public <T> T map(Function<SuccessType, T> successFunction, Function<String, T> errorFunction) {
+    return errorFunction.apply(errorMessage);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public SuccessType getMessage() {
+    throw new RuntimeException("This Result is not of type Success.");
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getErrorMessage() {
+    return errorMessage;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void ifFailure(Consumer<? super String> consumer) {
+    if (!isSuccessful()) {
+      consumer.accept(errorMessage);
+    }
+  }
+}
diff --git a/geode-modules/src/main/java/org/apache/geode/services/result/impl/Success.java b/geode-modules/src/main/java/org/apache/geode/services/result/impl/Success.java
new file mode 100644
index 0000000..af574f4
--- /dev/null
+++ b/geode-modules/src/main/java/org/apache/geode/services/result/impl/Success.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.services.result.impl;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.apache.geode.services.result.ModuleServiceResult;
+
+/**
+ * This type of {@link ModuleServiceResult} represents a successful operation. It contains the
+ * return value
+ * of type <SuccessType>
+ *
+ * @param <SuccessType> the result type for a successful operation.
+ *
+ * @since 1.14.0
+ */
+public class Success<SuccessType> implements ModuleServiceResult<SuccessType> {
+
+  private final SuccessType result;
+
+  private Success(SuccessType result) {
+    this.result = result;
+  }
+
+  /**
+   * Creates a {@link Success} object containing the errorMessage
+   *
+   * @param result the return value of the successful operation
+   * @return an {@link Success} instance containing the return value
+   */
+  public static <T> Success<T> of(T result) {
+    return new Success<>(result);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public <T> T map(Function<SuccessType, T> successFunction, Function<String, T> errorFunction) {
+    return successFunction.apply(result);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public SuccessType getMessage() {
+    return result;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getErrorMessage() {
+    throw new RuntimeException("This Result is not of type Failure.");
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isSuccessful() {
+    return true;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void ifSuccessful(Consumer<? super SuccessType> consumer) {
+    if (isSuccessful()) {
+      consumer.accept(result);
+    }
+  }
+}
diff --git a/geode-modules/src/main/java/org/jboss/modules/GeodeJarModuleFinder.java b/geode-modules/src/main/java/org/jboss/modules/GeodeJarModuleFinder.java
new file mode 100644
index 0000000..ec4d200
--- /dev/null
+++ b/geode-modules/src/main/java/org/jboss/modules/GeodeJarModuleFinder.java
@@ -0,0 +1,293 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.jboss.modules;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.Logger;
+
+import org.apache.geode.services.module.ModuleDescriptor;
+
+/**
+ * A custom implementation of a {@link ModuleFinder}. This implementation is based on
+ * {@link JarModuleFinder}
+ * which creates a {@link ModuleSpec} from a {@link ModuleDescriptor}
+ * The {@link ModuleDescriptor} defines the module name, a set of {@link JarFile}s and/or dependent
+ * module names for Module.
+ *
+ * This {@link ModuleFinder} has the ability to set both resource paths and dependent module names
+ * from either
+ * the {@link ModuleDescriptor} or from a {@link Manifest} file within the {@link JarFile}.
+ *
+ * {@link Manifest} file must contain the attribute {@link Attributes.Name#CLASS_PATH} for resource
+ * paths and "Dependent-Modules"
+ * for a list of modules this modules depends on.
+ *
+ * In the case of both values being set on the {@link ModuleDescriptor} and within the
+ * {@link Manifest} file,
+ * the rule of addition is followed rather than failure.
+ *
+ * @see ModuleDescriptor
+ * @see ModuleSpec
+ * @see ModuleFinder
+ * @see JarModuleFinder
+ * @see JarFile
+ * @see Manifest
+ *
+ * @since 1.14.0
+ *
+ */
+public class GeodeJarModuleFinder implements ModuleFinder {
+
+  private final ModuleDescriptor moduleDescriptor;
+  private final String moduleName;
+  private final List<JarFile> sourceJarFiles;
+  private final Logger logger;
+
+  private static final String[] EMPTY_STRING_ARRAY = new String[0];
+  private static final String DEPENDENT_MODULES = "Dependent-Modules";
+  private static final List<String> EMPTY_LIST = new ArrayList<>();
+
+  /**
+   * Constructs a {@link GeodeJarModuleFinder} using a {@link Logger} and {@link ModuleDescriptor}.
+   * In the the case of incorrect/missing resource paths an {@link IOException} will be thrown.
+   *
+   * @param logger a Logger to log messages
+   * @param moduleDescriptor the {@link ModuleDescriptor} describing the module
+   * @throws IOException is thrown in the case of incorrect/missing resource paths.
+   */
+  public GeodeJarModuleFinder(final Logger logger, final ModuleDescriptor moduleDescriptor)
+      throws IOException {
+    this.moduleName = moduleDescriptor.getName();
+    this.sourceJarFiles = parseSourcesIntoJarFiles(moduleDescriptor);
+    this.logger = logger;
+    this.moduleDescriptor = moduleDescriptor;
+  }
+
+  /**
+   * Processes the {@link ModuleDescriptor} resource paths into a collection of {@link JarFile}
+   *
+   * @param moduleDescriptor the {@link ModuleDescriptor} for the module
+   * @return a collection of {@link JarFile} from the {@link ModuleDescriptor}
+   * @throws IOException is thrown in the case of incorrect/missing resources
+   */
+  private List<JarFile> parseSourcesIntoJarFiles(ModuleDescriptor moduleDescriptor)
+      throws IOException {
+    List<JarFile> results = new LinkedList<>();
+    for (String sourcePath : moduleDescriptor.getResourceJarPaths()) {
+      results.add(new JarFile(sourcePath, true));
+    }
+    return results;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ModuleSpec findModule(String name, ModuleLoader delegateLoader)
+      throws ModuleLoadException {
+    if (this.moduleName.equals(name)) {
+      return findModuleOfRegisteredJars(name);
+    }
+    return null;
+  }
+
+  /**
+   * Create a {@link ModuleSpec} for the registered Module.
+   * It creates a {@link ModuleSpec} for the name, processes each of the {@link JarFile} in the
+   * {@link #sourceJarFiles} collection and adds dependencies to other modules.
+   *
+   * @param name the module name
+   * @return a {@link ModuleSpec} from the corresponding {@link ModuleDescriptor}
+   * @throws ModuleLoadException in case of failure loading resource paths
+   */
+  private ModuleSpec findModuleOfRegisteredJars(String name) throws ModuleLoadException {
+    ModuleSpec.Builder moduleSpecBuilder = getModuleSpec(name);
+    for (JarFile jarFile : sourceJarFiles) {
+      registerJarFile(moduleSpecBuilder, jarFile);
+    }
+    createDependenciesForModules(moduleSpecBuilder, moduleDescriptor.getDependedOnModules());
+    moduleSpecBuilder.addDependency(DependencySpec.createSystemDependencySpec(JDKPaths.JDK));
+    return moduleSpecBuilder.create();
+  }
+
+  /**
+   * Processes a single {@link JarFile}. Processes the optionally included {@link Manifest} and adds
+   * itself to the {@link ModuleSpec} as a source resource.
+   *
+   * @param moduleSpecBuilder the builder for the {@link ModuleSpec}
+   * @param jarFile the {@link JarFile} to be processed
+   * @throws ModuleLoadException in case of failure loading resource paths
+   */
+  private void registerJarFile(ModuleSpec.Builder moduleSpecBuilder, JarFile jarFile)
+      throws ModuleLoadException {
+    processManifestFileEntries(moduleSpecBuilder, jarFile);
+
+    moduleSpecBuilder.addResourceRoot(ResourceLoaderSpec
+        .createResourceLoaderSpec(ResourceLoaders.createJarResourceLoader(jarFile)));
+  }
+
+  /**
+   * Processes a collection of modules and adds them to the dependent modules list for the
+   * {@link ModuleSpec}
+   *
+   * @param moduleSpecBuilder the builder for the {@link ModuleSpec}
+   * @param modulesDependencies a list of module names on which this module depends on
+   */
+  private void createDependenciesForModules(ModuleSpec.Builder moduleSpecBuilder,
+      Collection<String> modulesDependencies) {
+    for (String moduleDependency : modulesDependencies) {
+      moduleSpecBuilder.addDependency(new ModuleDependencySpecBuilder()
+          .setExport(true)
+          .setImportServices(true)
+          .setName(moduleDependency)
+          .build());
+    }
+  }
+
+  /**
+   * Processes the {@link Manifest} and populates both the resource paths and dependent modules from
+   * it.
+   * The {@link Manifest} file optionally includes a {@link Attributes.Name#CLASS_PATH} attribute,
+   * describing the resource paths to be included
+   * or an attribute {@link #DEPENDENT_MODULES} describing a list of dependent module names.
+   *
+   * @param moduleSpecBuilder the builder for the {@link ModuleSpec}
+   * @param jarFile the {@link JarFile} which contains a {@link Manifest} file.
+   * @throws ModuleLoadException in case of failure loading resource paths
+   */
+  private void processManifestFileEntries(ModuleSpec.Builder moduleSpecBuilder, JarFile jarFile)
+      throws ModuleLoadException {
+    Optional<Manifest> manifestFromJar = getManifestFromJar(jarFile);
+    if (manifestFromJar.isPresent()) {
+      String rootPath =
+          jarFile.getName().substring(0, jarFile.getName().lastIndexOf(File.separator));
+      Attributes mainAttributes = manifestFromJar.get().getMainAttributes();
+      processClasspathFromManifest(moduleSpecBuilder, rootPath,
+          mainAttributes.getValue(Attributes.Name.CLASS_PATH));
+
+      processManifestDependents(moduleSpecBuilder, mainAttributes.getValue(DEPENDENT_MODULES));
+    }
+  }
+
+  /**
+   * Process the {@link Manifest} file for dependent module names. The {@link Manifest} file must
+   * contain
+   * attribute {@link #DEPENDENT_MODULES} entries for this to be successful.
+   *
+   * @param moduleSpecBuilder the builder for the {@link ModuleSpec}
+   * @param dependentModules a String (' ') single space delimited of dependent module names
+   */
+  private void processManifestDependents(ModuleSpec.Builder moduleSpecBuilder,
+      String dependentModules) {
+    List<String> dependentModulesEntries = !StringUtils.isEmpty(dependentModules) ? Arrays
+        .asList(dependentModules.split(" ")) : EMPTY_LIST;
+    createDependenciesForModules(moduleSpecBuilder, dependentModulesEntries);
+  }
+
+  /**
+   * Process the {@link Manifest} file for resource paths to include. The {@link Manifest} file must
+   * contain
+   * attribute {@link Attributes.Name#CLASS_PATH} entries for this to be successful.
+   *
+   * @param builder the builder for the {@link ModuleSpec}
+   * @param rootPath the root path for the source {@link JarFile}.
+   * @param classpath a String (' ') single space delimited list of resource paths.
+   * @throws ModuleLoadException in the case of incorrect/missing resource
+   */
+  private void processClasspathFromManifest(ModuleSpec.Builder builder, String rootPath,
+      String classpath) throws ModuleLoadException {
+    String[] classpathEntries =
+        !StringUtils.isEmpty(classpath) ? classpath.split(" ") : EMPTY_STRING_ARRAY;
+    for (String classpathEntry : classpathEntries) {
+      if (!classpathEntry.isEmpty()) {
+
+        File file = getFileForPath(rootPath, classpathEntry);
+        try {
+          builder.addResourceRoot(ResourceLoaderSpec
+              .createResourceLoaderSpec(
+                  ResourceLoaders.createJarResourceLoader(new JarFile(file, true))));
+        } catch (IOException e) {
+          logger.error(String.format(
+              "File for name: %s could not be loaded as a dependent jar file at location: %s",
+              classpathEntry, file.getName()));
+          throw new ModuleLoadException(e);
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns a {@link File} for the path = rootPath + fileName.
+   * Checks if the fileName includes ".jar" and adds it if it is not included.
+   *
+   * @param rootPath the root path where the file is to be located
+   * @param fileName the filename of the {@link JarFile} within the rootPath
+   * @return file represented by the rootPath + fileName.
+   */
+  private File getFileForPath(String rootPath, String fileName) {
+    if (fileName.endsWith(".jar")) {
+      return new File(rootPath + File.separator + fileName);
+    } else {
+      return new File(rootPath + File.separator + fileName + ".jar");
+    }
+  }
+
+  /**
+   * Creates and returns a {@link ModuleSpec.Builder} for the name specified
+   *
+   * @param name the name of the module
+   * @return a {@link ModuleSpec.Builder}
+   */
+  private ModuleSpec.Builder getModuleSpec(String name) {
+    ModuleSpec.Builder builder = ModuleSpec.build(name);
+    builder.addDependency(new LocalDependencySpecBuilder()
+        .setImportServices(true)
+        .setExport(true)
+        .build());
+
+    return builder;
+  }
+
+  /**
+   * Returns a {@link Manifest} file from the {@link JarFile}
+   *
+   * @param jarFile the {@link JarFile} from which to retrieve the {@link Manifest} file
+   * @return an {@link Optional} containing a {@link Manifest} file from the {@link JarFile}. In the
+   *         case of no {@link Manifest} file
+   *         being present, an {@link Optional#empty()} shall be returned.
+   */
+  private Optional<Manifest> getManifestFromJar(JarFile jarFile) {
+    try {
+      return Optional.ofNullable(jarFile.getManifest());
+    } catch (IOException e) {
+      logger.info(
+          String.format("Unable to find manifest file for jar ile name: %s", jarFile.getName()));
+    }
+    return Optional.empty();
+  }
+}
diff --git a/geode-modules/src/test/java/org/apache/geode/services/module/impl/JBossModuleServiceImplTest.java b/geode-modules/src/test/java/org/apache/geode/services/module/impl/JBossModuleServiceImplTest.java
deleted file mode 100644
index be14780..0000000
--- a/geode-modules/src/test/java/org/apache/geode/services/module/impl/JBossModuleServiceImplTest.java
+++ /dev/null
@@ -1,616 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License. You may obtain a
- * copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package org.apache.geode.services.module.impl;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-import java.util.List;
-
-import org.jboss.modules.Module;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import org.apache.geode.InvalidService;
-import org.apache.geode.TestService;
-import org.apache.geode.services.module.ModuleDescriptor;
-
-public class JBossModuleServiceImplTest {
-
-  private static final String MODULE1_PATH =
-      System.getProperty("user.dir") + "/../libs/module1.jar";
-  private static final String MODULE2_PATH =
-      System.getProperty("user.dir") + "/../libs/module2.jar";
-  private static final String MODULE3_PATH =
-      System.getProperty("user.dir") + "/../libs/module3.jar";
-  private static final String MODULE4_PATH =
-      System.getProperty("user.dir") + "/../libs/module4.jar";
-
-  private static final String MODULE1_MESSAGE = "Hello from Module1!";
-  private static final String MODULE2_MESSAGE = "Hello from Module2!";
-
-  private JBossModuleServiceImpl moduleService;
-
-  @Before
-  public void setup() {
-    moduleService = new JBossModuleServiceImpl();
-  }
-
-  @After
-  public void teardown() {
-    moduleService = null;
-  }
-
-  @Test
-  public void modulesNotAccessibleFromSystemClassloaderNoModulesLoaded() {
-    assertThatThrownBy(() -> {
-      this.getClass().getClassLoader().loadClass("org.apache.geode.Module1");
-    }).isInstanceOf(ClassNotFoundException.class);
-
-    assertThatThrownBy(() -> {
-      this.getClass().getClassLoader().loadClass("org.apache.geode.Module2");
-    }).isInstanceOf(ClassNotFoundException.class);
-  }
-
-  @Test
-  public void modulesNotAccessibleFromSystemClassloaderWithModulesLoaded() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE2_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    moduleService.loadModule(module2Descriptor);
-
-    assertThatThrownBy(() -> {
-      this.getClass().getClassLoader().loadClass("org.apache.geode.Module1");
-    }).isInstanceOf(ClassNotFoundException.class);
-
-    assertThatThrownBy(() -> {
-      this.getClass().getClassLoader().loadClass("org.apache.geode.Module2");
-    }).isInstanceOf(ClassNotFoundException.class);
-  }
-
-  @Test
-  public void loadSingleModuleFromSingleJarNoDependencies() throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    assertThat(moduleService.loadModule(module1Descriptor)).isTrue();
-
-    moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module1");
-  }
-
-  @Test
-  public void loadSingleModuleFromMultipleJarsNoDependencies() throws ClassNotFoundException {
-    ModuleDescriptor moduleDescriptor = new ModuleDescriptor.Builder("multiJarModule", "1.0")
-        .fromSources(MODULE1_PATH, MODULE2_PATH)
-        .build();
-    assertThat(moduleService.loadModule(moduleDescriptor)).isTrue();
-
-    moduleService.getModule(moduleDescriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module1");
-    moduleService.getModule(moduleDescriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module2");
-  }
-
-  @Test
-  public void loadMultipleModulesFromMultipleJarsNoDependencies() throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH, MODULE2_PATH)
-        .build();
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE3_PATH, MODULE4_PATH)
-        .build();
-
-    assertThat(moduleService.loadModule(module1Descriptor)).isTrue();
-    assertThat(moduleService.loadModule(module2Descriptor)).isTrue();
-
-    moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module1");
-    moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module2");
-
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module3");
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module4");
-  }
-
-  @Test
-  public void modulesCannotAccessOtherModulesMultipleModulesFromMultipleJarsNoDependencies()
-      throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH, MODULE2_PATH)
-        .build();
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE3_PATH, MODULE4_PATH)
-        .build();
-
-    moduleService.loadModule(module1Descriptor);
-    moduleService.loadModule(module2Descriptor);
-
-    assertThatThrownBy(() -> {
-      moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-          .loadClass("org.apache.geode.Module3");
-    }).isInstanceOf(ClassNotFoundException.class);
-    assertThatThrownBy(() -> {
-      moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-          .loadClass("org.apache.geode.Module4");
-    }).isInstanceOf(ClassNotFoundException.class);
-
-    assertThatThrownBy(() -> {
-      moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-          .loadClass("org.apache.geode.Module1");
-    }).isInstanceOf(ClassNotFoundException.class);
-    assertThatThrownBy(() -> {
-      moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-          .loadClass("org.apache.geode.Module2");
-    }).isInstanceOf(ClassNotFoundException.class);
-  }
-
-  @Test
-  public void loadMultipleModulesFromMultipleJarsWithDependencies() throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH, MODULE2_PATH)
-        .build();
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE3_PATH, MODULE4_PATH)
-        .dependsOnModules(module1Descriptor.getVersionedName())
-        .build();
-
-    assertThat(moduleService.loadModule(module1Descriptor)).isTrue();
-    assertThat(moduleService.loadModule(module2Descriptor)).isTrue();
-
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module1");
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module2");
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module3");
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module4");
-  }
-
-  @Test
-  public void dependenciesDoNotGoBothWaysMultipleModulesFromMultipleJars()
-      throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH, MODULE2_PATH)
-        .build();
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE3_PATH, MODULE4_PATH)
-        .dependsOnModules(module1Descriptor.getVersionedName())
-        .build();
-
-    moduleService.loadModule(module1Descriptor);
-    moduleService.loadModule(module2Descriptor);
-
-    assertThatThrownBy(() -> {
-      moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-          .loadClass("org.apache.geode.Module3");
-    }).isInstanceOf(ClassNotFoundException.class);
-    assertThatThrownBy(() -> {
-      moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-          .loadClass("org.apache.geode.Module4");
-    }).isInstanceOf(ClassNotFoundException.class);
-  }
-
-  @Test
-  public void loadMultipleModulesFromSingleJarNoDependencies() throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE2_PATH)
-        .build();
-    assertThat(moduleService.loadModule(module1Descriptor)).isTrue();
-    assertThat(moduleService.loadModule(module2Descriptor)).isTrue();
-
-    moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module1");
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module2");
-  }
-
-  @Test
-  public void modulesCannotAccessOtherModulesMultipleModulesFromSingleJarNoDependencies()
-      throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE2_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    moduleService.loadModule(module2Descriptor);
-
-    assertThatThrownBy(() -> {
-      moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-          .loadClass("org.apache.geode.Module2");
-    }).isInstanceOf(ClassNotFoundException.class);
-
-    assertThatThrownBy(() -> {
-      moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-          .loadClass("org.apache.geode.Module1");
-    }).isInstanceOf(ClassNotFoundException.class);
-  }
-
-  @Test
-  public void loadMultipleModulesFromSingleJarWithDependencies() throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE2_PATH)
-        .dependsOnModules(module1Descriptor.getVersionedName())
-        .build();
-    assertThat(moduleService.loadModule(module1Descriptor)).isTrue();
-    assertThat(moduleService.loadModule(module2Descriptor)).isTrue();
-
-    moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module1");
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module2");
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module1");
-  }
-
-  @Test
-  public void dependenciesDoNotGoBothWaysMultipleModulesFromSingleJar()
-      throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE2_PATH)
-        .dependsOnModules(module1Descriptor.getVersionedName())
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    moduleService.loadModule(module2Descriptor);
-
-    assertThatThrownBy(() -> {
-      moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-          .loadClass("org.apache.geode.Module2");
-    }).isInstanceOf(ClassNotFoundException.class);
-  }
-
-  @Test
-  public void loadModuleMultipleTimes() throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    assertThat(moduleService.loadModule(module1Descriptor)).isTrue();
-    assertThat(moduleService.loadModule(module1Descriptor)).isFalse();
-
-    moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module1");
-  }
-
-  @Test
-  public void loadModulesWithSameNameAndDifferentVersions() throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module1", "2.0")
-        .fromSources(MODULE2_PATH)
-        .build();
-    moduleService.loadModule(module2Descriptor);
-
-    moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module1");
-    assertThatThrownBy(() -> {
-      moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-          .loadClass("org.apache.geode.Module2");
-    }).isInstanceOf(ClassNotFoundException.class);
-
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module2");
-    assertThatThrownBy(() -> {
-      moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-          .loadClass("org.apache.geode.Module1");
-    }).isInstanceOf(ClassNotFoundException.class);
-  }
-
-  @Test
-  public void loadModuleFromInvalidSource() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources("/there/is/nothing/here.jar")
-        .build();
-    assertThat(moduleService.loadModule(module1Descriptor)).isFalse();
-    assertThat(moduleService.getModule(module1Descriptor.getVersionedName())).isNull();
-  }
-
-  @Test
-  public void loadModuleFromMixOfValidAndInvalidSources() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources("/there/is/nothing/here.jar", MODULE1_PATH)
-        .build();
-    assertThat(moduleService.loadModule(module1Descriptor)).isFalse();
-    assertThat(moduleService.getModule(module1Descriptor.getVersionedName())).isNull();
-  }
-
-  @Test
-  public void loadModuleWithInvalidDependencies() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .dependsOnModules("this_is_invalid")
-        .build();
-    assertThat(moduleService.loadModule(module1Descriptor)).isFalse();
-    assertThat(moduleService.getModule(module1Descriptor.getVersionedName())).isNull();
-  }
-
-  @Test
-  public void loadModuleWithMixOfValidAndInvalidDependencies() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE2_PATH)
-        .dependsOnModules("this_is_invalid", module1Descriptor.getVersionedName())
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    assertThat(moduleService.loadModule(module2Descriptor)).isFalse();
-    assertThat(moduleService.getModule(module2Descriptor.getVersionedName())).isNull();
-  }
-
-  @Test
-  public void getModuleNoModulesLoaded() {
-    assertThat(moduleService.getModule("module1:1.0")).isNull();
-  }
-
-  @Test
-  public void getModuleWithSingeModuleLoaded() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    Module module = moduleService.getModule(module1Descriptor.getVersionedName());
-    assertThat(module).isNotNull();
-    assertThat(module.getName()).isEqualTo(module1Descriptor.getVersionedName());
-  }
-
-  @Test
-  public void getModuleWithMultipleModulesLoaded() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE2_PATH)
-        .build();
-    moduleService.loadModule(module2Descriptor);
-
-    Module module1 = moduleService.getModule(module1Descriptor.getVersionedName());
-    assertThat(module1).isNotNull();
-    assertThat(module1.getName()).isEqualTo(module1Descriptor.getVersionedName());
-
-    Module module2 = moduleService.getModule(module2Descriptor.getVersionedName());
-    assertThat(module2).isNotNull();
-    assertThat(module2.getName()).isEqualTo(module2Descriptor.getVersionedName());
-  }
-
-  @Test
-  public void loadServiceNoModulesLoaded() {
-    assertThat(moduleService.loadService(TestService.class)).isEmpty();
-  }
-
-  @Test
-  public void loadServiceNoModulesImplementService() {
-    assertThat(moduleService.loadService(InvalidService.class)).isEmpty();
-  }
-
-  @Test
-  public void loadServiceFromSingleModule() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-
-    List<TestService> serviceList = moduleService.loadService(TestService.class);
-    assertThat(serviceList.size()).isEqualTo(1);
-    assertThat(serviceList.get(0).sayHello()).isEqualTo(MODULE1_MESSAGE);
-  }
-
-  @Test
-  public void loadServicesFromMultipleModules() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE2_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    moduleService.loadModule(module2Descriptor);
-
-    List<TestService> serviceList = moduleService.loadService(TestService.class);
-    assertThat(serviceList.size()).isEqualTo(2);
-    assertThat(serviceList.stream().map(TestService::sayHello)).contains(MODULE1_MESSAGE,
-        MODULE2_MESSAGE);
-  }
-
-  @Test
-  public void loadServicesFromCompositeModule() {
-    ModuleDescriptor moduleDescriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH, MODULE2_PATH)
-        .build();
-    moduleService.loadModule(moduleDescriptor);
-
-    List<TestService> serviceList = moduleService.loadService(TestService.class);
-    assertThat(serviceList.size()).isEqualTo(2);
-    assertThat(serviceList.stream().map(TestService::sayHello)).contains(MODULE1_MESSAGE,
-        MODULE2_MESSAGE);
-  }
-
-  @Test
-  public void loadServiceFromModulesWithDependencies() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE2_PATH)
-        .dependsOnModules(module1Descriptor.getVersionedName())
-        .build();
-    moduleService.loadModule(module2Descriptor);
-
-    List<TestService> serviceList = moduleService.loadService(TestService.class);
-    assertThat(serviceList.size()).isEqualTo(2);
-    assertThat(serviceList.stream().map(TestService::sayHello)).contains(MODULE1_MESSAGE,
-        MODULE2_MESSAGE);
-  }
-
-  @Test
-  public void loadServiceFromModuleWithDuplicateContents() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH, MODULE1_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-
-    List<TestService> serviceList = moduleService.loadService(TestService.class);
-    assertThat(serviceList.size()).isEqualTo(1);
-    assertThat(serviceList.get(0).sayHello()).isEqualTo(MODULE1_MESSAGE);
-  }
-
-  @Test
-  public void unloadModule() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    assertThat(moduleService.unloadModule(module1Descriptor.getVersionedName())).isTrue();
-
-    assertThat(moduleService.getModule(module1Descriptor.getVersionedName())).isNull();
-
-    assertThat(moduleService.loadService(TestService.class)).isEmpty();
-  }
-
-  @Test
-  public void unloadModuleFromMultipleJars() {
-    ModuleDescriptor moduleDescriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH, MODULE2_PATH)
-        .build();
-    moduleService.loadModule(moduleDescriptor);
-    assertThat(moduleService.unloadModule(moduleDescriptor.getVersionedName())).isTrue();
-
-    assertThat(moduleService.getModule(moduleDescriptor.getVersionedName())).isNull();
-
-    assertThat(moduleService.loadService(TestService.class)).isEmpty();
-  }
-
-  @Test
-  public void unloadOneOfMultipleModules() throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE2_PATH)
-        .build();
-    moduleService.loadModule(module2Descriptor);
-
-    assertThat(moduleService.unloadModule(module1Descriptor.getVersionedName())).isTrue();
-
-    assertThat(moduleService.getModule(module1Descriptor.getVersionedName())).isNull();
-
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module2");
-
-    assertThat(moduleService.loadService(TestService.class).size()).isEqualTo(1);
-  }
-
-  @Test
-  public void unloadInvalidModuleName() {
-    assertThat(moduleService.unloadModule("invalidModuleName")).isFalse();
-  }
-
-  @Test
-  public void reloadUnloadedModule() throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    assertThat(moduleService.unloadModule(module1Descriptor.getVersionedName())).isTrue();
-
-    assertThat(moduleService.getModule(module1Descriptor.getVersionedName())).isNull();
-
-    assertThat(moduleService.loadModule(module1Descriptor)).isTrue();
-    moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module1");
-
-    assertThat(moduleService.loadService(TestService.class).size()).isEqualTo(1);
-  }
-
-  @Test
-  public void unloadModuleWithDependencies() throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE2_PATH)
-        .dependsOnModules(module1Descriptor.getVersionedName())
-        .build();
-    moduleService.loadModule(module2Descriptor);
-
-    assertThat(moduleService.unloadModule(module2Descriptor.getVersionedName())).isTrue();
-
-    assertThat(moduleService.getModule(module2Descriptor.getVersionedName())).isNull();
-
-    moduleService.getModule(module1Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module1");
-
-    assertThat(moduleService.loadService(TestService.class).size()).isEqualTo(1);
-  }
-
-  @Test
-  public void unloadModuleTwice() {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-
-    assertThat(moduleService.unloadModule(module1Descriptor.getVersionedName())).isTrue();
-
-    assertThat(moduleService.unloadModule(module1Descriptor.getVersionedName())).isFalse();
-
-    assertThat(moduleService.getModule(module1Descriptor.getVersionedName())).isNull();
-  }
-
-  @Test
-  public void unloadModuleWithSourceSharedByOtherModule() throws ClassNotFoundException {
-    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1", "1.0")
-        .fromSources(MODULE1_PATH, MODULE2_PATH)
-        .build();
-    moduleService.loadModule(module1Descriptor);
-    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2", "1.0")
-        .fromSources(MODULE2_PATH, MODULE3_PATH)
-        .build();
-    moduleService.loadModule(module2Descriptor);
-    assertThat(moduleService.unloadModule(module1Descriptor.getVersionedName())).isTrue();
-
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module2");
-    moduleService.getModule(module2Descriptor.getVersionedName()).getClassLoader()
-        .loadClass("org.apache.geode.Module3");
-
-    assertThat(moduleService.loadService(TestService.class).size()).isEqualTo(2);
-  }
-}
diff --git a/geode-modules/src/test/java/org/apache/geode/services/module/impl/JBossModuleServiceImplWithoutPopulatedManifestFileTest.java b/geode-modules/src/test/java/org/apache/geode/services/module/impl/JBossModuleServiceImplWithoutPopulatedManifestFileTest.java
new file mode 100644
index 0000000..3756506
--- /dev/null
+++ b/geode-modules/src/test/java/org/apache/geode/services/module/impl/JBossModuleServiceImplWithoutPopulatedManifestFileTest.java
@@ -0,0 +1,819 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.services.module.impl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.logging.log4j.LogManager;
+import org.jboss.modules.ModuleClassLoader;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.InvalidService;
+import org.apache.geode.TestService;
+import org.apache.geode.services.module.ModuleDescriptor;
+import org.apache.geode.services.module.ModuleService;
+import org.apache.geode.services.result.ModuleServiceResult;
+
+public class JBossModuleServiceImplWithoutPopulatedManifestFileTest {
+
+  private static final String MODULE1_PATH =
+      System.getProperty("user.dir") + "/../libs/module1WithoutManifest-1.0.jar";
+  private static final String MODULE2_PATH =
+      System.getProperty("user.dir") + "/../libs/module2WithoutManifest-1.0.jar";
+  private static final String MODULE3_PATH =
+      System.getProperty("user.dir") + "/../libs/module3WithoutManifest-1.0.jar";
+  private static final String MODULE4_PATH =
+      System.getProperty("user.dir") + "/../libs/module4WithoutManifest-1.0.jar";
+
+  private static final String MODULE1_MESSAGE = "Hello from Module1!";
+  private static final String MODULE2_MESSAGE = "Hello from Module2!";
+
+  private ModuleService moduleService;
+
+  @Before
+  public void setup() {
+    moduleService = new JBossModuleServiceImpl(LogManager.getLogger());
+  }
+
+  @After
+  public void teardown() {
+    moduleService = null;
+  }
+
+  @Test
+  public void modulesNotAccessibleFromSystemClassloaderNoModulesLoaded() {
+    ModuleServiceResult<Map<String, Class<?>>> mapModuleServiceResult = moduleService
+        .loadClass("org.apache.geode.ModuleService1");
+    assertThat(mapModuleServiceResult.isSuccessful()).isEqualTo(true);
+    assertThat(mapModuleServiceResult.getMessage().size()).isEqualTo(0);
+
+    mapModuleServiceResult = moduleService
+        .loadClass("org.apache.geode.ModuleService2");
+    assertThat(mapModuleServiceResult.isSuccessful()).isEqualTo(true);
+    assertThat(mapModuleServiceResult.getMessage().size()).isEqualTo(0);
+  }
+
+  @Test
+  public void modulesNotAccessibleFromSystemClassloaderWithModulesLoaded() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThatExceptionOfType(ClassNotFoundException.class)
+        .isThrownBy(
+            () -> this.getClass().getClassLoader().loadClass("org.apache.geode.ModuleService1"));
+    assertThatExceptionOfType(ClassNotFoundException.class)
+        .isThrownBy(
+            () -> this.getClass().getClassLoader().loadClass("org.apache.geode.ModuleService2"));
+  }
+
+  @Test
+  public void loadSingleModuleFromSingleJarNoDependencies() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+
+    loadClassAndAssert("org.apache.geode.ModuleService1", module1Descriptor);
+  }
+
+  @Test
+  public void loadSingleModuleFromMultipleJarsNoDependencies() {
+    ModuleDescriptor moduleDescriptor = new ModuleDescriptor.Builder("multiJarModule", "1.0")
+        .fromResourcePaths(MODULE1_PATH, MODULE2_PATH)
+        .build();
+
+    assertThat(moduleService.registerModule(moduleDescriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(moduleDescriptor).isSuccessful()).isTrue();
+
+    loadClassAndAssert("org.apache.geode.ModuleService1", moduleDescriptor);
+
+    loadClassAndAssert("org.apache.geode.ModuleService2", moduleDescriptor);
+  }
+
+  private void loadClassAndAssert(String className, ModuleDescriptor loadFromModule,
+      ModuleDescriptor moduleClassLoader) {
+    ModuleServiceResult<Class<?>> loadClassResult =
+        moduleService.loadClass(className, loadFromModule);
+    assertThat(loadClassResult.isSuccessful()).isTrue();
+    String moduleNameFromClassLoader =
+        ((ModuleClassLoader) loadClassResult.getMessage().getClassLoader()).getName();
+    assertThat(moduleNameFromClassLoader).isEqualTo(moduleClassLoader.getName());
+  }
+
+  private void loadClassAndAssert(String className, ModuleDescriptor moduleDescriptor) {
+    loadClassAndAssert(className, moduleDescriptor, moduleDescriptor);
+  }
+
+  private void loadClassAndAssertFailure(String className, ModuleDescriptor moduleDescriptor,
+      String expectedErrorMessage) {
+    ModuleServiceResult<Class<?>> loadClassResult =
+        moduleService.loadClass(className, moduleDescriptor);
+    assertThat(loadClassResult.isSuccessful()).isFalse();
+    assertThat(loadClassResult.getErrorMessage()).isEqualTo(expectedErrorMessage);
+  }
+
+  @Test
+  public void loadMultipleModulesFromMultipleJarsNoDependencies() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH, MODULE2_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE3_PATH, MODULE4_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    loadClassAndAssert("org.apache.geode.ModuleService1", module1Descriptor);
+    loadClassAndAssert("org.apache.geode.ModuleService2", module1Descriptor);
+
+    loadClassAndAssert("org.apache.geode.ModuleService3", module2Descriptor);
+    loadClassAndAssert("org.apache.geode.ModuleService4", module2Descriptor);
+  }
+
+  @Test
+  public void modulesCannotAccessOtherModulesMultipleModulesFromMultipleJarsNoDependencies() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH, MODULE2_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE3_PATH, MODULE4_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    loadClassAndAssertFailure("org.apache.geode.ModuleService3", module1Descriptor,
+        "Could not find class for name: org.apache.geode.ModuleService3 in module: module1WithoutManifest-1.0");
+    loadClassAndAssertFailure("org.apache.geode.ModuleService4", module1Descriptor,
+        "Could not find class for name: org.apache.geode.ModuleService4 in module: module1WithoutManifest-1.0");
+    loadClassAndAssertFailure("org.apache.geode.ModuleService1", module2Descriptor,
+        "Could not find class for name: org.apache.geode.ModuleService1 in module: module2WithoutManifest-1.0");
+    loadClassAndAssertFailure("org.apache.geode.ModuleService2", module2Descriptor,
+        "Could not find class for name: org.apache.geode.ModuleService2 in module: module2WithoutManifest-1.0");
+  }
+
+  @Test
+  public void loadMultipleModulesFromMultipleJarsWithDependencies() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH, MODULE2_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE3_PATH, MODULE4_PATH)
+            .dependsOnModules(module1Descriptor.getName())
+            .build();
+
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+
+    loadClassAndAssert("org.apache.geode.ModuleService1", module2Descriptor, module1Descriptor);
+    loadClassAndAssert("org.apache.geode.ModuleService2", module2Descriptor, module1Descriptor);
+    loadClassAndAssert("org.apache.geode.ModuleService3", module2Descriptor);
+    loadClassAndAssert("org.apache.geode.ModuleService4", module2Descriptor);
+  }
+
+  @Test
+  public void dependenciesDoNotGoBothWaysMultipleModulesFromMultipleJars() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH, MODULE2_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE3_PATH, MODULE4_PATH)
+            .dependsOnModules(module1Descriptor.getName())
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    loadClassAndAssert("org.apache.geode.ModuleService1", module2Descriptor, module1Descriptor);
+    loadClassAndAssert("org.apache.geode.ModuleService2", module2Descriptor, module1Descriptor);
+
+    loadClassAndAssertFailure("org.apache.geode.ModuleService3", module1Descriptor,
+        "Could not find class for name: org.apache.geode.ModuleService3 in module: module1WithoutManifest-1.0");
+    loadClassAndAssertFailure("org.apache.geode.ModuleService4", module1Descriptor,
+        "Could not find class for name: org.apache.geode.ModuleService4 in module: module1WithoutManifest-1.0");
+  }
+
+  @Test
+  public void loadMultipleModulesFromSingleJarNoDependencies() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    loadClassAndAssert("org.apache.geode.ModuleService1", module1Descriptor);
+    loadClassAndAssert("org.apache.geode.ModuleService2", module2Descriptor);
+  }
+
+  @Test
+  public void modulesCannotAccessOtherModulesMultipleModulesFromSingleJarNoDependencies() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    loadClassAndAssertFailure("org.apache.geode.ModuleService2", module1Descriptor,
+        "Could not find class for name: org.apache.geode.ModuleService2 in module: module1WithoutManifest-1.0");
+    loadClassAndAssertFailure("org.apache.geode.ModuleService1", module2Descriptor,
+        "Could not find class for name: org.apache.geode.ModuleService1 in module: module2WithoutManifest-1.0");
+  }
+
+  @Test
+  public void loadMultipleModulesWithDependencies() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .dependsOnModules(module1Descriptor.getName())
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    loadClassAndAssert("org.apache.geode.ModuleService1", module1Descriptor);
+    loadClassAndAssert("org.apache.geode.ModuleService2", module2Descriptor);
+    loadClassAndAssert("org.apache.geode.ModuleService1", module2Descriptor, module1Descriptor);
+  }
+
+  @Test
+  public void loadMultipleModulesFromSingleSourceWithDependencies() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .dependsOnModules(module1Descriptor.getName())
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    loadClassAndAssert("org.apache.geode.ModuleService1", module1Descriptor);
+    loadClassAndAssert("org.apache.geode.ModuleService2", module2Descriptor);
+    loadClassAndAssert("org.apache.geode.ModuleService1", module2Descriptor, module1Descriptor);
+  }
+
+  @Test
+  public void dependenciesDoNotGoBothWaysMultipleModulesFromSingleJar() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .dependsOnModules(module1Descriptor.getName())
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    loadClassAndAssertFailure("org.apache.geode.ModuleService2", module1Descriptor,
+        "Could not find class for name: org.apache.geode.ModuleService2 in module: module1WithoutManifest-1.0");
+    loadClassAndAssert("org.apache.geode.ModuleService1", module2Descriptor, module1Descriptor);
+  }
+
+  @Test
+  public void loadModuleMultipleTimes() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isFalse();
+
+    loadClassAndAssert("org.apache.geode.ModuleService1", module1Descriptor);
+  }
+
+  @Test
+  public void loadModulesWithSameNameAndDifferentVersions() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "2.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .build();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    loadClassAndAssert("org.apache.geode.ModuleService1", module1Descriptor);
+    loadClassAndAssertFailure("org.apache.geode.ModuleService2", module1Descriptor,
+        "Could not find class for name: org.apache.geode.ModuleService2 in module: module1WithoutManifest-1.0");
+    loadClassAndAssert("org.apache.geode.ModuleService2", module2Descriptor);
+    loadClassAndAssertFailure("org.apache.geode.ModuleService1", module2Descriptor,
+        "Could not find class for name: org.apache.geode.ModuleService1 in module: module1WithoutManifest-2.0");
+  }
+
+  @Test
+  public void registerModuleFromInvalidSource() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths("/there/is/nothing/here.jar")
+            .build();
+
+    ModuleServiceResult<Boolean> moduleServiceResult =
+        moduleService.registerModule(module1Descriptor);
+    assertThat(moduleServiceResult.isSuccessful()).isFalse();
+    assertThat(moduleServiceResult.getErrorMessage()).contains(
+        "Registering module: module1WithoutManifest-1.0 failed with error: /there/is/nothing/here.jar");
+  }
+
+  @Test
+  public void loadModuleFromMixOfValidAndInvalidSources() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths("/there/is/nothing/here.jar", MODULE1_PATH)
+            .build();
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isFalse();
+  }
+
+  @Test
+  public void loadModuleWithInvalidDependencies() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .dependsOnModules("this_is_invalid")
+            .build();
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isFalse();
+  }
+
+  @Test
+  public void loadModuleWithMixOfValidAndInvalidDependencies() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .dependsOnModules("this_is_invalid", module1Descriptor.getName())
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isFalse();
+  }
+
+  @Test
+  public void loadServiceNoModulesLoaded() {
+    ModuleServiceResult<Map<String, Set<TestService>>> loadServiceResult =
+        moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage()).isEmpty();
+  }
+
+  @Test
+  public void loadServiceNoModulesImplementService() {
+    ModuleServiceResult<Map<String, Set<InvalidService>>> loadServiceResult =
+        moduleService.loadService(InvalidService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage()).isEmpty();
+  }
+
+  @Test
+  public void loadServiceFromSingleModule() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+
+    ModuleServiceResult<Map<String, Set<TestService>>> serviceList =
+        moduleService.loadService(TestService.class);
+    assertThat(serviceList.isSuccessful()).isTrue();
+    assertThat(serviceList.getMessage().size()).isEqualTo(1);
+    assertThat(serviceList.getMessage().get(module1Descriptor.getName()).stream()
+        .map(TestService::sayHello).findFirst().orElse("Error")).isEqualTo(MODULE1_MESSAGE);
+  }
+
+  @Test
+  public void loadServicesFromMultipleModules() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    ModuleServiceResult<Map<String, Set<TestService>>> serviceList =
+        moduleService.loadService(TestService.class);
+    assertThat(serviceList.isSuccessful()).isTrue();
+    assertThat(serviceList.getMessage().size()).isEqualTo(2);
+    Collection<Set<TestService>> values = serviceList.getMessage().values();
+    List<String> results = new ArrayList<>();
+    for (Set<TestService> services : values) {
+      results.addAll(services.stream().map(TestService::sayHello).collect(Collectors.toList()));
+    }
+    assertThat(results).contains(MODULE1_MESSAGE, MODULE2_MESSAGE);
+  }
+
+  @Test
+  public void loadServicesFromCompositeModule() {
+    ModuleDescriptor moduleDescriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH, MODULE2_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(moduleDescriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(moduleDescriptor).isSuccessful()).isTrue();
+
+    ModuleServiceResult<Map<String, Set<TestService>>> serviceList =
+        moduleService.loadService(TestService.class);
+    assertThat(serviceList.isSuccessful()).isTrue();
+    assertThat(serviceList.getMessage().size()).isEqualTo(1);
+    assertThat(serviceList.getMessage().get(moduleDescriptor.getName()).size())
+        .isEqualTo(2);
+
+    Collection<Set<TestService>> values = serviceList.getMessage().values();
+    List<String> results = new ArrayList<>();
+    for (Set<TestService> services : values) {
+      results.addAll(services.stream().map(TestService::sayHello).collect(Collectors.toList()));
+    }
+    assertThat(results).contains(MODULE1_MESSAGE, MODULE2_MESSAGE);
+  }
+
+  @Test
+  public void loadServiceFromModulesWithDependencies() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .dependsOnModules(module1Descriptor.getName())
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+
+    ModuleServiceResult<Map<String, Set<TestService>>> serviceList =
+        moduleService.loadService(TestService.class);
+    assertThat(serviceList.isSuccessful()).isTrue();
+    assertThat(serviceList.getMessage().size()).isEqualTo(2);
+    assertThat(serviceList.getMessage().get(module1Descriptor.getName()).size())
+        .isEqualTo(1);
+    assertThat(serviceList.getMessage().get(module2Descriptor.getName()).size())
+        .isEqualTo(1);
+
+    Collection<Set<TestService>> values = serviceList.getMessage().values();
+    List<String> results = new ArrayList<>();
+    for (Set<TestService> services : values) {
+      results.addAll(services.stream().map(TestService::sayHello).collect(Collectors.toList()));
+    }
+    assertThat(results).contains(MODULE1_MESSAGE, MODULE2_MESSAGE);
+  }
+
+  @Test
+  public void loadServiceFromModuleWithDuplicateContents() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH, MODULE1_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+
+    ModuleServiceResult<Map<String, Set<TestService>>> serviceList =
+        moduleService.loadService(TestService.class);
+    assertThat(serviceList.isSuccessful()).isTrue();
+    assertThat(serviceList.getMessage().size()).isEqualTo(1);
+    assertThat(serviceList.getMessage().get(module1Descriptor.getName()).stream()
+        .map(TestService::sayHello).findFirst().orElse("Error")).isEqualTo(MODULE1_MESSAGE);
+  }
+
+  @Test
+  public void unloadModule() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+
+    ModuleServiceResult<Map<String, Set<TestService>>> loadServiceResult =
+        moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage().size()).isEqualTo(1);
+
+    assertThat(moduleService.unloadModule(module1Descriptor.getName()).isSuccessful())
+        .isTrue();
+
+    loadServiceResult = moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage()).isEmpty();
+  }
+
+  @Test
+  public void unloadModuleFromMultipleJars() {
+    ModuleDescriptor moduleDescriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH, MODULE2_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(moduleDescriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(moduleDescriptor).isSuccessful()).isTrue();
+
+    ModuleServiceResult<Map<String, Set<TestService>>> loadServiceResult =
+        moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage().size()).isEqualTo(1);
+
+    assertThat(moduleService.unloadModule(moduleDescriptor.getName()).isSuccessful())
+        .isTrue();
+
+    loadServiceResult = moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage()).isEmpty();
+  }
+
+  @Test
+  public void unloadOneOfMultipleModules() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    ModuleServiceResult<Map<String, Set<TestService>>> loadServiceResult =
+        moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage().size()).isEqualTo(2);
+    assertThat(loadServiceResult.getMessage().get(module1Descriptor.getName()).size())
+        .isEqualTo(1);
+    assertThat(loadServiceResult.getMessage().get(module2Descriptor.getName()).size())
+        .isEqualTo(1);
+
+    assertThat(moduleService.unloadModule(module1Descriptor.getName()).isSuccessful())
+        .isTrue();
+
+    loadClassAndAssert("org.apache.geode.ModuleService2", module2Descriptor);
+
+    loadServiceResult = moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage().size()).isEqualTo(1);
+    assertThat(loadServiceResult.getMessage().get(module2Descriptor.getName()).size())
+        .isEqualTo(1);
+  }
+
+  @Test
+  public void unloadInvalidModuleName() {
+    assertThat(moduleService.unloadModule("invalidModuleName").isSuccessful()).isFalse();
+  }
+
+  @Test
+  public void reloadUnloadedModule() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+
+    ModuleServiceResult<Map<String, Set<TestService>>> loadServiceResult =
+        moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage().size()).isEqualTo(1);
+    assertThat(loadServiceResult.getMessage().get(module1Descriptor.getName()).size())
+        .isEqualTo(1);
+
+    assertThat(moduleService.unloadModule(module1Descriptor.getName()).isSuccessful())
+        .isTrue();
+
+    loadServiceResult = moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage()).isEmpty();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+
+    loadServiceResult = moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage().size()).isEqualTo(1);
+    assertThat(loadServiceResult.getMessage().get(module1Descriptor.getName()).size())
+        .isEqualTo(1);
+  }
+
+  @Test
+  public void unloadModuleWithDependencies() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .dependsOnModules(module1Descriptor.getName())
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+
+    ModuleServiceResult<Map<String, Set<TestService>>> loadServiceResult =
+        moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage().size()).isEqualTo(2);
+    assertThat(loadServiceResult.getMessage().get(module1Descriptor.getName()).size())
+        .isEqualTo(1);
+    assertThat(loadServiceResult.getMessage().get(module2Descriptor.getName()).size())
+        .isEqualTo(1);
+
+    assertThat(moduleService.unloadModule(module2Descriptor.getName()).isSuccessful())
+        .isTrue();
+
+    loadClassAndAssert("org.apache.geode.ModuleService1", module1Descriptor);
+
+    loadServiceResult = moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage().size()).isEqualTo(1);
+    assertThat(loadServiceResult.getMessage().get(module1Descriptor.getName()).size())
+        .isEqualTo(1);
+  }
+
+  @Test
+  public void unloadModuleTwice() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.unloadModule(module1Descriptor.getName()).isSuccessful())
+        .isTrue();
+    assertThat(moduleService.unloadModule(module1Descriptor.getName()).isSuccessful())
+        .isFalse();
+  }
+
+  @Test
+  public void unloadModuleWithSourceSharedByOtherModule() {
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH, MODULE2_PATH)
+            .build();
+
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2WithoutManifest", "1.0")
+            .fromResourcePaths(MODULE2_PATH, MODULE3_PATH)
+            .build();
+
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    assertThat(moduleService.loadModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.loadModule(module2Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.unloadModule(module1Descriptor.getName()).isSuccessful())
+        .isTrue();
+
+    loadClassAndAssert("org.apache.geode.ModuleService2", module2Descriptor);
+    loadClassAndAssert("org.apache.geode.ModuleService3", module2Descriptor);
+
+    ModuleServiceResult<Map<String, Set<TestService>>> loadServiceResult =
+        moduleService.loadService(TestService.class);
+    assertThat(loadServiceResult.isSuccessful()).isTrue();
+    assertThat(loadServiceResult.getMessage().size()).isEqualTo(1);
+    assertThat(loadServiceResult.getMessage().keySet().toArray()[0])
+        .isEqualTo(module2Descriptor.getName());
+
+    Set<TestService> testServices =
+        loadServiceResult.getMessage().get(module2Descriptor.getName());
+    assertThat(testServices.size()).isEqualTo(2);
+    assertThat(testServices.stream().map(service -> service.getClass().getName()))
+        .containsExactlyInAnyOrder("org.apache.geode.ModuleService2",
+            "org.apache.geode.ModuleService3");
+  }
+}
diff --git a/geode-modules/src/test/java/org/apache/geode/services/module/impl/JBossModulesServiceImplWithPopulatedManifestFileTest.java b/geode-modules/src/test/java/org/apache/geode/services/module/impl/JBossModulesServiceImplWithPopulatedManifestFileTest.java
new file mode 100644
index 0000000..145d7ac
--- /dev/null
+++ b/geode-modules/src/test/java/org/apache/geode/services/module/impl/JBossModulesServiceImplWithPopulatedManifestFileTest.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.services.module.impl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.logging.log4j.LogManager;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.services.module.ModuleDescriptor;
+import org.apache.geode.services.module.ModuleService;
+import org.apache.geode.services.result.ModuleServiceResult;
+import org.apache.geode.services.result.impl.Failure;
+
+public class JBossModulesServiceImplWithPopulatedManifestFileTest {
+
+  private static final String MODULE1_PATH =
+      System.getProperty("user.dir") + "/../libs/module1WithManifest-1.0.jar";
+  private static final String MODULE2_PATH =
+      System.getProperty("user.dir") + "/../libs/module2WithManifest-1.0.jar";
+  private static final String MODULE3_PATH =
+      System.getProperty("user.dir") + "/../libs/module3WithManifest-1.0.jar";
+  private static final String MODULE4_PATH =
+      System.getProperty("user.dir") + "/../libs/module4WithManifest-1.0.jar";
+  private static final String MODULE5_PATH =
+      System.getProperty("user.dir") + "/../libs/module5WithManifest-1.0.jar";
+  private static final String GEODE_COMMONS_SERVICES_PATH =
+      System.getProperty("user.dir") + "/../libs/geode-common-services-1.14.0-build.0.jar";
+  private static final String GEODE_COMMONS_PATH =
+      System.getProperty("user.dir") + "/../libs/geode-common-1.14.0-build.0.jar";
+
+  private ModuleService moduleService;
+  private ModuleDescriptor geodeCommonsServiceDescriptor;
+  private ModuleDescriptor geodeCommonDescriptor;
+
+  @Before
+  public void setup() {
+    moduleService = new JBossModuleServiceImpl(LogManager.getLogger());
+    geodeCommonsServiceDescriptor =
+        new ModuleDescriptor.Builder("geode-common-services-1.14.0-build.0")
+            .fromResourcePaths(GEODE_COMMONS_SERVICES_PATH)
+            .build();
+
+    geodeCommonDescriptor = new ModuleDescriptor.Builder("geode-common-1.14.0-build.0")
+        .fromResourcePaths(GEODE_COMMONS_PATH)
+        .build();
+  }
+
+  @Test
+  public void loadJarWithManifestAndClasspathAttribute() {
+    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1WithManifest", "1.0")
+        .fromResourcePaths(MODULE1_PATH)
+        .build();
+
+    ModuleDescriptor module2Descriptor = new ModuleDescriptor.Builder("module2WithManifest", "1.0")
+        .fromResourcePaths(MODULE2_PATH)
+        .build();
+
+    assertThat(moduleService.registerModule(geodeCommonsServiceDescriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(geodeCommonDescriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module2Descriptor).isSuccessful()).isTrue();
+
+    loadModuleAndAssert(geodeCommonsServiceDescriptor);
+    loadModuleAndAssert(module1Descriptor);
+    loadModuleAndAssert(module2Descriptor);
+
+    ModuleServiceResult<Map<String, Class<?>>> loadClassResult =
+        moduleService.loadClass("org.springframework.util.StringUtils");
+
+    assertThat(loadClassResult.isSuccessful()).isTrue();
+
+    Map<String, Class<?>> message = loadClassResult.getMessage();
+    assertThat(message.size()).isEqualTo(1);
+    assertThat(message.keySet().toArray()[0]).isEqualTo(module1Descriptor.getName());
+  }
+
+  private void loadModuleAndAssert(ModuleDescriptor descriptor) {
+    ModuleServiceResult<Boolean> loadModuleResult = moduleService.loadModule(descriptor);
+    assertThat(loadModuleResult.isSuccessful()).isTrue();
+    assertThat(loadModuleResult.getMessage()).isEqualTo(true);
+  }
+
+  @Test
+  public void loadJarWithManifestAndClasspathAttributeInvalidClassName() {
+    ModuleDescriptor module1Descriptor = new ModuleDescriptor.Builder("module1WithManifest", "1.0")
+        .fromResourcePaths(MODULE1_PATH)
+        .build();
+
+    assertThat(moduleService.registerModule(geodeCommonDescriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(geodeCommonsServiceDescriptor).isSuccessful()).isTrue();
+    assertThat(moduleService.registerModule(module1Descriptor).isSuccessful()).isTrue();
+    loadModuleAndAssert(module1Descriptor);
+
+    ModuleServiceResult<Map<String, Class<?>>> loadClassResult =
+        moduleService.loadClass(".ocm.this.should.not.Exist");
+    assertThat(loadClassResult.isSuccessful()).isTrue();
+    assertThat(loadClassResult.getMessage().size()).isEqualTo(0);
+  }
+
+  @Test
+  public void loadJarWithManifestWithInvalidClasspathLocation() {
+    ModuleDescriptor descriptor = new ModuleDescriptor.Builder("module5", "1.0")
+        .fromResourcePaths(MODULE5_PATH)
+        .build();
+
+    assertThat(moduleService.registerModule(descriptor).isSuccessful()).isTrue();
+
+    ModuleServiceResult<Boolean> loadModuleResult = moduleService.loadModule(descriptor);
+    assertThat(loadModuleResult.isSuccessful()).isFalse();
+    assertThat(loadModuleResult).isExactlyInstanceOf(Failure.class);
+
+    String[] errorMessageSnippet =
+        new String[] {"java.io.FileNotFoundException:", "java.nio.file.NoSuchFileException:"};
+    assertMessageContains(loadModuleResult.getErrorMessage(), errorMessageSnippet);
+    assertThat(loadModuleResult.getErrorMessage())
+        .contains("libs/invalidjar.jar");
+  }
+
+  private void assertMessageContains(String errorMessage, String[] errorMessageSnippet) {
+    AtomicBoolean containsString = new AtomicBoolean();
+    Arrays.stream(errorMessageSnippet)
+        .forEach(errorSnippet -> containsString.set(errorMessage.contains(errorMessage)));
+    assertThat(containsString.get()).isTrue();
+  }
+}
diff --git a/geode-modules/src/test/java/org/jboss/modules/GeodeJarModuleFinderTest.java b/geode-modules/src/test/java/org/jboss/modules/GeodeJarModuleFinderTest.java
new file mode 100644
index 0000000..99846a4
--- /dev/null
+++ b/geode-modules/src/test/java/org/jboss/modules/GeodeJarModuleFinderTest.java
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.jboss.modules;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.logging.log4j.LogManager;
+import org.junit.Test;
+
+import org.apache.geode.services.module.ModuleDescriptor;
+
+public class GeodeJarModuleFinderTest {
+
+  private static final String MODULE1_PATH =
+      System.getProperty("user.dir") + "/../libs/module1WithManifest-1.0.jar";
+  private static final String MODULE2_PATH =
+      System.getProperty("user.dir") + "/../libs/module2WithManifest-1.0.jar";
+  private static final String MODULE3_PATH =
+      System.getProperty("user.dir") + "/../libs/module3WithManifest-1.0.jar";
+  private static final String MODULE4_PATH =
+      System.getProperty("user.dir") + "/../libs/module4WithManifest-1.0.jar";
+
+  private static final String GEODE_COMMONS_SERVICES_PATH =
+      System.getProperty("user.dir") + "/../libs/geode-common-services-1.14.0-build.0.jar";
+  private static final String GEODE_COMMONS_PATH =
+      System.getProperty("user.dir") + "/../libs/geode-common-1.14.0-build.0.jar";
+
+  @Test
+  public void findModuleSimpleJar() throws IOException, ModuleLoadException {
+    ModuleDescriptor moduleDescriptor =
+        new ModuleDescriptor.Builder("module1WithManifest", "1.0").fromResourcePaths(MODULE1_PATH)
+            .build();
+
+    ModuleFinder moduleFinder = new GeodeJarModuleFinder(LogManager.getLogger(), moduleDescriptor);
+    ConcreteModuleSpec moduleSpec = (ConcreteModuleSpec) moduleFinder
+        .findModule(moduleDescriptor.getName(), Module.getSystemModuleLoader());
+
+    assertThat(moduleSpec.getName()).isEqualTo(moduleDescriptor.getName());
+    assertThat(moduleSpec.getDependencies().length).isEqualTo(4);
+    String[] expectedDependencies = new String[] {"spring-core", "spring-jcl", "log4j-core",
+        "log4j-api", "jboss-modules", "module1WithManifest"};
+    ResourceLoaderSpec[] resourceLoaders = moduleSpec.getResourceLoaders();
+    assertThat(resourceLoaders.length).isEqualTo(expectedDependencies.length);
+    List<String> loadedResources = Arrays.stream(resourceLoaders)
+        .map(resourceLoaderSpec -> resourceLoaderSpec.getResourceLoader().getLocation().toString())
+        .collect(Collectors.toList());
+    for (String expectedDependency : expectedDependencies) {
+      boolean found = false;
+      for (String loadedResource : loadedResources) {
+        boolean contains = loadedResource.contains(expectedDependency);
+        if (contains) {
+          found = true;
+        }
+      }
+      assertThat(found).isTrue();
+    }
+  }
+
+  @Test
+  public void findModuleMultipleSourceJars() throws IOException, ModuleLoadException {
+    ModuleDescriptor moduleDescriptor =
+        new ModuleDescriptor.Builder("module1WithManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH, MODULE2_PATH)
+            .build();
+
+    ModuleFinder moduleFinder = new GeodeJarModuleFinder(LogManager.getLogger(), moduleDescriptor);
+    ConcreteModuleSpec moduleSpec = (ConcreteModuleSpec) moduleFinder
+        .findModule(moduleDescriptor.getName(), Module.getSystemModuleLoader());
+
+    assertThat(moduleSpec.getName()).isEqualTo(moduleDescriptor.getName());
+    // This contain duplicate entries for 'geode-common-services'. This is because the underlying
+    // moduleBuilder does
+    // check for duplicates
+    assertThat(moduleSpec.getDependencies().length).isEqualTo(5);
+    String[] expectedDependencies = new String[] {"spring-core", "spring-jcl", "log4j-core",
+        "log4j-api", "jboss-modules", "module1WithManifest", "module2WithManifest"};
+    assertModuleResourcesEqual(moduleSpec, expectedDependencies);
+  }
+
+  private void assertModuleResourcesEqual(ConcreteModuleSpec moduleSpec,
+      String[] expectedDependencies) {
+    Set<String> loadedResources = Arrays.stream(moduleSpec.getResourceLoaders())
+        .map(resourceLoaderSpec -> resourceLoaderSpec.getResourceLoader().getLocation().toString())
+        .collect(Collectors.toSet());
+    assertThat(loadedResources.size()).isEqualTo(expectedDependencies.length);
+    for (String expectedDependency : expectedDependencies) {
+      boolean found = false;
+      for (String loadedResource : loadedResources) {
+        boolean contains = loadedResource.contains(expectedDependency);
+        if (contains) {
+          found = true;
+        }
+      }
+      assertThat(found).isTrue();
+    }
+  }
+
+  @Test
+  public void findModuleJarWithDependencies() throws IOException, ModuleLoadException {
+    ModuleDescriptor moduleDescriptor =
+        new ModuleDescriptor.Builder("module1WithManifest", "1.0").fromResourcePaths(MODULE1_PATH)
+            .dependsOnModules("exampleModule")
+            .build();
+
+    ModuleFinder moduleFinder = new GeodeJarModuleFinder(LogManager.getLogger(), moduleDescriptor);
+    ConcreteModuleSpec moduleSpec = (ConcreteModuleSpec) moduleFinder
+        .findModule(moduleDescriptor.getName(), Module.getSystemModuleLoader());
+
+    assertThat(moduleSpec.getName()).isEqualTo(moduleDescriptor.getName());
+    assertThat(moduleSpec.getDependencies().length).isEqualTo(5);
+    String[] expectedDependencies = new String[] {"spring-core", "spring-jcl", "log4j-core",
+        "log4j-api", "jboss-modules", "module1WithManifest"};
+    assertModuleResourcesEqual(moduleSpec, expectedDependencies);
+  }
+
+  @Test
+  public void loadJarFile() throws IOException, ModuleLoadException {
+    ModuleDescriptor moduleDescriptor =
+        new ModuleDescriptor.Builder("module1WithManifest", "1.0").fromResourcePaths(MODULE1_PATH)
+            .build();
+
+    ModuleDescriptor geodeCommonsServiceDescriptor =
+        new ModuleDescriptor.Builder("geode-common-services-1.14.0-build.0")
+            .fromResourcePaths(GEODE_COMMONS_SERVICES_PATH)
+            .build();
+
+    ModuleDescriptor geodeCommonDescriptor =
+        new ModuleDescriptor.Builder("geode-common-1.14.0-build.0")
+            .fromResourcePaths(GEODE_COMMONS_PATH)
+            .build();
+
+    ModuleLoader moduleLoader = new TestModuleLoader(Module.getSystemModuleLoader(),
+        new ModuleFinder[] {
+            new GeodeJarModuleFinder(LogManager.getLogger(),
+                moduleDescriptor),
+            new GeodeJarModuleFinder(LogManager.getLogger(),
+                geodeCommonsServiceDescriptor),
+            new GeodeJarModuleFinder(LogManager.getLogger(),
+                geodeCommonDescriptor)
+        });
+    Module module = moduleLoader.loadModule(moduleDescriptor.getName());
+    assertThat(module).isNotNull();
+  }
+
+  @Test
+  public void loadMultipleJarFiles() throws IOException, ModuleLoadException {
+    ModuleDescriptor moduleDescriptor =
+        new ModuleDescriptor.Builder("module1WithManifest", "1.0")
+            .fromResourcePaths(MODULE1_PATH, MODULE2_PATH)
+            .build();
+
+    ModuleDescriptor geodeCommonsServiceDescriptor =
+        new ModuleDescriptor.Builder("geode-common-services-1.14.0-build.0")
+            .fromResourcePaths(GEODE_COMMONS_SERVICES_PATH)
+            .build();
+
+    ModuleDescriptor geodeCommonDescriptor =
+        new ModuleDescriptor.Builder("geode-common-1.14.0-build.0")
+            .fromResourcePaths(GEODE_COMMONS_PATH)
+            .build();
+
+    ModuleLoader moduleLoader = new TestModuleLoader(Module.getSystemModuleLoader(),
+        new ModuleFinder[] {
+            new GeodeJarModuleFinder(LogManager.getLogger(), moduleDescriptor),
+            new GeodeJarModuleFinder(LogManager.getLogger(), geodeCommonsServiceDescriptor),
+            new GeodeJarModuleFinder(LogManager.getLogger(), geodeCommonDescriptor)
+        });
+    Module module = moduleLoader.loadModule(moduleDescriptor.getName());
+    assertThat(module).isNotNull();
+  }
+
+  @Test
+  public void loadJarFileWithDependencies() throws IOException, ModuleLoadException {
+    ModuleDescriptor commonServices =
+        new ModuleDescriptor.Builder("geode-common-services", "1.14.0-build.0")
+            .fromResourcePaths(GEODE_COMMONS_SERVICES_PATH).build();
+
+    ModuleDescriptor geodeCommon = new ModuleDescriptor.Builder("geode-common", "1.14.0-build.0")
+        .fromResourcePaths(GEODE_COMMONS_PATH).build();
+
+    ModuleDescriptor module1Descriptor =
+        new ModuleDescriptor.Builder("module1WithManifest", "1.0").fromResourcePaths(MODULE1_PATH)
+            .build();
+
+    ModuleDescriptor module2Descriptor =
+        new ModuleDescriptor.Builder("module2", "1.0")
+            .fromResourcePaths(MODULE2_PATH)
+            .build();
+
+    ModuleLoader moduleLoader = new TestModuleLoader(Module.getSystemModuleLoader(),
+        new ModuleFinder[] {
+            new GeodeJarModuleFinder(LogManager.getLogger(), geodeCommon),
+            new GeodeJarModuleFinder(LogManager.getLogger(), commonServices),
+            new GeodeJarModuleFinder(LogManager.getLogger(),
+                module1Descriptor),
+            new GeodeJarModuleFinder(LogManager.getLogger(), module2Descriptor)
+        });
+
+    assertThat(moduleLoader.loadModule(geodeCommon.getName())).isNotNull();
+    assertThat(moduleLoader.loadModule(commonServices.getName())).isNotNull();
+    assertThat(moduleLoader.loadModule(module1Descriptor.getName())).isNotNull();
+    Module module = moduleLoader.loadModule(module2Descriptor.getName());
+    assertThat(module).isNotNull();
+  }
+
+  private static class TestModuleLoader extends DelegatingModuleLoader {
+
+    public TestModuleLoader(ModuleLoader delegate, ModuleFinder[] finders) {
+      super(delegate, finders);
+    }
+  }
+}
diff --git a/geode-modules/src/test/resources/expected-pom.xml b/geode-modules/src/test/resources/expected-pom.xml
index 50c2beb..62c7dae 100644
--- a/geode-modules/src/test/resources/expected-pom.xml
+++ b/geode-modules/src/test/resources/expected-pom.xml
@@ -19,7 +19,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>org.apache.geode</groupId>
   <artifactId>geode-modules</artifactId>
-  <version>1.13.0-SNAPSHOT</version>
+  <version>${version}</version>
   <name>Apache Geode</name>
   <description>Apache Geode provides a database-like consistency model, reliable transaction processing and a shared-nothing architecture to maintain very low latency performance with high concurrency processing</description>
   <url>http://geode.apache.org</url>
@@ -34,25 +34,9 @@
     <developerConnection>scm:git:https://github.com:apache/geode.git</developerConnection>
     <url>https://github.com/apache/geode</url>
   </scm>
-  <dependencyManagement>
-    <dependencies>
-      <dependency>
-        <groupId>org.apache.geode</groupId>
-        <artifactId>geode-all-bom</artifactId>
-        <version>1.13.0-SNAPSHOT</version>
-        <type>pom</type>
-        <scope>import</scope>
-      </dependency>
-    </dependencies>
-  </dependencyManagement>
   <dependencies>
     <dependency>
       <groupId>org.apache.geode</groupId>
-      <artifactId>geode-common</artifactId>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.geode</groupId>
       <artifactId>geode-common-services</artifactId>
       <scope>compile</scope>
     </dependency>
@@ -62,16 +46,6 @@
       <scope>compile</scope>
     </dependency>
     <dependency>
-      <groupId>org.apache.geode</groupId>
-      <artifactId>geode-logging</artifactId>
-      <scope>runtime</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.geode</groupId>
-      <artifactId>geode-log4j</artifactId>
-      <scope>runtime</scope>
-    </dependency>
-    <dependency>
       <groupId>org.apache.logging.log4j</groupId>
       <artifactId>log4j-core</artifactId>
       <scope>runtime</scope>
diff --git a/geode-modules/src/testModules/module1/resources/META-INF/services/org.apache.geode.TestService b/geode-modules/src/testModules/module1/resources/META-INF/services/org.apache.geode.TestService
deleted file mode 100644
index 209c875..0000000
--- a/geode-modules/src/testModules/module1/resources/META-INF/services/org.apache.geode.TestService
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.geode.Module1
\ No newline at end of file
diff --git a/geode-modules/src/testModules/module1/java/org/apache/geode/Module1.java b/geode-modules/src/testModules/module1WithManifest/java/org/apache/geode/ModuleService1.java
similarity index 94%
copy from geode-modules/src/testModules/module1/java/org/apache/geode/Module1.java
copy to geode-modules/src/testModules/module1WithManifest/java/org/apache/geode/ModuleService1.java
index 0463781..05cd417 100644
--- a/geode-modules/src/testModules/module1/java/org/apache/geode/Module1.java
+++ b/geode-modules/src/testModules/module1WithManifest/java/org/apache/geode/ModuleService1.java
@@ -15,7 +15,7 @@
 
 package org.apache.geode;
 
-public class Module1 implements TestService {
+public class ModuleService1 implements TestService {
   @Override
   public String sayHello() {
     return "Hello from Module1!";
diff --git a/geode-modules/src/testModules/module1WithManifest/resources/META-INF/services/org.apache.geode.TestService b/geode-modules/src/testModules/module1WithManifest/resources/META-INF/services/org.apache.geode.TestService
new file mode 100644
index 0000000..51549ab
--- /dev/null
+++ b/geode-modules/src/testModules/module1WithManifest/resources/META-INF/services/org.apache.geode.TestService
@@ -0,0 +1 @@
+org.apache.geode.ModuleService1
\ No newline at end of file
diff --git a/geode-modules/src/testModules/module1/java/org/apache/geode/Module1.java b/geode-modules/src/testModules/module1WithoutManifest/java/org/apache/geode/ModuleService1.java
similarity index 94%
rename from geode-modules/src/testModules/module1/java/org/apache/geode/Module1.java
rename to geode-modules/src/testModules/module1WithoutManifest/java/org/apache/geode/ModuleService1.java
index 0463781..05cd417 100644
--- a/geode-modules/src/testModules/module1/java/org/apache/geode/Module1.java
+++ b/geode-modules/src/testModules/module1WithoutManifest/java/org/apache/geode/ModuleService1.java
@@ -15,7 +15,7 @@
 
 package org.apache.geode;
 
-public class Module1 implements TestService {
+public class ModuleService1 implements TestService {
   @Override
   public String sayHello() {
     return "Hello from Module1!";
diff --git a/geode-modules/src/testModules/module1WithoutManifest/resources/META-INF/services/org.apache.geode.TestService b/geode-modules/src/testModules/module1WithoutManifest/resources/META-INF/services/org.apache.geode.TestService
new file mode 100644
index 0000000..51549ab
--- /dev/null
+++ b/geode-modules/src/testModules/module1WithoutManifest/resources/META-INF/services/org.apache.geode.TestService
@@ -0,0 +1 @@
+org.apache.geode.ModuleService1
\ No newline at end of file
diff --git a/geode-modules/src/testModules/module2/resources/META-INF/services/org.apache.geode.TestService b/geode-modules/src/testModules/module2/resources/META-INF/services/org.apache.geode.TestService
deleted file mode 100644
index 6e0cde8..0000000
--- a/geode-modules/src/testModules/module2/resources/META-INF/services/org.apache.geode.TestService
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.geode.Module2
\ No newline at end of file
diff --git a/geode-modules/src/testModules/module2/java/org/apache/geode/Module2.java b/geode-modules/src/testModules/module2WithManifest/java/org/apache/geode/ModuleService2.java
similarity index 94%
copy from geode-modules/src/testModules/module2/java/org/apache/geode/Module2.java
copy to geode-modules/src/testModules/module2WithManifest/java/org/apache/geode/ModuleService2.java
index 1008bbd..241ce69 100644
--- a/geode-modules/src/testModules/module2/java/org/apache/geode/Module2.java
+++ b/geode-modules/src/testModules/module2WithManifest/java/org/apache/geode/ModuleService2.java
@@ -15,7 +15,7 @@
 
 package org.apache.geode;
 
-public class Module2 implements TestService {
+public class ModuleService2 implements TestService {
   @Override
   public String sayHello() {
     return "Hello from Module2!";
diff --git a/geode-modules/src/testModules/module2WithManifest/resources/META-INF/services/org.apache.geode.TestService b/geode-modules/src/testModules/module2WithManifest/resources/META-INF/services/org.apache.geode.TestService
new file mode 100644
index 0000000..33cc5d0
--- /dev/null
+++ b/geode-modules/src/testModules/module2WithManifest/resources/META-INF/services/org.apache.geode.TestService
@@ -0,0 +1 @@
+org.apache.geode.ModuleService2
\ No newline at end of file
diff --git a/geode-modules/src/testModules/module2/java/org/apache/geode/Module2.java b/geode-modules/src/testModules/module2WithoutManifest/java/org/apache/geode/ModuleService2.java
similarity index 94%
rename from geode-modules/src/testModules/module2/java/org/apache/geode/Module2.java
rename to geode-modules/src/testModules/module2WithoutManifest/java/org/apache/geode/ModuleService2.java
index 1008bbd..241ce69 100644
--- a/geode-modules/src/testModules/module2/java/org/apache/geode/Module2.java
+++ b/geode-modules/src/testModules/module2WithoutManifest/java/org/apache/geode/ModuleService2.java
@@ -15,7 +15,7 @@
 
 package org.apache.geode;
 
-public class Module2 implements TestService {
+public class ModuleService2 implements TestService {
   @Override
   public String sayHello() {
     return "Hello from Module2!";
diff --git a/geode-modules/src/testModules/module2WithoutManifest/resources/META-INF/services/org.apache.geode.TestService b/geode-modules/src/testModules/module2WithoutManifest/resources/META-INF/services/org.apache.geode.TestService
new file mode 100644
index 0000000..33cc5d0
--- /dev/null
+++ b/geode-modules/src/testModules/module2WithoutManifest/resources/META-INF/services/org.apache.geode.TestService
@@ -0,0 +1 @@
+org.apache.geode.ModuleService2
\ No newline at end of file
diff --git a/geode-modules/src/testModules/module3/resources/META-INF/services/org.apache.geode.TestService b/geode-modules/src/testModules/module3/resources/META-INF/services/org.apache.geode.TestService
deleted file mode 100644
index e47bd26..0000000
--- a/geode-modules/src/testModules/module3/resources/META-INF/services/org.apache.geode.TestService
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.geode.Module3
\ No newline at end of file
diff --git a/geode-modules/src/testModules/module3/java/org/apache/geode/Module3.java b/geode-modules/src/testModules/module3WithManifest/java/org/apache/geode/ModuleService3.java
similarity index 94%
copy from geode-modules/src/testModules/module3/java/org/apache/geode/Module3.java
copy to geode-modules/src/testModules/module3WithManifest/java/org/apache/geode/ModuleService3.java
index 34bfbdd..600a3fe 100644
--- a/geode-modules/src/testModules/module3/java/org/apache/geode/Module3.java
+++ b/geode-modules/src/testModules/module3WithManifest/java/org/apache/geode/ModuleService3.java
@@ -15,7 +15,7 @@
 
 package org.apache.geode;
 
-public class Module3 implements TestService {
+public class ModuleService3 implements TestService {
   @Override
   public String sayHello() {
     return "Hello from Module3!";
diff --git a/geode-modules/src/testModules/module3WithManifest/resources/META-INF/services/org.apache.geode.TestService b/geode-modules/src/testModules/module3WithManifest/resources/META-INF/services/org.apache.geode.TestService
new file mode 100644
index 0000000..7d46531
--- /dev/null
+++ b/geode-modules/src/testModules/module3WithManifest/resources/META-INF/services/org.apache.geode.TestService
@@ -0,0 +1 @@
+org.apache.geode.ModuleService3
\ No newline at end of file
diff --git a/geode-modules/src/testModules/module3/java/org/apache/geode/Module3.java b/geode-modules/src/testModules/module3WithoutManifest/java/org/apache/geode/ModuleService3.java
similarity index 94%
rename from geode-modules/src/testModules/module3/java/org/apache/geode/Module3.java
rename to geode-modules/src/testModules/module3WithoutManifest/java/org/apache/geode/ModuleService3.java
index 34bfbdd..600a3fe 100644
--- a/geode-modules/src/testModules/module3/java/org/apache/geode/Module3.java
+++ b/geode-modules/src/testModules/module3WithoutManifest/java/org/apache/geode/ModuleService3.java
@@ -15,7 +15,7 @@
 
 package org.apache.geode;
 
-public class Module3 implements TestService {
+public class ModuleService3 implements TestService {
   @Override
   public String sayHello() {
     return "Hello from Module3!";
diff --git a/geode-modules/src/testModules/module3WithoutManifest/resources/META-INF/services/org.apache.geode.TestService b/geode-modules/src/testModules/module3WithoutManifest/resources/META-INF/services/org.apache.geode.TestService
new file mode 100644
index 0000000..7d46531
--- /dev/null
+++ b/geode-modules/src/testModules/module3WithoutManifest/resources/META-INF/services/org.apache.geode.TestService
@@ -0,0 +1 @@
+org.apache.geode.ModuleService3
\ No newline at end of file
diff --git a/geode-modules/src/testModules/module4/java/org/apache/geode/Module4.java b/geode-modules/src/testModules/module4WithManifest/java/org/apache/geode/ModuleService4.java
similarity index 96%
copy from geode-modules/src/testModules/module4/java/org/apache/geode/Module4.java
copy to geode-modules/src/testModules/module4WithManifest/java/org/apache/geode/ModuleService4.java
index c494905..91a9887 100644
--- a/geode-modules/src/testModules/module4/java/org/apache/geode/Module4.java
+++ b/geode-modules/src/testModules/module4WithManifest/java/org/apache/geode/ModuleService4.java
@@ -15,5 +15,5 @@
 
 package org.apache.geode;
 
-public class Module4 {
+public class ModuleService4 {
 }
diff --git a/geode-modules/src/testModules/module4/java/org/apache/geode/Module4.java b/geode-modules/src/testModules/module4WithoutManifest/java/org/apache/geode/ModuleService4.java
similarity index 96%
copy from geode-modules/src/testModules/module4/java/org/apache/geode/Module4.java
copy to geode-modules/src/testModules/module4WithoutManifest/java/org/apache/geode/ModuleService4.java
index c494905..91a9887 100644
--- a/geode-modules/src/testModules/module4/java/org/apache/geode/Module4.java
+++ b/geode-modules/src/testModules/module4WithoutManifest/java/org/apache/geode/ModuleService4.java
@@ -15,5 +15,5 @@
 
 package org.apache.geode;
 
-public class Module4 {
+public class ModuleService4 {
 }
diff --git a/geode-modules/src/testModules/module4/java/org/apache/geode/Module4.java b/geode-modules/src/testModules/module5WithManifest/java/org/apache/geode/ModuleService5.java
similarity index 96%
rename from geode-modules/src/testModules/module4/java/org/apache/geode/Module4.java
rename to geode-modules/src/testModules/module5WithManifest/java/org/apache/geode/ModuleService5.java
index c494905..f155862 100644
--- a/geode-modules/src/testModules/module4/java/org/apache/geode/Module4.java
+++ b/geode-modules/src/testModules/module5WithManifest/java/org/apache/geode/ModuleService5.java
@@ -15,5 +15,5 @@
 
 package org.apache.geode;
 
-public class Module4 {
+public class ModuleService5 {
 }