You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by kl...@apache.org on 2022/03/29 19:29:32 UTC

[geode] 05/06: GEODE-9758: Add internal serial filter API (#7217)

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

klund pushed a commit to branch support/1.14
in repository https://gitbox.apache.org/repos/asf/geode.git

commit 92e0e89c982ef4ed53e0a7be0194aea45adb8726
Author: Kirk Lund <kl...@apache.org>
AuthorDate: Tue Jan 18 11:01:25 2022 -0800

    GEODE-9758: Add internal serial filter API (#7217)
    
    GEODE-9758: Add internal serial filter API #7217
    
    Expand ObjectInputStreamFilterWrapper to be an internal API which
    supports all of Geode's uses of Java's ObjectInputFilter.
    
    Introduce a new system property, geode.enableGlobalSerialFilter, to
    enable a process-wide filter with all serializable Geode classes on the
    classpath and the value of serializable-object-filter accept-listed.
    
    To enable the process-wide filter with GFSH start commands, add:
    
    * --J=-Dgeode.enableGlobalSerialFilter=true
    
    Functional Capabilities
    
    The internal API lives in geode-serialization and works on OpenJDK
    based JREs providing a facade for Java's ObjectInputFilter in Java 8
    and Java 9 or greater using reflection. The API provides the following
    capabilities:
    
    * creating an ObjectInputFilter
    * setting an ObjectInputFilter on an ObjectInputStream
    * getting an ObjectInputFilter from a ObjectInputStream
    * setting a process-wide ObjectInputFilter
    * getting a process-wide ObjectInputFilter
    
    Design Notes
    
    The API defines the following primary interface types:
    
    * factory interfaces for creating instances of types within the API
    * filter interfaces to split out single ops from Java's
      ObjectInputFilter
    * configuration interfaces for handling system properties, logging,
      and config validation
    
    The concrete classes in the API receive parameters injected via a
    constructor for any collaborators that are not specified by the
    interfaces. This is intentional even when the instance is only used
    once before de-referencing it. All collaborators that are defined in
    the interface are passed in as parameters to the implementing
    method; all others are passed in via the constructor and stored as
    fields.
    
    (cherry picked from commit 7978abf34707d11da45cff0b7cb7445f18d21438)
---
 ...artLocatorGlobalSerialFilterAcceptanceTest.java | 138 ++++++++
 ...StartLocatorJmxSerialFilterAcceptanceTest.java} |  58 ++--
 ...tartServerGlobalSerialFilterAcceptanceTest.java | 150 +++++++++
 .../StartServerJmxSerialFilterAcceptanceTest.java} |  55 ++--
 geode-core/build.gradle                            |   2 +-
 ...ValidateSerializableObjectsDistributedTest.java | 320 +++++++++++++++++++
 .../geode/distributed/LocatorIntegrationTest.java  | 180 ++++++-----
 ...catorLauncherJmxManagerLocalRegressionTest.java |  22 +-
 ...cherGlobalSerialFilterBlankIntegrationTest.java |  55 ++++
 ...herGlobalSerialFilterExistsIntegrationTest.java |  55 ++++
 ...rLauncherGlobalSerialFilterIntegrationTest.java |  52 +++
 ...alSerialFilterPropertyBlankIntegrationTest.java |  54 ++++
 ...alSerialFilterPropertyEmptyIntegrationTest.java |  54 ++++
 ...atorLauncherJmxSerialFilterIntegrationTest.java |  84 +++++
 ...mxSerialFilterPropertyBlankIntegrationTest.java |  88 ++++++
 ...mxSerialFilterPropertyEmptyIntegrationTest.java |  88 ++++++
 ...xSerialFilterPropertyExistsIntegrationTest.java |  90 ++++++
 .../filter/LocatorLauncherWithJmxManager.java      |  82 +++++
 .../filter/SerialFilterAssertions.java             |  64 ++++
 ...cherGlobalSerialFilterBlankIntegrationTest.java |  55 ++++
 ...herGlobalSerialFilterExistsIntegrationTest.java |  55 ++++
 ...rLauncherGlobalSerialFilterIntegrationTest.java |  51 +++
 ...alSerialFilterPropertyBlankIntegrationTest.java |  53 ++++
 ...alSerialFilterPropertyEmptyIntegrationTest.java |  53 ++++
 ...rverLauncherJmxSerialFilterIntegrationTest.java |  87 +++++
 ...mxSerialFilterPropertyBlankIntegrationTest.java |  88 ++++++
 ...mxSerialFilterPropertyEmptyIntegrationTest.java |  88 ++++++
 ...xSerialFilterPropertyExistsIntegrationTest.java |  90 ++++++
 .../filter/ServerLauncherWithJmxManager.java       |  77 +++++
 ...erConfiguresJmxSerialFilterIntegrationTest.java | 204 ------------
 .../internal/ManagementAgentIntegrationTest.java   |   6 +-
 ...erConfiguresJmxSerialFilterIntegrationTest.java | 201 ------------
 .../apache/geode/distributed/LocatorLauncher.java  |  12 +
 .../apache/geode/distributed/ServerLauncher.java   |  11 +
 .../distributed/internal/DistributionConfig.java   |   4 +-
 .../DistributedSerializableObjectConfig.java       |  51 +++
 .../geode/internal/InternalDataSerializer.java     |  41 ++-
 .../internal/ObjectInputStreamFilterWrapper.java   | 233 --------------
 .../internal/JmxRmiOpenTypesSerialFilter.java      | 148 ---------
 .../geode/management/internal/ManagementAgent.java |  10 +-
 .../internal/ManagementAgentFactory.java           |   3 +-
 .../internal/SystemManagementService.java          |   9 +-
 .../internal/DistributionConfigJUnitTest.java      |   8 +
 .../DistributedSerializableObjectConfigTest.java   |  62 ++++
 ...lDataSerializerSerializationAcceptlistTest.java | 144 ++++++---
 .../InternalDataSerializerShiroAcceptListTest.java |  34 +-
 .../internal/JmxRmiOpenTypesSerialFilterTest.java  | 351 ---------------------
 geode-core/src/test/resources/expected-pom.xml     |  10 +-
 geode-http-service/build.gradle                    |   1 +
 .../codeAnalysis/AnalyzeSerializablesTestBase.java |   6 +-
 ...nputFilterApiSetFilterBlankIntegrationTest.java |  63 ++++
 ...jectInputFilterApiSetFilterIntegrationTest.java |  60 ++++
 ...InputFilterApiSetFilterNullIntegrationTest.java |  61 ++++
 ...edSerializablesService.java => ApiPackage.java} |  28 +-
 .../serialization/filter/FilterConfiguration.java  |  12 +-
 .../serialization/filter/FilterPattern.java        |  13 +-
 .../serialization/filter/GlobalSerialFilter.java   |  12 +-
 .../filter/GlobalSerialFilterConfiguration.java    | 144 +++++++++
 ...=> GlobalSerialFilterConfigurationFactory.java} |  24 +-
 ...Service.java => GlobalSerialFilterFactory.java} |  21 +-
 .../Java9ReflectiveObjectInputFilterApi.java       |  64 ++++
 .../filter/JmxSerialFilterConfiguration.java       |  72 +++++
 .../JmxSerialFilterConfigurationFactory.java       |  13 +-
 .../filter/NullObjectInputFilter.java              |  10 +-
 .../serialization/filter/ObjectInputFilter.java    |  15 +-
 .../serialization/filter/ObjectInputFilterApi.java |  85 +++++
 .../filter/ObjectInputFilterApiFactory.java        |   9 +-
 .../filter/ObjectInputFilterFactory.java           |  12 +-
 .../filter/ObjectInputFilterUtils.java             |  61 ++++
 .../filter/OpenMBeanFilterPattern.java             |  68 ++++
 .../filter/ReflectiveFacadeGlobalSerialFilter.java |  71 +++++
 ...ReflectiveFacadeGlobalSerialFilterFactory.java} |  29 +-
 .../filter/ReflectiveFacadeObjectInputFilter.java  |  78 +++++
 .../ReflectiveFacadeObjectInputFilterFactory.java  |  61 ++++
 .../filter/ReflectiveObjectInputFilterApi.java     | 269 ++++++++++++++++
 .../ReflectiveObjectInputFilterApiFactory.java     |  45 +++
 .../filter/SanctionedSerializables.java            |  13 +-
 .../SanctionedSerializablesFilterPattern.java      | 111 +++++++
 .../filter/SanctionedSerializablesService.java     |   6 +
 ...sService.java => SerializableObjectConfig.java} |  22 +-
 ...ertyGlobalSerialFilterConfigurationFactory.java |  52 +++
 ...ropertyJmxSerialFilterConfigurationFactory.java |  55 ++++
 ...anctioned-geode-serialization-serializables.txt |   1 +
 .../SerializationDependenciesTest.java             |   2 +-
 .../serialization/filter/ApiPackageTest.java       |  61 ++++
 .../GlobalSerialFilterConfigurationTest.java       |  76 +++++
 .../JmxSerialFilterConfigurationFactoryTest.java   |  65 ++++
 .../filter/JmxSerialFilterConfigurationTest.java   | 155 +++++++++
 .../filter/NullObjectInputFilterTest.java}         |  24 +-
 .../filter/ObjectInputFilterUtilsTest.java         |  94 ++++++
 .../filter/OpenMBeanFilterPatternTest.java         | 142 +++++++++
 ...lectiveFacadeGlobalSerialFilterFactoryTest.java |  71 +++++
 .../ReflectiveFacadeGlobalSerialFilterTest.java    | 104 ++++++
 ...flectiveFacadeObjectInputFilterFactoryTest.java |  84 +++++
 .../ReflectiveFacadeObjectInputFilterTest.java     | 142 +++++++++
 .../ReflectiveObjectInputFilterApiFactoryTest.java |  60 ++++
 .../filter/ReflectiveObjectInputFilterApiTest.java | 223 +++++++++++++
 .../SanctionedSerializablesFilterPatternTest.java  | 206 ++++++++++++
 .../filter/SerializableObjectConfigTest.java       |  46 +++
 ...GlobalSerialFilterConfigurationFactoryTest.java |  75 +++++
 ...rtyJmxSerialFilterConfigurationFactoryTest.java |  90 ++++++
 101 files changed, 5841 insertions(+), 1476 deletions(-)

diff --git a/geode-assembly/src/acceptanceTest/java/org/apache/geode/serialization/filter/StartLocatorGlobalSerialFilterAcceptanceTest.java b/geode-assembly/src/acceptanceTest/java/org/apache/geode/serialization/filter/StartLocatorGlobalSerialFilterAcceptanceTest.java
new file mode 100644
index 0000000..e4cc254
--- /dev/null
+++ b/geode-assembly/src/acceptanceTest/java/org/apache/geode/serialization/filter/StartLocatorGlobalSerialFilterAcceptanceTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.serialization.filter;
+
+import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPorts;
+import static org.apache.geode.test.assertj.LogFileAssert.assertThat;
+import static org.apache.geode.test.awaitility.GeodeAwaitility.await;
+
+import java.nio.file.Path;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import org.apache.geode.test.junit.rules.RequiresGeodeHome;
+import org.apache.geode.test.junit.rules.gfsh.GfshRule;
+
+public class StartLocatorGlobalSerialFilterAcceptanceTest {
+
+  @Rule
+  public RequiresGeodeHome requiresGeodeHome = new RequiresGeodeHome();
+  @Rule
+  public GfshRule gfshRule = new GfshRule();
+
+  private Path locatorFolder;
+  private int locatorPort;
+  private int locatorJmxPort;
+  private Path locatorLogFile;
+
+  @Before
+  public void setUpFiles() {
+    locatorFolder = gfshRule.getTemporaryFolder().getRoot().toPath().toAbsolutePath();
+    locatorLogFile = locatorFolder.resolve("locator.log");
+  }
+
+  @Before
+  public void setUpPorts() {
+    int[] ports = getRandomAvailableTCPPorts(2);
+    locatorPort = ports[0];
+    locatorJmxPort = ports[1];
+  }
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_byDefault() {
+    String startLocatorCommand = String.join(" ",
+        "start locator",
+        "--name=locator",
+        "--dir=" + locatorFolder,
+        "--port=" + locatorPort,
+        "--J=-Dgemfire.jmx-manager-port=" + locatorJmxPort);
+
+    gfshRule.execute(startLocatorCommand);
+
+    await().untilAsserted(() -> {
+      assertThat(locatorLogFile.toFile())
+          .as(locatorLogFile.toFile().getAbsolutePath())
+          .exists()
+          .doesNotContain("Global serial filter is now configured.")
+          .doesNotContain("jdk.serialFilter");
+    });
+  }
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenJdkSerialFilterIsNotBlank() {
+    String startLocatorCommand = String.join(" ",
+        "start locator",
+        "--name=locator",
+        "--dir=" + locatorFolder,
+        "--port=" + locatorPort,
+        "--J=-Dgemfire.jmx-manager-port=" + locatorJmxPort,
+        "--J=-Djdk.serialFilter=*");
+
+    gfshRule.execute(startLocatorCommand);
+
+    await().untilAsserted(() -> {
+      assertThat(locatorLogFile.toFile())
+          .as(locatorLogFile.toFile().getAbsolutePath())
+          .exists()
+          .doesNotContain("Global serial filter is now configured.")
+          .contains("jdk.serialFilter");
+    });
+  }
+
+  @Test
+  public void startConfiguresGlobalSerialFilter_whenEnableGlobalSerialFilterIsTrue() {
+    String startLocatorCommand = String.join(" ",
+        "start locator",
+        "--name=locator",
+        "--dir=" + locatorFolder,
+        "--port=" + locatorPort,
+        "--J=-Dgemfire.jmx-manager-port=" + locatorJmxPort,
+        "--J=-Dgeode.enableGlobalSerialFilter=true");
+
+    gfshRule.execute(startLocatorCommand);
+
+    await().untilAsserted(() -> {
+      assertThat(locatorLogFile.toFile())
+          .as(locatorLogFile.toFile().getAbsolutePath())
+          .exists()
+          .contains("Global serial filter is now configured.")
+          .doesNotContain("jdk.serialFilter");
+    });
+  }
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenEnableGlobalSerialFilterIsTrue_andJdkSerialFilterIsNotBlank() {
+    String startLocatorCommand = String.join(" ",
+        "start locator",
+        "--name=locator",
+        "--dir=" + locatorFolder,
+        "--port=" + locatorPort,
+        "--J=-Dgemfire.jmx-manager-port=" + locatorJmxPort,
+        "--J=-Dgeode.enableGlobalSerialFilter=true",
+        "--J=-Djdk.serialFilter=*");
+
+    gfshRule.execute(startLocatorCommand);
+
+    await().untilAsserted(() -> {
+      assertThat(locatorLogFile.toFile())
+          .as(locatorLogFile.toFile().getAbsolutePath())
+          .exists()
+          .doesNotContain("Global serial filter is now configured.")
+          .contains("jdk.serialFilter");
+    });
+  }
+}
diff --git a/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/LocatorManagerConfiguresJmxSerialFilterAcceptanceTest.java b/geode-assembly/src/acceptanceTest/java/org/apache/geode/serialization/filter/StartLocatorJmxSerialFilterAcceptanceTest.java
similarity index 66%
rename from geode-assembly/src/acceptanceTest/java/org/apache/geode/management/LocatorManagerConfiguresJmxSerialFilterAcceptanceTest.java
rename to geode-assembly/src/acceptanceTest/java/org/apache/geode/serialization/filter/StartLocatorJmxSerialFilterAcceptanceTest.java
index de0b8c8..f6d50c2 100644
--- a/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/LocatorManagerConfiguresJmxSerialFilterAcceptanceTest.java
+++ b/geode-assembly/src/acceptanceTest/java/org/apache/geode/serialization/filter/StartLocatorJmxSerialFilterAcceptanceTest.java
@@ -12,65 +12,61 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.management;
+package org.apache.geode.serialization.filter;
 
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
 import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
 import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
 import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPorts;
-import static org.apache.geode.management.internal.JmxRmiOpenTypesSerialFilter.PROPERTY_NAME;
+import static org.apache.geode.test.assertj.LogFileAssert.assertThat;
 import static org.apache.geode.test.awaitility.GeodeAwaitility.await;
 import static org.assertj.core.api.Assumptions.assumeThat;
 
 import java.nio.file.Path;
 
-import org.apache.commons.lang3.JavaVersion;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
 
-import org.apache.geode.test.assertj.LogFileAssert;
 import org.apache.geode.test.junit.rules.RequiresGeodeHome;
 import org.apache.geode.test.junit.rules.gfsh.GfshRule;
 
-public class LocatorManagerConfiguresJmxSerialFilterAcceptanceTest {
+public class StartLocatorJmxSerialFilterAcceptanceTest {
 
-  private static final String NAME = "the-locator";
-
-  private Path workingDir;
-  private int locatorPort;
-  private int jmxPort;
-  private Path locatorLogFile;
+  private static final String PROPERTY_NAME = "jmx.remote.rmi.server.serial.filter.pattern";
 
   @Rule
   public RequiresGeodeHome requiresGeodeHome = new RequiresGeodeHome();
   @Rule
   public GfshRule gfshRule = new GfshRule();
 
-  @Before
-  public void setUpOutputFiles() {
-    TemporaryFolder temporaryFolder = gfshRule.getTemporaryFolder();
+  private Path locatorFolder;
+  private int locatorPort;
+  private int jmxPort;
+  private Path locatorLogFile;
 
-    workingDir = temporaryFolder.getRoot().toPath().toAbsolutePath();
-    locatorLogFile = workingDir.resolve(NAME + ".log");
+  @Before
+  public void setUpFiles() {
+    locatorFolder = gfshRule.getTemporaryFolder().getRoot().toPath().toAbsolutePath();
+    locatorLogFile = locatorFolder.resolve("locator.log");
   }
 
   @Before
-  public void setUpRandomPorts() {
+  public void setUpPorts() {
     int[] ports = getRandomAvailableTCPPorts(2);
-
     locatorPort = ports[0];
     jmxPort = ports[1];
   }
 
   @Test
-  public void startingLocatorWithJmxManager_configuresSerialFilter_atLeastJava9() {
-    assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue();
+  public void startWithJmxManagerConfiguresJmxSerialFilter_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
 
     String startLocatorCommand = String.join(" ",
         "start locator",
-        "--name=" + NAME,
-        "--dir=" + workingDir,
+        "--name=locator",
+        "--dir=" + locatorFolder,
         "--port=" + locatorPort,
         "--J=-Dgemfire.enable-cluster-configuration=false",
         "--J=-Dgemfire.http-service-port=0",
@@ -81,21 +77,21 @@ public class LocatorManagerConfiguresJmxSerialFilterAcceptanceTest {
     gfshRule.execute(startLocatorCommand);
 
     await().untilAsserted(() -> {
-      LogFileAssert.assertThat(locatorLogFile.toFile())
+      assertThat(locatorLogFile.toFile())
           .as(locatorLogFile.toFile().getAbsolutePath())
           .exists()
-          .contains("System property " + PROPERTY_NAME + " is now configured with");
+          .contains("System property '" + PROPERTY_NAME + "' is now configured with");
     });
   }
 
   @Test
-  public void startingLocatorWithJmxManager_configuresSerialFilter_atMostJava8() {
-    assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue();
+  public void startWithJmxManagerDoesNotConfigureJmxSerialFilter_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
 
     String startLocatorCommand = String.join(" ",
         "start locator",
-        "--name=" + NAME,
-        "--dir=" + workingDir,
+        "--name=locator",
+        "--dir=" + locatorFolder,
         "--port=" + locatorPort,
         "--J=-Dgemfire.enable-cluster-configuration=false",
         "--J=-Dgemfire.http-service-port=0",
@@ -106,10 +102,10 @@ public class LocatorManagerConfiguresJmxSerialFilterAcceptanceTest {
     gfshRule.execute(startLocatorCommand);
 
     await().untilAsserted(() -> {
-      LogFileAssert.assertThat(locatorLogFile.toFile())
+      assertThat(locatorLogFile.toFile())
           .as(locatorLogFile.toFile().getAbsolutePath())
           .exists()
-          .doesNotContain("System property " + PROPERTY_NAME + " is now configured with");
+          .doesNotContain("System property '" + PROPERTY_NAME + "' is now configured with");
     });
   }
 }
diff --git a/geode-assembly/src/acceptanceTest/java/org/apache/geode/serialization/filter/StartServerGlobalSerialFilterAcceptanceTest.java b/geode-assembly/src/acceptanceTest/java/org/apache/geode/serialization/filter/StartServerGlobalSerialFilterAcceptanceTest.java
new file mode 100644
index 0000000..06bd7e2
--- /dev/null
+++ b/geode-assembly/src/acceptanceTest/java/org/apache/geode/serialization/filter/StartServerGlobalSerialFilterAcceptanceTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.serialization.filter;
+
+import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPort;
+import static org.apache.geode.test.awaitility.GeodeAwaitility.await;
+
+import java.io.File;
+import java.nio.file.Path;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import org.apache.geode.test.assertj.LogFileAssert;
+import org.apache.geode.test.junit.rules.RequiresGeodeHome;
+import org.apache.geode.test.junit.rules.gfsh.GfshRule;
+
+public class StartServerGlobalSerialFilterAcceptanceTest {
+
+  @Rule
+  public RequiresGeodeHome requiresGeodeHome = new RequiresGeodeHome();
+  @Rule
+  public GfshRule gfshRule = new GfshRule();
+
+  private File serverFolder;
+  private int jmxPort;
+
+  @Before
+  public void setServerFolder() {
+    serverFolder = gfshRule.getTemporaryFolder().getRoot();
+  }
+
+  @Before
+  public void setJmxPort() {
+    jmxPort = getRandomAvailableTCPPort();
+  }
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_byDefault() {
+    String startServerCommand = String.join(" ",
+        "start server",
+        "--name=server",
+        "--dir=" + serverFolder.getAbsolutePath(),
+        "--disable-default-server",
+        "--J=-Dgemfire.enable-cluster-configuration=false",
+        "--J=-Dgemfire.http-service-port=0",
+        "--J=-Dgemfire.jmx-manager=true",
+        "--J=-Dgemfire.jmx-manager-port=" + jmxPort,
+        "--J=-Dgemfire.jmx-manager-start=true");
+
+    gfshRule.execute(startServerCommand);
+
+    Path serverLogFile = serverFolder.toPath().resolve("server.log");
+    await().untilAsserted(() -> {
+      LogFileAssert.assertThat(serverLogFile.toFile()).exists()
+          .doesNotContain("Global serial filter is now configured.")
+          .doesNotContain("jdk.serialFilter");
+    });
+  }
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenJdkSerialFilterIsNotBlank() {
+    String startServerCommand = String.join(" ",
+        "start server",
+        "--name=server",
+        "--dir=" + serverFolder.getAbsolutePath(),
+        "--disable-default-server",
+        "--J=-Dgemfire.enable-cluster-configuration=false",
+        "--J=-Dgemfire.http-service-port=0",
+        "--J=-Dgemfire.jmx-manager=true",
+        "--J=-Dgemfire.jmx-manager-port=" + jmxPort,
+        "--J=-Dgemfire.jmx-manager-start=true",
+        "--J=-Djdk.serialFilter=*");
+
+    gfshRule.execute(startServerCommand);
+
+    Path serverLogFile = serverFolder.toPath().resolve("server.log");
+    await().untilAsserted(() -> {
+      LogFileAssert.assertThat(serverLogFile.toFile()).exists()
+          .doesNotContain("Global serial filter is now configured.")
+          .contains("jdk.serialFilter");
+    });
+  }
+
+  /**
+   * ServerLauncher is not currently wired up with EnabledGlobalSerialFilterConfigurationFactory
+   * like LocatorLauncher is.
+   */
+  @Test
+  public void startConfiguresGlobalSerialFilter_whenEnableGlobalSerialFilterIsTrue() {
+    String startServerCommand = String.join(" ",
+        "start server",
+        "--name=server",
+        "--dir=" + serverFolder.getAbsolutePath(),
+        "--disable-default-server",
+        "--J=-Dgemfire.enable-cluster-configuration=false",
+        "--J=-Dgemfire.http-service-port=0",
+        "--J=-Dgemfire.jmx-manager=true",
+        "--J=-Dgemfire.jmx-manager-port=" + jmxPort,
+        "--J=-Dgemfire.jmx-manager-start=true",
+        "--J=-Dgeode.enableGlobalSerialFilter=true");
+
+    gfshRule.execute(startServerCommand);
+
+    Path serverLogFile = serverFolder.toPath().resolve("server.log");
+    await().untilAsserted(() -> {
+      LogFileAssert.assertThat(serverLogFile.toFile()).exists()
+          .contains("Global serial filter is now configured.")
+          .doesNotContain("jdk.serialFilter");
+    });
+  }
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenEnableGlobalSerialFilterIsTrue_andJdkSerialFilterIsNotBlank() {
+    String startServerCommand = String.join(" ",
+        "start server",
+        "--name=server",
+        "--dir=" + serverFolder.getAbsolutePath(),
+        "--disable-default-server",
+        "--J=-Dgemfire.enable-cluster-configuration=false",
+        "--J=-Dgemfire.http-service-port=0",
+        "--J=-Dgemfire.jmx-manager=true",
+        "--J=-Dgemfire.jmx-manager-port=" + jmxPort,
+        "--J=-Dgemfire.jmx-manager-start=true",
+        "--J=-Dgeode.enableGlobalSerialFilter=true",
+        "--J=-Djdk.serialFilter=*");
+
+    gfshRule.execute(startServerCommand);
+
+    Path serverLogFile = serverFolder.toPath().resolve("server.log");
+    await().untilAsserted(() -> {
+      LogFileAssert.assertThat(serverLogFile.toFile()).exists()
+          .doesNotContain("Global serial filter is now configured.")
+          .contains("jdk.serialFilter");
+    });
+  }
+}
diff --git a/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/ServerManagerConfiguresJmxSerialFilterAcceptanceTest.java b/geode-assembly/src/acceptanceTest/java/org/apache/geode/serialization/filter/StartServerJmxSerialFilterAcceptanceTest.java
similarity index 66%
rename from geode-assembly/src/acceptanceTest/java/org/apache/geode/management/ServerManagerConfiguresJmxSerialFilterAcceptanceTest.java
rename to geode-assembly/src/acceptanceTest/java/org/apache/geode/serialization/filter/StartServerJmxSerialFilterAcceptanceTest.java
index 7754dec..bf03b22 100644
--- a/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/ServerManagerConfiguresJmxSerialFilterAcceptanceTest.java
+++ b/geode-assembly/src/acceptanceTest/java/org/apache/geode/serialization/filter/StartServerJmxSerialFilterAcceptanceTest.java
@@ -12,61 +12,58 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.management;
+package org.apache.geode.serialization.filter;
 
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
 import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
 import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
 import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPort;
-import static org.apache.geode.management.internal.JmxRmiOpenTypesSerialFilter.PROPERTY_NAME;
+import static org.apache.geode.test.assertj.LogFileAssert.assertThat;
 import static org.apache.geode.test.awaitility.GeodeAwaitility.await;
 import static org.assertj.core.api.Assumptions.assumeThat;
 
 import java.nio.file.Path;
 
-import org.apache.commons.lang3.JavaVersion;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
 
-import org.apache.geode.test.assertj.LogFileAssert;
 import org.apache.geode.test.junit.rules.RequiresGeodeHome;
 import org.apache.geode.test.junit.rules.gfsh.GfshRule;
 
-public class ServerManagerConfiguresJmxSerialFilterAcceptanceTest {
+public class StartServerJmxSerialFilterAcceptanceTest {
 
-  private static final String NAME = "the-server";
-
-  private Path workingDir;
-  private int jmxPort;
-  private Path serverLogFile;
+  private static final String PROPERTY_NAME = "jmx.remote.rmi.server.serial.filter.pattern";
 
   @Rule
   public RequiresGeodeHome requiresGeodeHome = new RequiresGeodeHome();
   @Rule
   public GfshRule gfshRule = new GfshRule();
 
-  @Before
-  public void setUpOutputFiles() {
-    TemporaryFolder temporaryFolder = gfshRule.getTemporaryFolder();
+  private Path serverFolder;
+  private int jmxPort;
+  private Path serverLogFile;
 
-    workingDir = temporaryFolder.getRoot().toPath().toAbsolutePath();
-    serverLogFile = workingDir.resolve(NAME + ".log");
+  @Before
+  public void setUpFiles() {
+    serverFolder = gfshRule.getTemporaryFolder().getRoot().toPath().toAbsolutePath();
+    serverLogFile = serverFolder.resolve("server.log");
   }
 
   @Before
-  public void setUpRandomPorts() {
+  public void setUpPorts() {
     jmxPort = getRandomAvailableTCPPort();
   }
 
   @Test
-  public void startingServerWithJmxManager_configuresSerialFilter_atLeastJava9() {
-    assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue();
+  public void startWithJmxManagerConfiguresJmxSerialFilter_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
 
     String startServerCommand = String.join(" ",
         "start server",
-        "--name=" + NAME,
-        "--dir=" + workingDir,
+        "--name=server",
+        "--dir=" + serverFolder,
         "--disable-default-server",
         "--J=-Dgemfire.enable-cluster-configuration=false",
         "--J=-Dgemfire.http-service-port=0",
@@ -77,21 +74,21 @@ public class ServerManagerConfiguresJmxSerialFilterAcceptanceTest {
     gfshRule.execute(startServerCommand);
 
     await().untilAsserted(() -> {
-      LogFileAssert.assertThat(serverLogFile.toFile())
+      assertThat(serverLogFile.toFile())
           .as(serverLogFile.toFile().getAbsolutePath())
           .exists()
-          .contains("System property " + PROPERTY_NAME + " is now configured with");
+          .contains("System property '" + PROPERTY_NAME + "' is now configured with");
     });
   }
 
   @Test
-  public void startingServerWithJmxManager_configuresSerialFilter_atMostJava8() {
-    assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue();
+  public void startWithJmxManagerDoesNotConfigureJmxSerialFilter_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
 
     String startServerCommand = String.join(" ",
         "start server",
-        "--name=" + NAME,
-        "--dir=" + workingDir,
+        "--name=server",
+        "--dir=" + serverFolder,
         "--disable-default-server",
         "--J=-Dgemfire.enable-cluster-configuration=false",
         "--J=-Dgemfire.http-service-port=0",
@@ -102,10 +99,10 @@ public class ServerManagerConfiguresJmxSerialFilterAcceptanceTest {
     gfshRule.execute(startServerCommand);
 
     await().untilAsserted(() -> {
-      LogFileAssert.assertThat(serverLogFile.toFile())
+      assertThat(serverLogFile.toFile())
           .as(serverLogFile.toFile().getAbsolutePath())
           .exists()
-          .doesNotContain("System property " + PROPERTY_NAME + " is now configured with");
+          .doesNotContain("System property '" + PROPERTY_NAME + "' is now configured with");
     });
   }
 }
diff --git a/geode-core/build.gradle b/geode-core/build.gradle
index b129c55..51ee9be 100755
--- a/geode-core/build.gradle
+++ b/geode-core/build.gradle
@@ -315,7 +315,7 @@ dependencies {
     implementation(project(':geode-logging'))
     implementation(project(':geode-membership'))
     implementation(project(':geode-unsafe'))
-    implementation(project(':geode-serialization'))
+    api(project(':geode-serialization'))
     implementation(project(':geode-tcp-server'))
 
     //geode-management currently has pieces of the public API
diff --git a/geode-core/src/distributedTest/java/org/apache/geode/cache/ValidateSerializableObjectsDistributedTest.java b/geode-core/src/distributedTest/java/org/apache/geode/cache/ValidateSerializableObjectsDistributedTest.java
new file mode 100644
index 0000000..27d60d3
--- /dev/null
+++ b/geode-core/src/distributedTest/java/org/apache/geode/cache/ValidateSerializableObjectsDistributedTest.java
@@ -0,0 +1,320 @@
+/*
+ * 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.cache;
+
+import static java.util.Arrays.asList;
+import static org.apache.geode.cache.RegionShortcut.REPLICATE;
+import static org.apache.geode.distributed.ConfigurationProperties.ENABLE_CLUSTER_CONFIGURATION;
+import static org.apache.geode.distributed.ConfigurationProperties.LOCATORS;
+import static org.apache.geode.distributed.ConfigurationProperties.VALIDATE_SERIALIZABLE_OBJECTS;
+import static org.apache.geode.test.dunit.IgnoredException.addIgnoredException;
+import static org.apache.geode.test.dunit.VM.getVM;
+import static org.apache.geode.test.dunit.rules.DistributedRule.getLocatorPort;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InvalidClassException;
+import java.io.NotSerializableException;
+import java.io.Serializable;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import org.apache.geode.InternalGemFireException;
+import org.apache.geode.SerializationException;
+import org.apache.geode.cache.util.CacheListenerAdapter;
+import org.apache.geode.distributed.ServerLauncher;
+import org.apache.geode.test.dunit.VM;
+import org.apache.geode.test.dunit.rules.DistributedReference;
+import org.apache.geode.test.dunit.rules.DistributedRule;
+import org.apache.geode.test.junit.rules.serializable.SerializableTemporaryFolder;
+
+@SuppressWarnings("serial")
+public class ValidateSerializableObjectsDistributedTest implements Serializable {
+
+  private VM server1;
+  private VM server2;
+
+  private File server1Dir;
+  private File server2Dir;
+  private int locatorPort;
+
+  @Rule
+  public DistributedRule distributedRule = new DistributedRule();
+  @Rule
+  public DistributedReference<ServerLauncher> server = new DistributedReference<>();
+  @Rule
+  public SerializableTemporaryFolder temporaryFolder = new SerializableTemporaryFolder();
+
+  @Before
+  public void setUp() throws IOException {
+    server1 = getVM(0);
+    server2 = getVM(1);
+
+    server1Dir = temporaryFolder.newFolder("server1");
+    server2Dir = temporaryFolder.newFolder("server2");
+
+    locatorPort = getLocatorPort();
+
+    server1.invoke(() -> {
+      server.set(startServer("server1", server1Dir));
+    });
+    server2.invoke(() -> {
+      server.set(startServer("server2", server2Dir));
+    });
+
+    asList(server1, server2).forEach(vm -> vm.invoke(() -> {
+      server.get().getCache()
+          .createRegionFactory(REPLICATE)
+          .addCacheListener(new CacheListenerAdapter<Object, Object>() {
+            @Override
+            public void afterCreate(EntryEvent<Object, Object> event) {
+              // cache listener afterCreate causes all creates to deserialize the value which causes
+              // the tests to pass if serialization filter is configured
+              assertThat(event.getNewValue()).isNotNull();
+            }
+          })
+          .create("region");
+    }));
+  }
+
+  @Test
+  public void stringIsAllowed() {
+    server1.invoke(() -> {
+      Region<Object, Object> region = server.get().getCache().getRegion("region");
+
+      Object key = "key";
+      Object value = "value";
+
+      // puts entry locally and propagates to server2
+      region.put(key, value);
+
+      // entry exists in server1
+      assertThat(region.containsKey(key)).isTrue();
+      assertThat(region.get(key)).isSameAs(value);
+    });
+
+    server2.invoke(() -> {
+      Region<Object, Object> region = server.get().getCache().getRegion("region");
+
+      Object key = "key";
+      Object value = "value";
+
+      // entry exists in server2
+      assertThat(region.containsKey(key)).isTrue();
+      assertThat(region.get(key)).isEqualTo(value);
+    });
+  }
+
+  @Test
+  public void primitiveIsAllowed() {
+    server1.invoke(() -> {
+      Region<Object, Object> region = server.get().getCache().getRegion("region");
+
+      Object key = 1;
+      Object value = 1;
+
+      // puts entry locally and propagates to server2
+      region.put(key, value);
+
+      // entry exists in server1
+      assertThat(region.containsKey(key)).isTrue();
+      assertThat(region.get(key)).isSameAs(value);
+    });
+
+    server2.invoke(() -> {
+      Region<Object, Object> region = server.get().getCache().getRegion("region");
+
+      Object key = 1;
+      Object value = 1;
+
+      // entry exists in server2
+      assertThat(region.containsKey(key)).isTrue();
+      assertThat(region.get(key)).isEqualTo(value);
+    });
+  }
+
+  @Test
+  public void nonSerializableThrowsNotSerializableException() {
+    server1.invoke(() -> {
+      Region<Object, Object> region = server.get().getCache().getRegion("region");
+
+      Object key = new Object();
+      Object value = new Object();
+
+      // put stores entry locally and then tries to propagate to server2
+      Throwable thrown = catchThrowable(() -> {
+        region.put(key, value);
+      });
+
+      // key and value fail to serialize non-serializable objects in server1
+      assertThat(thrown).hasCauseExactlyInstanceOf(NotSerializableException.class);
+
+      // entry exists in server1 despite serialization failure
+      assertThat(region.containsKey(key)).isTrue();
+      assertThat(region.get(key)).isSameAs(value);
+    });
+
+    server2.invoke(() -> {
+      Region<Object, Object> region = server.get().getCache().getRegion("region");
+
+      Object key = new Object();
+
+      // entry does NOT exist in server2 at all
+      assertThat(region.containsKey(key)).isFalse();
+      assertThat(region.get(key)).isNull();
+    });
+  }
+
+  @Test
+  public void nonAllowedValueFailsToDeserializeInOtherServer() {
+    addIgnoredException(InvalidClassException.class);
+    addIgnoredException(SerializationException.class);
+
+    server1.invoke(() -> {
+      Region<Object, Object> region = server.get().getCache().getRegion("region");
+
+      Object key = "key";
+      Object value = new SerializableClass();
+
+      // put stores entry locally and sends serialized key/value to server2
+      region.put(key, value);
+
+      // entry exists in server1
+      assertThat(region.get(key)).isInstanceOf(SerializableClass.class);
+    });
+
+    server2.invoke(() -> {
+      Region<Object, Object> region = server.get().getCache().getRegion("region");
+
+      Object key = "key";
+
+      // key exists in server2
+      assertThat(region.containsKey(key)).isTrue();
+
+      // get tries to fetch value from server1 if it's missing in server2
+      Throwable thrown = catchThrowable(() -> {
+        region.get(key);
+      });
+
+      // value fails to deserialize in server2
+      assertThat(thrown)
+          .isInstanceOf(SerializationException.class)
+          .hasCauseInstanceOf(InvalidClassException.class);
+    });
+  }
+
+  @Test
+  public void nonAllowedKeyFailsToDeserializeInOtherServer() {
+    addIgnoredException(InvalidClassException.class);
+    addIgnoredException(IOException.class);
+
+    server1.invoke(() -> {
+      Region<Object, Object> region = server.get().getCache().getRegion("region");
+
+      Object key = new SerializableClass();
+      Object value = "value";
+
+      // put stores entry locally and ails to serialize for propagation to server2
+      Throwable thrown = catchThrowable(() -> {
+        region.put(key, value);
+      });
+
+      // put fails to create entry locally because key is not serializable
+      assertThat(thrown)
+          .isInstanceOf(InternalGemFireException.class)
+          .hasCauseInstanceOf(IOException.class)
+          .hasRootCauseInstanceOf(InvalidClassException.class);
+
+      // entry exists in server1
+      assertThat(region.containsKey(key)).isTrue();
+      assertThat(region.get(key)).isEqualTo(value);
+    });
+
+    server2.invoke(() -> {
+      Region<Object, Object> region = server.get().getCache().getRegion("region");
+
+      Object key = new Object();
+
+      // entry does NOT exist in server2
+      assertThat(region.containsKey(key)).isFalse();
+      assertThat(region.get(key)).isNull();
+    });
+  }
+
+  @Test
+  public void nonAllowedKeyAndValueThrowsInsteadOfPutting() {
+    addIgnoredException(InvalidClassException.class);
+    addIgnoredException(IOException.class);
+
+    server1.invoke(() -> {
+      Region<Object, Object> region = server.get().getCache().getRegion("region");
+
+      Object key = new SerializableClass();
+      Object value = new SerializableClass();
+
+      // put stores entry locally tries sending to server/key to server2
+      Throwable thrown = catchThrowable(() -> {
+        region.put(key, value);
+      });
+
+      // value fails to propagate to server2
+      assertThat(thrown)
+          .isInstanceOf(InternalGemFireException.class)
+          .hasCauseInstanceOf(IOException.class)
+          .hasRootCauseInstanceOf(InvalidClassException.class);
+    });
+
+    server2.invoke(() -> {
+      Region<Object, Object> region = server.get().getCache().getRegion("region");
+
+      Object key = new SerializableClass();
+
+      // entry does NOT exist in server2
+      assertThat(region.containsKey(key)).isFalse();
+      assertThat(region.get(key)).isNull();
+    });
+  }
+
+  private ServerLauncher startServer(String serverName, File serverDir) {
+    ServerLauncher serverLauncher = new ServerLauncher.Builder()
+        .setDisableDefaultServer(true)
+        .setDeletePidFileOnStop(true)
+        .setMemberName(serverName)
+        .setWorkingDirectory(serverDir.getAbsolutePath())
+        .setServerPort(0)
+        .set(ENABLE_CLUSTER_CONFIGURATION, "false")
+        .set(LOCATORS, "localHost[" + locatorPort + "]")
+        .set(VALIDATE_SERIALIZABLE_OBJECTS, "true")
+        .build();
+
+    serverLauncher.start();
+
+    return serverLauncher;
+  }
+
+  /**
+   * Simple Java serializable class that is not accept listed using serializable-object-filter.
+   * Deserialization of instances of this type will be rejected when validate-serializable-objects
+   * is enabled.
+   */
+  public static class SerializableClass implements Serializable {
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/distributed/LocatorIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/distributed/LocatorIntegrationTest.java
index 329197e..92212a8 100644
--- a/geode-core/src/integrationTest/java/org/apache/geode/distributed/LocatorIntegrationTest.java
+++ b/geode-core/src/integrationTest/java/org/apache/geode/distributed/LocatorIntegrationTest.java
@@ -15,84 +15,56 @@
 package org.apache.geode.distributed;
 
 import static org.apache.geode.distributed.ConfigurationProperties.ENABLE_CLUSTER_CONFIGURATION;
-import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_HTTP_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
 import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
 import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
-import static org.apache.geode.distributed.ConfigurationProperties.LOCATOR_WAIT_TIME;
 import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
 import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT;
 import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPort;
-import static org.apache.geode.util.internal.GeodeGlossary.GEMFIRE_PREFIX;
+import static org.apache.geode.internal.security.SecurableCommunicationChannel.LOCATOR;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.catchThrowable;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.List;
 import java.util.Properties;
-import java.util.function.IntSupplier;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.contrib.java.lang.system.RestoreSystemProperties;
 import org.junit.experimental.categories.Category;
 import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-import org.junit.runners.Parameterized.UseParametersRunnerFactory;
 
 import org.apache.geode.distributed.internal.InternalLocator;
-import org.apache.geode.distributed.internal.ServerLocation;
 import org.apache.geode.distributed.internal.tcpserver.HostAndPort;
 import org.apache.geode.distributed.internal.tcpserver.TcpClient;
 import org.apache.geode.distributed.internal.tcpserver.TcpSocketFactory;
-import org.apache.geode.internal.AvailablePortHelper;
 import org.apache.geode.internal.InternalDataSerializer;
-import org.apache.geode.internal.cache.GemFireCacheImpl;
+import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.net.SocketCreatorFactory;
-import org.apache.geode.internal.security.SecurableCommunicationChannel;
 import org.apache.geode.logging.internal.OSProcess;
+import org.apache.geode.management.ManagementService;
 import org.apache.geode.management.internal.JmxManagerAdvisor.JmxManagerProfile;
+import org.apache.geode.management.internal.SystemManagementService;
 import org.apache.geode.management.internal.configuration.messages.SharedConfigurationStatusRequest;
 import org.apache.geode.test.junit.categories.MembershipTest;
-import org.apache.geode.test.junit.runners.CategoryWithParameterizedRunnerFactory;
 
-@Category({MembershipTest.class})
-@RunWith(Parameterized.class)
-@UseParametersRunnerFactory(CategoryWithParameterizedRunnerFactory.class)
+@Category(MembershipTest.class)
 public class LocatorIntegrationTest {
 
   private Locator locator;
-  private File tmpFile;
+  private File logFile;
   private int port;
 
-  @Parameters
-  public static Collection<Object> data() {
-    return Arrays.asList(new Object[] {
-        (IntSupplier) () -> 0,
-        (IntSupplier) AvailablePortHelper::getRandomAvailableTCPPort});
-  }
-
-  @Parameter
-  public IntSupplier portSupplier;
-
-  @Rule
-  public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
-
   @Rule
   public TemporaryFolder temporaryFolder = new TemporaryFolder();
 
   @Before
   public void setUp() throws IOException {
-    tmpFile = File.createTempFile("locator", ".log");
-    port = portSupplier.getAsInt();
-    deleteLocatorViewFile(port);
+    logFile = temporaryFolder.newFile("locator.log");
   }
 
   @After
@@ -100,21 +72,22 @@ public class LocatorIntegrationTest {
     if (locator != null) {
       locator.stop();
     }
-    assertThat(Locator.hasLocator()).isFalse();
+    deleteLocatorViewFile(port);
   }
 
   /**
    * Fix: locator creates "locator0view.dat" file when started with port 0
    */
   @Test
-  public void testThatLocatorDoesNotCreateFileWithZeroPort() throws Exception {
+  public void doesNotCreateZeroPortViewFileForEphemeralPort() throws Exception {
     deleteLocatorViewFile(0);
 
     Properties configProperties = new Properties();
-    configProperties.setProperty(MCAST_PORT, "0");
     configProperties.setProperty(ENABLE_CLUSTER_CONFIGURATION, "false");
-    configProperties.setProperty(LOCATOR_WAIT_TIME, "1"); // seconds
+    configProperties.setProperty(HTTP_SERVICE_PORT, "0");
+    configProperties.setProperty(JMX_MANAGER, "false");
     configProperties.setProperty(LOG_FILE, "");
+    configProperties.setProperty(MCAST_PORT, "0");
 
     locator = Locator.startLocatorAndDS(port, null, configProperties);
 
@@ -123,75 +96,121 @@ public class LocatorIntegrationTest {
     assertThat(viewFile).doesNotExist();
   }
 
+  @Test
+  public void locatorStartsOnSpecifiedPort() throws IOException {
+    Properties configProperties = new Properties();
+    configProperties.setProperty(ENABLE_CLUSTER_CONFIGURATION, "false");
+    configProperties.setProperty(HTTP_SERVICE_PORT, "0");
+    configProperties.setProperty(JMX_MANAGER, "false");
+    configProperties.setProperty(LOG_FILE, "");
+    configProperties.setProperty(MCAST_PORT, "0");
+
+    port = getRandomAvailableTCPPort();
+    locator = Locator.startLocatorAndDS(port, null, configProperties);
+
+    port = locator.getPort();
+    assertThat(port).isEqualTo(port);
+  }
+
+  @Test
+  public void locatorStartsOnEphemeralPort() throws IOException {
+    Properties configProperties = new Properties();
+    configProperties.setProperty(ENABLE_CLUSTER_CONFIGURATION, "false");
+    configProperties.setProperty(HTTP_SERVICE_PORT, "0");
+    configProperties.setProperty(JMX_MANAGER, "false");
+    configProperties.setProperty(LOG_FILE, "");
+    configProperties.setProperty(MCAST_PORT, "0");
+
+    locator = Locator.startLocatorAndDS(0, null, configProperties);
+
+    port = locator.getPort();
+    assertThat(port).isNotZero();
+  }
+
   /**
    * Fix: if jmx-manager-start is true in a locator then gfsh connect will fail
    */
   @Test
-  public void testGfshConnectShouldSucceedIfJmxManagerStartIsTrueInLocator() throws Exception {
+  public void gfshConnectsIfJmxManagerStartIsTrue() throws Exception {
     int jmxPort = getRandomAvailableTCPPort();
 
     Properties configProperties = new Properties();
-    configProperties.setProperty(MCAST_PORT, "0");
-    configProperties.setProperty(JMX_MANAGER_PORT, "" + jmxPort);
-    configProperties.setProperty(JMX_MANAGER_START, "true");
-    configProperties.setProperty(JMX_MANAGER_HTTP_PORT, "0");
     configProperties.setProperty(ENABLE_CLUSTER_CONFIGURATION, "false");
+    configProperties.setProperty(HTTP_SERVICE_PORT, "0");
+    configProperties.setProperty(JMX_MANAGER_PORT, String.valueOf(jmxPort));
+    configProperties.setProperty(JMX_MANAGER_START, "true");
     configProperties.setProperty(LOG_FILE, "");
-
-    // not needed
-    System.setProperty(GEMFIRE_PREFIX + "disableManagement", "false");
+    configProperties.setProperty(MCAST_PORT, "0");
 
     locator = Locator.startLocatorAndDS(port, null, configProperties);
-    List<JmxManagerProfile> alreadyManaging =
-        GemFireCacheImpl.getInstance().getJmxManagerAdvisor().adviseAlreadyManaging();
+    InternalLocator internalLocator = (InternalLocator) locator;
+    InternalCache cache = internalLocator.getCache();
+    List<JmxManagerProfile> alreadyManaging = cache.getJmxManagerAdvisor().adviseAlreadyManaging();
 
     assertThat(alreadyManaging).hasSize(1);
-    assertThat(alreadyManaging.get(0).getDistributedMember())
-        .isEqualTo(GemFireCacheImpl.getInstance().getMyId());
+    assertThat(alreadyManaging.get(0).getDistributedMember()).isEqualTo(cache.getMyId());
   }
 
   @Test
-  public void testHandlersAreWaitedOn() throws Exception {
+  public void hasHandlerForSharedConfigurationStatusRequest() throws Exception {
     Properties configProperties = new Properties();
-    configProperties.setProperty(MCAST_PORT, "0");
     configProperties.setProperty(ENABLE_CLUSTER_CONFIGURATION, "false");
-    configProperties.setProperty(LOCATOR_WAIT_TIME, "1"); // seconds
+    configProperties.setProperty(HTTP_SERVICE_PORT, "0");
+    configProperties.setProperty(JMX_MANAGER, "false");
     configProperties.setProperty(LOG_FILE, "");
+    configProperties.setProperty(MCAST_PORT, "0");
 
     locator = Locator.startLocatorAndDS(port, null, configProperties);
 
     InternalLocator internalLocator = (InternalLocator) locator;
 
     // the locator should always install a SharedConfigurationStatusRequest handler
-    assertThat(internalLocator.hasHandlerForClass(SharedConfigurationStatusRequest.class)).isTrue();
+    boolean hasHandler = internalLocator.hasHandlerForClass(SharedConfigurationStatusRequest.class);
+    assertThat(hasHandler).isTrue();
+  }
+
+  @Test
+  public void infoRequestIncludesActualPortWhenSpecifiedPortIsZero() throws Exception {
+    locator = Locator.startLocator(0, logFile);
+    port = locator.getPort();
+    TcpClient client = new TcpClient(
+        SocketCreatorFactory.getSocketCreatorForComponent(LOCATOR),
+        InternalDataSerializer.getDSFIDSerializer().getObjectSerializer(),
+        InternalDataSerializer.getDSFIDSerializer().getObjectDeserializer(),
+        TcpSocketFactory.DEFAULT);
+
+    String[] info = client.getInfo(new HostAndPort("localhost", port));
+
+    assertThat(info).isNotNull();
+    assertThat(info.length).isGreaterThanOrEqualTo(1);
   }
 
   @Test
-  public void testBasicInfo() throws Exception {
-    locator = Locator.startLocator(port, tmpFile);
-    int boundPort = port == 0 ? locator.getPort() : port;
-    TcpClient client = new TcpClient(SocketCreatorFactory
-        .getSocketCreatorForComponent(SecurableCommunicationChannel.LOCATOR),
+  public void infoRequestIncludesActualPortWhenSpecifiedIsNonZero() throws Exception {
+    locator = Locator.startLocator(getRandomAvailableTCPPort(), logFile);
+    port = locator.getPort();
+    TcpClient client = new TcpClient(
+        SocketCreatorFactory.getSocketCreatorForComponent(LOCATOR),
         InternalDataSerializer.getDSFIDSerializer().getObjectSerializer(),
         InternalDataSerializer.getDSFIDSerializer().getObjectDeserializer(),
         TcpSocketFactory.DEFAULT);
-    String[] info = client.getInfo(new HostAndPort("localhost", boundPort));
+
+    String[] info = client.getInfo(new HostAndPort("localhost", port));
 
     assertThat(info).isNotNull();
     assertThat(info.length).isGreaterThanOrEqualTo(1);
   }
 
   @Test
-  public void testNoThreadLeftBehind() throws Exception {
+  public void threadsAreCleanedUpWhenStartFails() throws Exception {
     Properties configProperties = new Properties();
+    configProperties.setProperty(LOG_FILE, "");
     configProperties.setProperty(MCAST_PORT, "0");
-    configProperties.setProperty(JMX_MANAGER_START, "false");
-    configProperties.setProperty(ENABLE_CLUSTER_CONFIGURATION, "false");
 
     int threadCount = Thread.activeCount();
 
     Throwable thrown = catchThrowable(
-        () -> locator = Locator.startLocatorAndDS(-2, new File(""), configProperties));
+        () -> locator = Locator.startLocatorAndDS(-2, null, configProperties));
 
     assertThat(thrown).isInstanceOf(IllegalArgumentException.class);
 
@@ -206,22 +225,31 @@ public class LocatorIntegrationTest {
     assertThat(threadCount)
         .as("Expected " + threadCount + " threads or fewer but found " + Thread.activeCount()
             + ". Check log file for a thread dump.")
+        .withThreadDumpOnError()
         .isGreaterThanOrEqualTo(Thread.activeCount());
   }
 
   /**
-   * Make sure two ServerLocation objects on different hosts but with the same port are not equal
-   *
-   * <p>
-   * Fix: LoadBalancing directs all traffic to a single cache server if all servers are started on
-   * the same port
+   * Validates that Locator.startLocatorAndDS does not start a JMX Manager by default. Only the
+   * LocatorLauncher starts one by default.
    */
   @Test
-  public void testServerLocationOnDifferentHostsShouldNotTestEqual() {
-    ServerLocation serverLocation1 = new ServerLocation("host1", 777);
-    ServerLocation serverLocation2 = new ServerLocation("host2", 777);
+  public void doesNotStartJmxManagerByDefault() throws Exception {
+    Properties configProperties = new Properties();
+    configProperties.setProperty(ENABLE_CLUSTER_CONFIGURATION, "false");
+    configProperties.setProperty(HTTP_SERVICE_PORT, "0");
+    configProperties.setProperty(LOG_FILE, "");
+    configProperties.setProperty(MCAST_PORT, "0");
+
+    locator = Locator.startLocatorAndDS(port, null, configProperties);
+
+    InternalLocator internalLocator = (InternalLocator) locator;
+    InternalCache cache = internalLocator.getCache();
+    SystemManagementService managementService =
+        (SystemManagementService) ManagementService.getManagementService(cache);
 
-    assertThat(serverLocation1).isNotEqualTo(serverLocation2);
+    boolean isManager = managementService.isManager();
+    assertThat(isManager).isFalse();
   }
 
   private void deleteLocatorViewFile(int portNumber) {
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/distributed/LocatorLauncherJmxManagerLocalRegressionTest.java b/geode-core/src/integrationTest/java/org/apache/geode/distributed/LocatorLauncherJmxManagerLocalRegressionTest.java
index d304b91..430d8b1 100644
--- a/geode-core/src/integrationTest/java/org/apache/geode/distributed/LocatorLauncherJmxManagerLocalRegressionTest.java
+++ b/geode-core/src/integrationTest/java/org/apache/geode/distributed/LocatorLauncherJmxManagerLocalRegressionTest.java
@@ -35,7 +35,7 @@ import org.apache.geode.internal.process.ProcessType;
  * Regression tests for stopping a JMX Manager process launched with {@link LocatorLauncher}.
  *
  * <p>
- * Confirms fix for <bold>Locator not stopping correctly if jmx-manager-port=0</bold>
+ * Confirms fix for <strong>Locator not stopping correctly if jmx-manager-port=0</strong>
  *
  * <p>
  * Refactored from LocatorLauncherAssemblyIntegrationTest which used to be in geode-assembly.
@@ -104,6 +104,26 @@ public class LocatorLauncherJmxManagerLocalRegressionTest
     assertThatThreadsStopped();
   }
 
+  @Test
+  public void startsLocatorWithJmxManager() {
+    startLocator(newBuilder()
+        .setDeletePidFileOnStop(true)
+        .setMemberName(getUniqueName())
+        .setPort(defaultLocatorPort)
+        .setRedirectOutput(false)
+        .setWorkingDirectory(getWorkingDirectoryPath())
+        .set(ENABLE_CLUSTER_CONFIGURATION, "false")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_START, "true")
+        .set(JMX_MANAGER_PORT, String.valueOf(jmxManagerPort))
+        .set(LOG_LEVEL, "config"));
+
+    stopLocator();
+
+    assertDeletionOf(getPidFile());
+    assertThatThreadsStopped();
+  }
+
   private void assertThatThreadsStopped() {
     await()
         .atMost(AWAIT_MILLIS, MILLISECONDS)
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterBlankIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterBlankIntegrationTest.java
new file mode 100644
index 0000000..0b352c4
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterBlankIntegrationTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.apache.geode.internal.serialization.filter.SerialFilterAssertions.assertThatSerialFilterIsSameAs;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.LocatorLauncher;
+
+public class LocatorLauncherGlobalSerialFilterBlankIntegrationTest
+    extends LocatorLauncherWithJmxManager {
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenFilterIsBlank()
+      throws InvocationTargetException, IllegalAccessException {
+    Object existingGlobalSerialFilter = OBJECT_INPUT_FILTER_API.createFilter(" ");
+    OBJECT_INPUT_FILTER_API.setSerialFilter(existingGlobalSerialFilter);
+
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThatSerialFilterIsSameAs(existingGlobalSerialFilter);
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterExistsIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterExistsIntegrationTest.java
new file mode 100644
index 0000000..c948f59
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterExistsIntegrationTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.apache.geode.internal.serialization.filter.SerialFilterAssertions.assertThatSerialFilterIsSameAs;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.LocatorLauncher;
+
+public class LocatorLauncherGlobalSerialFilterExistsIntegrationTest
+    extends LocatorLauncherWithJmxManager {
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenFilterExists()
+      throws InvocationTargetException, IllegalAccessException {
+    Object existingGlobalSerialFilter = OBJECT_INPUT_FILTER_API.createFilter("!*");
+    OBJECT_INPUT_FILTER_API.setSerialFilter(existingGlobalSerialFilter);
+
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThatSerialFilterIsSameAs(existingGlobalSerialFilter);
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterIntegrationTest.java
new file mode 100644
index 0000000..8be64f3
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterIntegrationTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.apache.geode.internal.serialization.filter.SerialFilterAssertions.assertThatSerialFilterIsNull;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.LocatorLauncher;
+
+public class LocatorLauncherGlobalSerialFilterIntegrationTest
+    extends LocatorLauncherWithJmxManager {
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenFilterDoesNotExist()
+      throws InvocationTargetException, IllegalAccessException {
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThatSerialFilterIsNull();
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterPropertyBlankIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterPropertyBlankIntegrationTest.java
new file mode 100644
index 0000000..ea021f4
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterPropertyBlankIntegrationTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.apache.geode.internal.serialization.filter.SerialFilterAssertions.assertThatSerialFilterIsNull;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.LocatorLauncher;
+
+public class LocatorLauncherGlobalSerialFilterPropertyBlankIntegrationTest
+    extends LocatorLauncherWithJmxManager {
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenPropertyIsBlank()
+      throws InvocationTargetException, IllegalAccessException {
+    System.setProperty(JDK_PROPERTY, " ");
+
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThatSerialFilterIsNull();
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterPropertyEmptyIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterPropertyEmptyIntegrationTest.java
new file mode 100644
index 0000000..52b46f6
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherGlobalSerialFilterPropertyEmptyIntegrationTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.apache.geode.internal.serialization.filter.SerialFilterAssertions.assertThatSerialFilterIsNull;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.LocatorLauncher;
+
+public class LocatorLauncherGlobalSerialFilterPropertyEmptyIntegrationTest
+    extends LocatorLauncherWithJmxManager {
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenPropertyIsEmpty()
+      throws InvocationTargetException, IllegalAccessException {
+    System.setProperty(JDK_PROPERTY, "");
+
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThatSerialFilterIsNull();
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherJmxSerialFilterIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherJmxSerialFilterIntegrationTest.java
new file mode 100644
index 0000000..66f7137
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherJmxSerialFilterIntegrationTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.LocatorLauncher;
+
+public class LocatorLauncherJmxSerialFilterIntegrationTest
+    extends LocatorLauncherWithJmxManager {
+
+  @Test
+  public void startConfiguresJmxSerialFilter_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
+
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo(openMBeanFilterPattern);
+  }
+
+  @Test
+  public void startDoesNotConfigureJmxSerialFilter_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
+
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isNull();
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherJmxSerialFilterPropertyBlankIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherJmxSerialFilterPropertyBlankIntegrationTest.java
new file mode 100644
index 0000000..bb6b01b
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherJmxSerialFilterPropertyBlankIntegrationTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.LocatorLauncher;
+
+public class LocatorLauncherJmxSerialFilterPropertyBlankIntegrationTest
+    extends LocatorLauncherWithJmxManager {
+
+  @Test
+  public void startConfiguresJmxSerialFilter_whenPropertyIsBlank_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
+
+    System.setProperty(JMX_PROPERTY, " ");
+
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo(openMBeanFilterPattern);
+  }
+
+  @Test
+  public void startDoesNotConfigureJmxSerialFilter_whenPropertyIsBlank_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
+
+    System.setProperty(JMX_PROPERTY, " ");
+
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo(" ");
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherJmxSerialFilterPropertyEmptyIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherJmxSerialFilterPropertyEmptyIntegrationTest.java
new file mode 100644
index 0000000..d924f6c
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherJmxSerialFilterPropertyEmptyIntegrationTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.LocatorLauncher;
+
+public class LocatorLauncherJmxSerialFilterPropertyEmptyIntegrationTest
+    extends LocatorLauncherWithJmxManager {
+
+  @Test
+  public void startConfiguresJmxSerialFilter_whenPropertyIsEmpty_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
+
+    System.setProperty(JMX_PROPERTY, "");
+
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo(openMBeanFilterPattern);
+  }
+
+  @Test
+  public void startDoesNotConfigureJmxSerialFilter_whenPropertyIsEmpty_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
+
+    System.setProperty(JMX_PROPERTY, "");
+
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo("");
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherJmxSerialFilterPropertyExistsIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherJmxSerialFilterPropertyExistsIntegrationTest.java
new file mode 100644
index 0000000..3de7ac2
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherJmxSerialFilterPropertyExistsIntegrationTest.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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.LocatorLauncher;
+
+public class LocatorLauncherJmxSerialFilterPropertyExistsIntegrationTest
+    extends LocatorLauncherWithJmxManager {
+
+  @Test
+  public void startDoesNotConfigureJmxSerialFilter_whenPropertyExists_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
+
+    String existingJmxSerialFilter = "!*";
+    System.setProperty(JMX_PROPERTY, existingJmxSerialFilter);
+
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo(existingJmxSerialFilter);
+  }
+
+  @Test
+  public void startDoesNotConfigureJmxSerialFilter_whenPropertyExists_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
+
+    String existingJmxSerialFilter = "!*";
+    System.setProperty(JMX_PROPERTY, existingJmxSerialFilter);
+
+    locator.set(new LocatorLauncher.Builder()
+        .setMemberName(NAME)
+        .setPort(locatorPort)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo(existingJmxSerialFilter);
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherWithJmxManager.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherWithJmxManager.java
new file mode 100644
index 0000000..8bd96f5
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/LocatorLauncherWithJmxManager.java
@@ -0,0 +1,82 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPorts;
+
+import java.nio.file.Path;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.contrib.java.lang.system.RestoreSystemProperties;
+import org.junit.rules.TemporaryFolder;
+
+import org.apache.geode.distributed.LocatorLauncher;
+import org.apache.geode.distributed.internal.InternalLocator;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.management.ManagementService;
+import org.apache.geode.management.internal.SystemManagementService;
+import org.apache.geode.test.junit.rules.CloseableReference;
+
+abstract class LocatorLauncherWithJmxManager {
+
+  static final String NAME = "locator";
+  static final String JDK_PROPERTY = "jdk.serialFilter";
+  static final String JMX_PROPERTY = "jmx.remote.rmi.server.serial.filter.pattern";
+
+  static final ObjectInputFilterApi OBJECT_INPUT_FILTER_API =
+      new ReflectiveObjectInputFilterApiFactory().createObjectInputFilterApi();
+
+  Path workingDirectory;
+  int locatorPort;
+  int jmxPort;
+  Path logFile;
+  String openMBeanFilterPattern;
+
+  @Rule
+  public CloseableReference<LocatorLauncher> locator = new CloseableReference<>();
+  @Rule
+  public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Before
+  public void setUpFiles() {
+    workingDirectory = temporaryFolder.getRoot().toPath().toAbsolutePath();
+    logFile = workingDirectory.resolve(NAME + ".log").toAbsolutePath();
+  }
+
+  @Before
+  public void setUpPorts() {
+    int[] ports = getRandomAvailableTCPPorts(2);
+    jmxPort = ports[0];
+    locatorPort = ports[1];
+  }
+
+  @Before
+  public void setUpFilterPattern() {
+    openMBeanFilterPattern = new OpenMBeanFilterPattern().pattern();
+  }
+
+  SystemManagementService getSystemManagementService() {
+    InternalLocator locator = (InternalLocator) this.locator.get().getLocator();
+    InternalCache cache = locator.getCache();
+    return (SystemManagementService) ManagementService.getManagementService(cache);
+  }
+
+  boolean isJmxManagerStarted() {
+    return getSystemManagementService().isManager();
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/SerialFilterAssertions.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/SerialFilterAssertions.java
new file mode 100644
index 0000000..b6d552a
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/SerialFilterAssertions.java
@@ -0,0 +1,64 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.lang.reflect.InvocationTargetException;
+
+@SuppressWarnings({"unused", "WeakerAccess"})
+public class SerialFilterAssertions {
+
+  private static final ObjectInputFilterApi API = new ReflectiveObjectInputFilterApiFactory()
+      .createObjectInputFilterApi();
+
+  private SerialFilterAssertions() {
+    // do not instantiate
+  }
+
+  public static void assertThatSerialFilterIsNull()
+      throws InvocationTargetException, IllegalAccessException {
+    boolean exists = API.getSerialFilter() != null;
+    assertThat(exists)
+        .as("ObjectInputFilter$Config.getSerialFilter() is null")
+        .isFalse();
+  }
+
+  public static void assertThatSerialFilterIsNotNull()
+      throws InvocationTargetException, IllegalAccessException {
+    boolean exists = API.getSerialFilter() != null;
+    assertThat(exists)
+        .as("ObjectInputFilter$Config.getSerialFilter() is not null")
+        .isTrue();
+  }
+
+  public static void assertThatSerialFilterIsSameAs(Object objectInputFilter)
+      throws InvocationTargetException, IllegalAccessException {
+    Object currentFilter = API.getSerialFilter();
+    boolean sameIdentity = currentFilter == objectInputFilter;
+    assertThat(sameIdentity)
+        .as("ObjectInputFilter$Config.getSerialFilter() is same as parameter")
+        .isTrue();
+  }
+
+  public static void assertThatSerialFilterIsNotSameAs(Object objectInputFilter)
+      throws InvocationTargetException, IllegalAccessException {
+    Object currentFilter = API.getSerialFilter();
+    boolean sameIdentity = currentFilter == objectInputFilter;
+    assertThat(sameIdentity)
+        .as("ObjectInputFilter$Config.getSerialFilter() is same as parameter")
+        .isFalse();
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterBlankIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterBlankIntegrationTest.java
new file mode 100644
index 0000000..5cbb8c2
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterBlankIntegrationTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.apache.geode.internal.serialization.filter.SerialFilterAssertions.assertThatSerialFilterIsSameAs;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.ServerLauncher;
+
+public class ServerLauncherGlobalSerialFilterBlankIntegrationTest
+    extends ServerLauncherWithJmxManager {
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenFilterIsBlank()
+      throws InvocationTargetException, IllegalAccessException {
+    Object existingGlobalSerialFilter = OBJECT_INPUT_FILTER_API.createFilter(" ");
+    OBJECT_INPUT_FILTER_API.setSerialFilter(existingGlobalSerialFilter);
+
+    server.set(new ServerLauncher.Builder()
+        .setMemberName(NAME)
+        .setDisableDefaultServer(true)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThatSerialFilterIsSameAs(existingGlobalSerialFilter);
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterExistsIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterExistsIntegrationTest.java
new file mode 100644
index 0000000..b5560c6
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterExistsIntegrationTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.apache.geode.internal.serialization.filter.SerialFilterAssertions.assertThatSerialFilterIsSameAs;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.ServerLauncher;
+
+public class ServerLauncherGlobalSerialFilterExistsIntegrationTest
+    extends ServerLauncherWithJmxManager {
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenFilterExists()
+      throws InvocationTargetException, IllegalAccessException {
+    Object existingGlobalSerialFilter = OBJECT_INPUT_FILTER_API.createFilter("!*");
+    OBJECT_INPUT_FILTER_API.setSerialFilter(existingGlobalSerialFilter);
+
+    server.set(new ServerLauncher.Builder()
+        .setMemberName(NAME)
+        .setDisableDefaultServer(true)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThatSerialFilterIsSameAs(existingGlobalSerialFilter);
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterIntegrationTest.java
new file mode 100644
index 0000000..7c90152
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterIntegrationTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.ServerLauncher;
+
+public class ServerLauncherGlobalSerialFilterIntegrationTest
+    extends ServerLauncherWithJmxManager {
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter() {
+    server.set(new ServerLauncher.Builder()
+        .setMemberName(NAME)
+        .setDisableDefaultServer(true)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(System.getProperty(JDK_PROPERTY))
+        .as(JDK_PROPERTY)
+        .isNull();
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterPropertyBlankIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterPropertyBlankIntegrationTest.java
new file mode 100644
index 0000000..6fb95df
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterPropertyBlankIntegrationTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.ServerLauncher;
+
+public class ServerLauncherGlobalSerialFilterPropertyBlankIntegrationTest
+    extends ServerLauncherWithJmxManager {
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenPropertyIsBlank() {
+    System.setProperty(JDK_PROPERTY, " ");
+
+    server.set(new ServerLauncher.Builder()
+        .setMemberName(NAME)
+        .setDisableDefaultServer(true)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(System.getProperty(JDK_PROPERTY))
+        .as(JDK_PROPERTY)
+        .isEqualTo(" ");
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterPropertyEmptyIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterPropertyEmptyIntegrationTest.java
new file mode 100644
index 0000000..e34d400
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherGlobalSerialFilterPropertyEmptyIntegrationTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.ServerLauncher;
+
+public class ServerLauncherGlobalSerialFilterPropertyEmptyIntegrationTest
+    extends ServerLauncherWithJmxManager {
+
+  @Test
+  public void startDoesNotConfigureGlobalSerialFilter_whenPropertyIsEmpty() {
+    System.setProperty(JDK_PROPERTY, "");
+
+    server.set(new ServerLauncher.Builder()
+        .setMemberName(NAME)
+        .setDisableDefaultServer(true)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(System.getProperty(JDK_PROPERTY))
+        .as(JDK_PROPERTY)
+        .isEmpty();
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherJmxSerialFilterIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherJmxSerialFilterIntegrationTest.java
new file mode 100644
index 0000000..cf4c851
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherJmxSerialFilterIntegrationTest.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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.ServerLauncher;
+
+public class ServerLauncherJmxSerialFilterIntegrationTest
+    extends ServerLauncherWithJmxManager {
+
+  @Test
+  public void startConfiguresJmxSerialFilter_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
+
+    server.set(new ServerLauncher.Builder()
+        .setDisableDefaultServer(true)
+        .setMemberName(NAME)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo(openMBeanFilterPattern);
+  }
+
+  /**
+   * The system property for the JmxSerialFilter was not added until Java 9.
+   */
+  @Test
+  public void startDoesNotConfigureJmxSerialFilter_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
+
+    server.set(new ServerLauncher.Builder()
+        .setDisableDefaultServer(true)
+        .setMemberName(NAME)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isNull();
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherJmxSerialFilterPropertyBlankIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherJmxSerialFilterPropertyBlankIntegrationTest.java
new file mode 100644
index 0000000..26aa5ed
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherJmxSerialFilterPropertyBlankIntegrationTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.ServerLauncher;
+
+public class ServerLauncherJmxSerialFilterPropertyBlankIntegrationTest
+    extends ServerLauncherWithJmxManager {
+
+  @Test
+  public void startConfiguresJmxSerialFilter_whenPropertyIsBlank_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
+
+    System.setProperty(JMX_PROPERTY, " ");
+
+    server.set(new ServerLauncher.Builder()
+        .setDisableDefaultServer(true)
+        .setMemberName(NAME)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo(openMBeanFilterPattern);
+  }
+
+  @Test
+  public void startDoesNotConfigureJmxSerialFilter_whenPropertyIsBlank_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
+
+    System.setProperty(JMX_PROPERTY, " ");
+
+    server.set(new ServerLauncher.Builder()
+        .setDisableDefaultServer(true)
+        .setMemberName(NAME)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo(" ");
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherJmxSerialFilterPropertyEmptyIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherJmxSerialFilterPropertyEmptyIntegrationTest.java
new file mode 100644
index 0000000..68232a2
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherJmxSerialFilterPropertyEmptyIntegrationTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.ServerLauncher;
+
+public class ServerLauncherJmxSerialFilterPropertyEmptyIntegrationTest
+    extends ServerLauncherWithJmxManager {
+
+  @Test
+  public void startConfiguresJmxSerialFilter_whenPropertyIsEmpty_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
+
+    System.setProperty(JMX_PROPERTY, "");
+
+    server.set(new ServerLauncher.Builder()
+        .setDisableDefaultServer(true)
+        .setMemberName(NAME)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo(openMBeanFilterPattern);
+  }
+
+  @Test
+  public void startDoesNotConfigureJmxSerialFilter_whenPropertyIsEmpty_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
+
+    System.setProperty(JMX_PROPERTY, "");
+
+    server.set(new ServerLauncher.Builder()
+        .setDisableDefaultServer(true)
+        .setMemberName(NAME)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo("");
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherJmxSerialFilterPropertyExistsIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherJmxSerialFilterPropertyExistsIntegrationTest.java
new file mode 100644
index 0000000..05e33ac
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherJmxSerialFilterPropertyExistsIntegrationTest.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.internal.serialization.filter;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
+import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+
+import org.junit.Test;
+
+import org.apache.geode.distributed.ServerLauncher;
+
+public class ServerLauncherJmxSerialFilterPropertyExistsIntegrationTest
+    extends ServerLauncherWithJmxManager {
+
+  @Test
+  public void startDoesNotConfigureJmxSerialFilter_whenPropertyExists_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
+
+    String existingJmxSerialFilter = "!*";
+    System.setProperty(JMX_PROPERTY, existingJmxSerialFilter);
+
+    server.set(new ServerLauncher.Builder()
+        .setDisableDefaultServer(true)
+        .setMemberName(NAME)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo(existingJmxSerialFilter);
+  }
+
+  @Test
+  public void startDoesNotConfigureJmxSerialFilter_whenPropertyExists_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
+
+    String existingJmxSerialFilter = "!*";
+    System.setProperty(JMX_PROPERTY, existingJmxSerialFilter);
+
+    server.set(new ServerLauncher.Builder()
+        .setDisableDefaultServer(true)
+        .setMemberName(NAME)
+        .setWorkingDirectory(valueOf(workingDirectory))
+        .set(HTTP_SERVICE_PORT, "0")
+        .set(JMX_MANAGER, "true")
+        .set(JMX_MANAGER_PORT, valueOf(jmxPort))
+        .set(JMX_MANAGER_START, "true")
+        .set(LOG_FILE, valueOf(logFile))
+        .build())
+        .get()
+        .start();
+
+    assertThat(isJmxManagerStarted())
+        .isTrue();
+    assertThat(System.getProperty(JMX_PROPERTY))
+        .as(JMX_PROPERTY)
+        .isEqualTo(existingJmxSerialFilter);
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherWithJmxManager.java b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherWithJmxManager.java
new file mode 100644
index 0000000..eba157c
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ServerLauncherWithJmxManager.java
@@ -0,0 +1,77 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPort;
+
+import java.nio.file.Path;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.contrib.java.lang.system.RestoreSystemProperties;
+import org.junit.rules.TemporaryFolder;
+
+import org.apache.geode.cache.Cache;
+import org.apache.geode.distributed.ServerLauncher;
+import org.apache.geode.management.ManagementService;
+import org.apache.geode.management.internal.SystemManagementService;
+import org.apache.geode.test.junit.rules.CloseableReference;
+
+abstract class ServerLauncherWithJmxManager {
+
+  static final String NAME = "server";
+  static final String JDK_PROPERTY = "jdk.serialFilter";
+  static final String JMX_PROPERTY = "jmx.remote.rmi.server.serial.filter.pattern";
+
+  static final ObjectInputFilterApi OBJECT_INPUT_FILTER_API =
+      new ReflectiveObjectInputFilterApiFactory().createObjectInputFilterApi();
+
+  Path workingDirectory;
+  int jmxPort;
+  Path logFile;
+  String openMBeanFilterPattern;
+
+  @Rule
+  public CloseableReference<ServerLauncher> server = new CloseableReference<>();
+  @Rule
+  public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Before
+  public void setUpFiles() {
+    workingDirectory = temporaryFolder.getRoot().toPath().toAbsolutePath();
+    logFile = workingDirectory.resolve(NAME + ".log").toAbsolutePath();
+  }
+
+  @Before
+  public void setUpPorts() {
+    jmxPort = getRandomAvailableTCPPort();
+  }
+
+  @Before
+  public void setUpFilterPattern() {
+    openMBeanFilterPattern = new OpenMBeanFilterPattern().pattern();
+  }
+
+  SystemManagementService getSystemManagementService() {
+    Cache cache = server.get().getCache();
+    return (SystemManagementService) ManagementService.getManagementService(cache);
+  }
+
+  boolean isJmxManagerStarted() {
+    return getSystemManagementService().isManager();
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/management/internal/LocatorManagerConfiguresJmxSerialFilterIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/management/internal/LocatorManagerConfiguresJmxSerialFilterIntegrationTest.java
deleted file mode 100644
index 47f196f..0000000
--- a/geode-core/src/integrationTest/java/org/apache/geode/management/internal/LocatorManagerConfiguresJmxSerialFilterIntegrationTest.java
+++ /dev/null
@@ -1,204 +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.management.internal;
-
-import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
-import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
-import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
-import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
-import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
-import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
-import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
-import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPorts;
-import static org.apache.geode.management.internal.JmxRmiOpenTypesSerialFilter.PROPERTY_NAME;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assumptions.assumeThat;
-
-import java.io.File;
-
-import org.apache.commons.lang3.JavaVersion;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import org.apache.geode.distributed.LocatorLauncher;
-import org.apache.geode.test.junit.rules.CloseableReference;
-
-public class LocatorManagerConfiguresJmxSerialFilterIntegrationTest {
-
-  private static final String NAME = "locator";
-
-  private File workingDirectory;
-  private int locatorPort;
-  private int jmxPort;
-  private String expectedSerialFilter;
-
-  @Rule
-  public CloseableReference<LocatorLauncher> locator = new CloseableReference<>();
-  @Rule
-  public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
-  @Before
-  public void setUp() throws Exception {
-    workingDirectory = temporaryFolder.newFolder(NAME);
-    int[] ports = getRandomAvailableTCPPorts(2);
-    locatorPort = ports[0];
-    jmxPort = ports[1];
-    expectedSerialFilter = new JmxRmiOpenTypesSerialFilter().createSerialFilterPattern();
-  }
-
-  @After
-  public void tearDown() {
-    System.clearProperty(PROPERTY_NAME);
-  }
-
-  @Test
-  public void startingLocatorWithJmxManager_configuresSerialFilter_atLeastJava9() {
-    assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue();
-
-    locator.set(new LocatorLauncher.Builder()
-        .setMemberName(NAME)
-        .setPort(locatorPort)
-        .setWorkingDirectory(workingDirectory.getAbsolutePath())
-        .set(HTTP_SERVICE_PORT, "0")
-        .set(JMX_MANAGER, "true")
-        .set(JMX_MANAGER_PORT, String.valueOf(jmxPort))
-        .set(JMX_MANAGER_START, "true")
-        .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath())
-        .build())
-        .get()
-        .start();
-
-    String serialFilter = System.getProperty(PROPERTY_NAME);
-    assertThat(serialFilter).isEqualTo(expectedSerialFilter);
-  }
-
-  @Test
-  public void startingLocatorWithJmxManager_changesEmptySerialFilter_atLeastJava9() {
-    assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue();
-
-    System.setProperty(PROPERTY_NAME, "");
-
-    locator.set(new LocatorLauncher.Builder()
-        .setMemberName(NAME)
-        .setPort(locatorPort)
-        .setWorkingDirectory(workingDirectory.getAbsolutePath())
-        .set(HTTP_SERVICE_PORT, "0")
-        .set(JMX_MANAGER, "true")
-        .set(JMX_MANAGER_PORT, String.valueOf(jmxPort))
-        .set(JMX_MANAGER_START, "true")
-        .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath())
-        .build())
-        .get()
-        .start();
-
-    String serialFilter = System.getProperty(PROPERTY_NAME);
-    assertThat(serialFilter).isEqualTo(expectedSerialFilter);
-  }
-
-  @Test
-  public void startingLocatorWithJmxManager_skipsNonEmptySerialFilter_atLeastJava9() {
-    assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue();
-
-    String existingSerialFilter = "!*";
-    System.setProperty(PROPERTY_NAME, existingSerialFilter);
-
-    locator.set(new LocatorLauncher.Builder()
-        .setMemberName(NAME)
-        .setPort(locatorPort)
-        .setWorkingDirectory(workingDirectory.getAbsolutePath())
-        .set(HTTP_SERVICE_PORT, "0")
-        .set(JMX_MANAGER, "true")
-        .set(JMX_MANAGER_PORT, String.valueOf(jmxPort))
-        .set(JMX_MANAGER_START, "true")
-        .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath())
-        .build())
-        .get()
-        .start();
-
-    String serialFilter = System.getProperty(PROPERTY_NAME);
-    assertThat(serialFilter).isEqualTo(existingSerialFilter);
-  }
-
-  @Test
-  public void startingLocatorWithJmxManager_skipsSerialFilter_atMostJava8() {
-    assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue();
-
-    locator.set(new LocatorLauncher.Builder()
-        .setMemberName(NAME)
-        .setPort(locatorPort)
-        .setWorkingDirectory(workingDirectory.getAbsolutePath())
-        .set(HTTP_SERVICE_PORT, "0")
-        .set(JMX_MANAGER, "true")
-        .set(JMX_MANAGER_PORT, String.valueOf(jmxPort))
-        .set(JMX_MANAGER_START, "true")
-        .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath())
-        .build())
-        .get()
-        .start();
-
-    String serialFilter = System.getProperty(PROPERTY_NAME);
-    assertThat(serialFilter).isNull();
-  }
-
-  @Test
-  public void startingLocatorWithJmxManager_skipsEmptySerialFilter_atMostJava8() {
-    assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue();
-
-    System.setProperty(PROPERTY_NAME, "");
-
-    locator.set(new LocatorLauncher.Builder()
-        .setMemberName(NAME)
-        .setPort(locatorPort)
-        .setWorkingDirectory(workingDirectory.getAbsolutePath())
-        .set(HTTP_SERVICE_PORT, "0")
-        .set(JMX_MANAGER, "true")
-        .set(JMX_MANAGER_PORT, String.valueOf(jmxPort))
-        .set(JMX_MANAGER_START, "true")
-        .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath())
-        .build())
-        .get()
-        .start();
-
-    String serialFilter = System.getProperty(PROPERTY_NAME);
-    assertThat(serialFilter).isEqualTo("");
-  }
-
-  @Test
-  public void startingLocatorWithJmxManager_skipsNonEmptySerialFilter_atMostJava8() {
-    assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue();
-
-    String existingSerialFilter = "!*";
-    System.setProperty(PROPERTY_NAME, existingSerialFilter);
-
-    locator.set(new LocatorLauncher.Builder()
-        .setMemberName(NAME)
-        .setPort(locatorPort)
-        .setWorkingDirectory(workingDirectory.getAbsolutePath())
-        .set(HTTP_SERVICE_PORT, "0")
-        .set(JMX_MANAGER, "true")
-        .set(JMX_MANAGER_PORT, String.valueOf(jmxPort))
-        .set(JMX_MANAGER_START, "true")
-        .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath())
-        .build())
-        .get()
-        .start();
-
-    String serialFilter = System.getProperty(PROPERTY_NAME);
-    assertThat(serialFilter).isEqualTo(existingSerialFilter);
-  }
-}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/management/internal/ManagementAgentIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/management/internal/ManagementAgentIntegrationTest.java
index 15815a5..257454d9 100644
--- a/geode-core/src/integrationTest/java/org/apache/geode/management/internal/ManagementAgentIntegrationTest.java
+++ b/geode-core/src/integrationTest/java/org/apache/geode/management/internal/ManagementAgentIntegrationTest.java
@@ -35,10 +35,10 @@ import org.junit.rules.TemporaryFolder;
 import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.security.SecurityService;
+import org.apache.geode.internal.serialization.filter.FilterConfiguration;
 
 public class ManagementAgentIntegrationTest {
 
-  // private ManagementAgent managementAgent = spy(ManagementAgent.class);
   @Rule
   public TemporaryFolder temporaryFolder = new TemporaryFolder();
 
@@ -46,14 +46,14 @@ public class ManagementAgentIntegrationTest {
   public void testSetMBeanServer() throws IOException {
     DistributionConfig distributionConfig = mock(DistributionConfig.class);
     InternalCache internalCache = mock(InternalCache.class);
-    JmxRmiSerialFilter serialFilter = mock(JmxRmiSerialFilter.class);
+    FilterConfiguration filterConfiguration = mock(FilterConfiguration.class);
     SecurityService securityService = mock(SecurityService.class);
     when(internalCache.getSecurityService()).thenReturn(securityService);
     when(securityService.isIntegratedSecurity()).thenReturn(false);
     File tempFile = temporaryFolder.newFile("testFile");
     when(distributionConfig.getJmxManagerAccessFile()).thenReturn(tempFile.getCanonicalPath());
     ManagementAgent managementAgent =
-        new ManagementAgent(distributionConfig, internalCache, serialFilter);
+        new ManagementAgent(distributionConfig, internalCache, filterConfiguration);
     MBeanServer mBeanServer = mock(MBeanServer.class);
     JMXConnectorServer jmxConnectorServerWithMBeanServer = new JMXConnectorServer(mBeanServer) {
       @Override
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/management/internal/ServerManagerConfiguresJmxSerialFilterIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/management/internal/ServerManagerConfiguresJmxSerialFilterIntegrationTest.java
deleted file mode 100644
index f77438e..0000000
--- a/geode-core/src/integrationTest/java/org/apache/geode/management/internal/ServerManagerConfiguresJmxSerialFilterIntegrationTest.java
+++ /dev/null
@@ -1,201 +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.management.internal;
-
-import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
-import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
-import static org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_PORT;
-import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
-import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
-import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
-import static org.apache.geode.distributed.ConfigurationProperties.LOG_FILE;
-import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPort;
-import static org.apache.geode.management.internal.JmxRmiOpenTypesSerialFilter.PROPERTY_NAME;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assumptions.assumeThat;
-
-import java.io.File;
-
-import org.apache.commons.lang3.JavaVersion;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import org.apache.geode.distributed.ServerLauncher;
-import org.apache.geode.test.junit.rules.CloseableReference;
-
-public class ServerManagerConfiguresJmxSerialFilterIntegrationTest {
-
-  private static final String NAME = "server";
-
-  private File workingDirectory;
-  private int jmxPort;
-  private String expectedSerialFilter;
-
-  @Rule
-  public CloseableReference<ServerLauncher> server = new CloseableReference<>();
-  @Rule
-  public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
-  @Before
-  public void setUp() throws Exception {
-    workingDirectory = temporaryFolder.newFolder(NAME);
-    jmxPort = getRandomAvailableTCPPort();
-    expectedSerialFilter = new JmxRmiOpenTypesSerialFilter().createSerialFilterPattern();
-  }
-
-  @After
-  public void tearDown() {
-    System.clearProperty(PROPERTY_NAME);
-  }
-
-  @Test
-  public void startingServerWithJmxManager_configuresSerialFilter_atLeastJava9() {
-    assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue();
-
-    server.set(new ServerLauncher.Builder()
-        .setMemberName(NAME)
-        .setDisableDefaultServer(true)
-        .setWorkingDirectory(workingDirectory.getAbsolutePath())
-        .set(HTTP_SERVICE_PORT, "0")
-        .set(JMX_MANAGER, "true")
-        .set(JMX_MANAGER_PORT, String.valueOf(jmxPort))
-        .set(JMX_MANAGER_START, "true")
-        .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath())
-        .build())
-        .get()
-        .start();
-
-    String serialFilter = System.getProperty(PROPERTY_NAME);
-    assertThat(serialFilter).isEqualTo(expectedSerialFilter);
-  }
-
-  @Test
-  public void startingServerWithJmxManager_changesEmptySerialFilter_atLeastJava9() {
-    assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue();
-
-    System.setProperty(PROPERTY_NAME, "");
-
-    server.set(new ServerLauncher.Builder()
-        .setMemberName(NAME)
-        .setDisableDefaultServer(true)
-        .setWorkingDirectory(workingDirectory.getAbsolutePath())
-        .set(HTTP_SERVICE_PORT, "0")
-        .set(JMX_MANAGER, "true")
-        .set(JMX_MANAGER_PORT, String.valueOf(jmxPort))
-        .set(JMX_MANAGER_START, "true")
-        .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath())
-        .build())
-        .get()
-        .start();
-
-    String serialFilter = System.getProperty(PROPERTY_NAME);
-    assertThat(serialFilter).isEqualTo(expectedSerialFilter);
-  }
-
-  @Test
-  public void startingServerWithJmxManager_skipsNonEmptySerialFilter_atLeastJava9() {
-    assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue();
-
-    String existingSerialFilter = "!*";
-    System.setProperty(PROPERTY_NAME, existingSerialFilter);
-
-    server.set(new ServerLauncher.Builder()
-        .setMemberName(NAME)
-        .setDisableDefaultServer(true)
-        .setWorkingDirectory(workingDirectory.getAbsolutePath())
-        .set(HTTP_SERVICE_PORT, "0")
-        .set(JMX_MANAGER, "true")
-        .set(JMX_MANAGER_PORT, String.valueOf(jmxPort))
-        .set(JMX_MANAGER_START, "true")
-        .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath())
-        .build())
-        .get()
-        .start();
-
-    String serialFilter = System.getProperty(PROPERTY_NAME);
-    assertThat(serialFilter).isEqualTo(existingSerialFilter);
-  }
-
-  @Test
-  public void startingServerWithJmxManager_skipsSerialFilter_atMostJava8() {
-    assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue();
-
-    server.set(new ServerLauncher.Builder()
-        .setMemberName(NAME)
-        .setDisableDefaultServer(true)
-        .setWorkingDirectory(workingDirectory.getAbsolutePath())
-        .set(HTTP_SERVICE_PORT, "0")
-        .set(JMX_MANAGER, "true")
-        .set(JMX_MANAGER_PORT, String.valueOf(jmxPort))
-        .set(JMX_MANAGER_START, "true")
-        .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath())
-        .build())
-        .get()
-        .start();
-
-    String serialFilter = System.getProperty(PROPERTY_NAME);
-    assertThat(serialFilter).isNull();
-  }
-
-  @Test
-  public void startingServerWithJmxManager_skipsEmptySerialFilter_atMostJava8() {
-    assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue();
-
-    System.setProperty(PROPERTY_NAME, "");
-
-    server.set(new ServerLauncher.Builder()
-        .setMemberName(NAME)
-        .setDisableDefaultServer(true)
-        .setWorkingDirectory(workingDirectory.getAbsolutePath())
-        .set(HTTP_SERVICE_PORT, "0")
-        .set(JMX_MANAGER, "true")
-        .set(JMX_MANAGER_PORT, String.valueOf(jmxPort))
-        .set(JMX_MANAGER_START, "true")
-        .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath())
-        .build())
-        .get()
-        .start();
-
-    String serialFilter = System.getProperty(PROPERTY_NAME);
-    assertThat(serialFilter).isEqualTo("");
-  }
-
-  @Test
-  public void startingServerWithJmxManager_skipsNonEmptySerialFilter_atMostJava8() {
-    assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue();
-
-    String existingSerialFilter = "!*";
-    System.setProperty(PROPERTY_NAME, existingSerialFilter);
-
-    server.set(new ServerLauncher.Builder()
-        .setMemberName(NAME)
-        .setDisableDefaultServer(true)
-        .setWorkingDirectory(workingDirectory.getAbsolutePath())
-        .set(HTTP_SERVICE_PORT, "0")
-        .set(JMX_MANAGER, "true")
-        .set(JMX_MANAGER_PORT, String.valueOf(jmxPort))
-        .set(JMX_MANAGER_START, "true")
-        .set(LOG_FILE, new File(workingDirectory, NAME + ".log").getAbsolutePath())
-        .build())
-        .get()
-        .start();
-
-    String serialFilter = System.getProperty(PROPERTY_NAME);
-    assertThat(serialFilter).isEqualTo(existingSerialFilter);
-  }
-}
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/LocatorLauncher.java b/geode-core/src/main/java/org/apache/geode/distributed/LocatorLauncher.java
index 0cd015e..fa9f7cc 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/LocatorLauncher.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/LocatorLauncher.java
@@ -64,6 +64,7 @@ import org.apache.geode.distributed.internal.tcpserver.HostAndPort;
 import org.apache.geode.distributed.internal.tcpserver.TcpClient;
 import org.apache.geode.distributed.internal.tcpserver.TcpSocketCreator;
 import org.apache.geode.distributed.internal.tcpserver.TcpSocketFactory;
+import org.apache.geode.internal.DistributedSerializableObjectConfig;
 import org.apache.geode.internal.DistributionLocator;
 import org.apache.geode.internal.GemFireVersion;
 import org.apache.geode.internal.InternalDataSerializer;
@@ -87,6 +88,7 @@ import org.apache.geode.internal.process.ProcessType;
 import org.apache.geode.internal.process.ProcessUtils;
 import org.apache.geode.internal.process.UnableToControlProcessException;
 import org.apache.geode.internal.security.SecurableCommunicationChannel;
+import org.apache.geode.internal.serialization.filter.SystemPropertyGlobalSerialFilterConfigurationFactory;
 import org.apache.geode.lang.AttachAPINotFoundException;
 import org.apache.geode.logging.internal.log4j.api.LogService;
 import org.apache.geode.management.internal.util.HostUtils;
@@ -702,6 +704,11 @@ public class LocatorLauncher extends AbstractLauncher<String> {
     if (isStartable()) {
       INSTANCE.compareAndSet(null, this);
 
+      boolean serializationFilterConfigured =
+          new SystemPropertyGlobalSerialFilterConfigurationFactory()
+              .create(new DistributedSerializableObjectConfig(getDistributedSystemProperties()))
+              .configure();
+
       try {
         this.process =
             new FileControllableProcess(this.controlHandler, new File(getWorkingDirectory()),
@@ -716,6 +723,11 @@ public class LocatorLauncher extends AbstractLauncher<String> {
           this.locator = InternalLocator.startLocator(getPort(), getLogFile(), null, null,
               getBindAddress(), true, getDistributedSystemProperties(), getHostnameForClients(),
               Paths.get(workingDirectory));
+
+          if (serializationFilterConfigured) {
+            log.info("Global serial filter is now configured.");
+          }
+
         } finally {
           ProcessLauncherContext.remove();
         }
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/ServerLauncher.java b/geode-core/src/main/java/org/apache/geode/distributed/ServerLauncher.java
index f10b053..d1b6c97 100755
--- a/geode-core/src/main/java/org/apache/geode/distributed/ServerLauncher.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/ServerLauncher.java
@@ -71,6 +71,7 @@ import org.apache.geode.cache.partition.PartitionRegionHelper;
 import org.apache.geode.cache.server.CacheServer;
 import org.apache.geode.distributed.internal.DefaultServerLauncherCacheProvider;
 import org.apache.geode.distributed.internal.InternalDistributedSystem;
+import org.apache.geode.internal.DistributedSerializableObjectConfig;
 import org.apache.geode.internal.GemFireVersion;
 import org.apache.geode.internal.cache.AbstractCacheServer;
 import org.apache.geode.internal.cache.CacheConfig;
@@ -94,6 +95,7 @@ import org.apache.geode.internal.process.ProcessControllerParameters;
 import org.apache.geode.internal.process.ProcessLauncherContext;
 import org.apache.geode.internal.process.ProcessType;
 import org.apache.geode.internal.process.UnableToControlProcessException;
+import org.apache.geode.internal.serialization.filter.SystemPropertyGlobalSerialFilterConfigurationFactory;
 import org.apache.geode.lang.AttachAPINotFoundException;
 import org.apache.geode.logging.internal.executors.LoggingThread;
 import org.apache.geode.logging.internal.log4j.api.LogService;
@@ -787,6 +789,11 @@ public class ServerLauncher extends AbstractLauncher<String> {
     if (isStartable()) {
       INSTANCE.compareAndSet(null, this);
 
+      boolean serializationFilterConfigured =
+          new SystemPropertyGlobalSerialFilterConfigurationFactory()
+              .create(new DistributedSerializableObjectConfig(getDistributedSystemProperties()))
+              .configure();
+
       try {
         process = getControllableProcess();
 
@@ -806,6 +813,10 @@ public class ServerLauncher extends AbstractLauncher<String> {
           final Properties gemfireProperties = getDistributedSystemProperties(getProperties());
           cache = createCache(gemfireProperties);
 
+          if (serializationFilterConfigured) {
+            log.info("Global serial filter is now configured.");
+          }
+
           // Set the resource manager options
           if (criticalHeapPercentage != null) {
             cache.getResourceManager().setCriticalHeapPercentage(getCriticalHeapPercentage());
diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfig.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfig.java
index 6f6f523..027a536 100644
--- a/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfig.java
+++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/DistributionConfig.java
@@ -211,6 +211,7 @@ import org.apache.geode.internal.Config;
 import org.apache.geode.internal.ConfigSource;
 import org.apache.geode.internal.logging.LogWriterImpl;
 import org.apache.geode.internal.security.SecurableCommunicationChannel;
+import org.apache.geode.internal.serialization.filter.SerializableObjectConfig;
 import org.apache.geode.internal.statistics.StatisticsConfig;
 import org.apache.geode.internal.tcp.Connection;
 import org.apache.geode.logging.internal.spi.LogConfig;
@@ -227,7 +228,8 @@ import org.apache.geode.util.internal.GeodeGlossary;
  * @see Config
  * @since GemFire 2.1
  */
-public interface DistributionConfig extends Config, LogConfig, StatisticsConfig {
+public interface DistributionConfig
+    extends Config, LogConfig, StatisticsConfig, SerializableObjectConfig {
 
   /**
    * The static String definition of the prefix used to defined ssl-* properties
diff --git a/geode-core/src/main/java/org/apache/geode/internal/DistributedSerializableObjectConfig.java b/geode-core/src/main/java/org/apache/geode/internal/DistributedSerializableObjectConfig.java
new file mode 100644
index 0000000..1a9aec0
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/DistributedSerializableObjectConfig.java
@@ -0,0 +1,51 @@
+/*
+ * 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.internal;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.geode.distributed.ConfigurationProperties.SERIALIZABLE_OBJECT_FILTER;
+import static org.apache.geode.distributed.ConfigurationProperties.VALIDATE_SERIALIZABLE_OBJECTS;
+
+import java.util.Properties;
+
+import org.apache.geode.internal.serialization.filter.SerializableObjectConfig;
+
+/**
+ * Simple implementation of {@code SerializableObjectConfig} for use when an instance of
+ * {@code DistributionConfig} is not available.
+ */
+public class DistributedSerializableObjectConfig implements SerializableObjectConfig {
+
+  private final Properties config;
+
+  public DistributedSerializableObjectConfig(Properties config) {
+    this.config = requireNonNull(config);
+  }
+
+  @Override
+  public boolean getValidateSerializableObjects() {
+    return "true".equalsIgnoreCase(config.getProperty(VALIDATE_SERIALIZABLE_OBJECTS));
+  }
+
+  @Override
+  public void setValidateSerializableObjects(boolean value) {
+    config.setProperty(VALIDATE_SERIALIZABLE_OBJECTS, Boolean.valueOf(value).toString());
+  }
+
+  @Override
+  public String getSerializableObjectFilter() {
+    return config.getProperty(SERIALIZABLE_OBJECT_FILTER);
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/internal/InternalDataSerializer.java b/geode-core/src/main/java/org/apache/geode/internal/InternalDataSerializer.java
index 9c5018a..064044f 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/InternalDataSerializer.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/InternalDataSerializer.java
@@ -14,6 +14,7 @@
  */
 package org.apache.geode.internal;
 
+import static org.apache.geode.internal.serialization.filter.SanctionedSerializables.loadSanctionedClassNames;
 import static org.apache.geode.internal.serialization.filter.SanctionedSerializables.loadSanctionedSerializablesServices;
 
 import java.io.DataInput;
@@ -73,7 +74,6 @@ import org.apache.geode.CancelException;
 import org.apache.geode.CanonicalInstantiator;
 import org.apache.geode.DataSerializable;
 import org.apache.geode.DataSerializer;
-import org.apache.geode.GemFireConfigException;
 import org.apache.geode.GemFireIOException;
 import org.apache.geode.GemFireRethrowable;
 import org.apache.geode.Instantiator;
@@ -88,7 +88,6 @@ import org.apache.geode.cache.CacheClosedException;
 import org.apache.geode.cache.execute.Function;
 import org.apache.geode.distributed.internal.ClusterDistributionManager;
 import org.apache.geode.distributed.internal.DMStats;
-import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.distributed.internal.InternalDistributedSystem;
 import org.apache.geode.distributed.internal.LonerDistributionManager;
 import org.apache.geode.distributed.internal.SerialDistributionMessage;
@@ -103,7 +102,6 @@ import org.apache.geode.internal.cache.tier.sockets.ClientDataSerializerMessage;
 import org.apache.geode.internal.cache.tier.sockets.ClientProxyMembershipID;
 import org.apache.geode.internal.cache.tier.sockets.OldClientSupportService;
 import org.apache.geode.internal.cache.tier.sockets.Part;
-import org.apache.geode.internal.lang.utils.ClassUtils;
 import org.apache.geode.internal.logging.log4j.LogMarker;
 import org.apache.geode.internal.serialization.BasicSerializable;
 import org.apache.geode.internal.serialization.DSCODE;
@@ -119,7 +117,12 @@ import org.apache.geode.internal.serialization.SerializationContext;
 import org.apache.geode.internal.serialization.SerializationVersions;
 import org.apache.geode.internal.serialization.StaticSerialization;
 import org.apache.geode.internal.serialization.VersionedDataStream;
+import org.apache.geode.internal.serialization.filter.NullObjectInputFilter;
+import org.apache.geode.internal.serialization.filter.ObjectInputFilter;
+import org.apache.geode.internal.serialization.filter.ObjectInputFilterFactory;
+import org.apache.geode.internal.serialization.filter.ReflectiveFacadeObjectInputFilterFactory;
 import org.apache.geode.internal.serialization.filter.SanctionedSerializablesService;
+import org.apache.geode.internal.serialization.filter.SerializableObjectConfig;
 import org.apache.geode.internal.util.concurrent.CopyOnWriteHashMap;
 import org.apache.geode.logging.internal.log4j.api.LogService;
 import org.apache.geode.pdx.NonPortableClassException;
@@ -283,12 +286,12 @@ public abstract class InternalDataSerializer extends DataSerializer {
       "org.apache.geode.cache.query.internal.cq.ServerCQImpl";
 
   @Immutable
-  private static final InputStreamFilter defaultSerializationFilter = new EmptyInputStreamFilter();
+  private static final ObjectInputFilter defaultSerializationFilter = new NullObjectInputFilter();
   /**
    * A deserialization filter for ObjectInputStreams
    */
   @MakeNotStatic
-  private static InputStreamFilter serializationFilter = defaultSerializationFilter;
+  private static ObjectInputFilter serializationFilter = defaultSerializationFilter;
   /**
    * support for old GemFire clients and WAN sites - needed to enable moving from GemFire to Geode
    */
@@ -397,35 +400,29 @@ public abstract class InternalDataSerializer extends DataSerializer {
    * Initializes the optional serialization "accept list" if the user has requested it in the
    * DistributionConfig
    *
-   * @param distributionConfig the DistributedSystem configuration
+   * @param config the DistributedSystem configuration
    */
-  public static void initializeSerializationFilter(DistributionConfig distributionConfig) {
-    initializeSerializationFilter(distributionConfig, loadSanctionedSerializablesServices());
+  public static void initializeSerializationFilter(SerializableObjectConfig config) {
+    initializeSerializationFilter(config, loadSanctionedSerializablesServices());
   }
 
   /**
    * Initializes the optional serialization "accept list" if the user has requested it in the
    * DistributionConfig
    *
-   * @param distributionConfig the DistributedSystem configuration
+   * @param config the DistributedSystem configuration
    * @param services SanctionedSerializablesService that might have classes to acceptlist
    */
   @VisibleForTesting
-  public static void initializeSerializationFilter(DistributionConfig distributionConfig,
+  public static void initializeSerializationFilter(SerializableObjectConfig config,
       Collection<SanctionedSerializablesService> services) {
     logger.info("initializing InternalDataSerializer with {} services", services.size());
-    if (distributionConfig.getValidateSerializableObjects()) {
-      if (!ClassUtils.isClassAvailable("sun.misc.ObjectInputFilter")
-          && !ClassUtils.isClassAvailable("java.io.ObjectInputFilter")) {
-        throw new GemFireConfigException(
-            "A serialization filter has been specified but this version of Java does not support serialization filters - ObjectInputFilter is not available");
-      }
-      serializationFilter =
-          new ObjectInputStreamFilterWrapper(SANCTIONED_SERIALIZABLES_DEPENDENCIES_PATTERN
-              + distributionConfig.getSerializableObjectFilter() + ";!*", services);
-    } else {
-      clearSerializationFilter();
-    }
+
+    ObjectInputFilterFactory objectInputFilterFactory =
+        new ReflectiveFacadeObjectInputFilterFactory();
+
+    serializationFilter =
+        objectInputFilterFactory.create(config, loadSanctionedClassNames(services));
   }
 
   @VisibleForTesting
diff --git a/geode-core/src/main/java/org/apache/geode/internal/ObjectInputStreamFilterWrapper.java b/geode-core/src/main/java/org/apache/geode/internal/ObjectInputStreamFilterWrapper.java
deleted file mode 100644
index 25d5e35..0000000
--- a/geode-core/src/main/java/org/apache/geode/internal/ObjectInputStreamFilterWrapper.java
+++ /dev/null
@@ -1,233 +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.internal;
-
-import java.io.IOException;
-import java.io.InvalidClassException;
-import java.io.ObjectInputStream;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.apache.logging.log4j.Logger;
-
-import org.apache.geode.GemFireConfigException;
-import org.apache.geode.InternalGemFireError;
-import org.apache.geode.InternalGemFireException;
-import org.apache.geode.internal.serialization.filter.SanctionedSerializablesService;
-import org.apache.geode.logging.internal.log4j.api.LogService;
-
-
-/**
- * ObjectInputStreamFilterWrapper isolates InternalDataSerializer from the JDK's
- * ObjectInputFilter class, which is absent in builds of java8 prior to 121 and
- * is sun.misc.ObjectInputFilter in later builds but is java.io.ObjectInputFilter
- * in java9.
- * <p>
- * This class uses reflection and dynamic proxies to find the class and create a
- * serialization filter. Once java8 is retired and no longer supported by Geode the
- * use of reflection should be removed ObjectInputFilter can be directly used by
- * InternalDataSerializer.
- */
-public class ObjectInputStreamFilterWrapper implements InputStreamFilter {
-  private static final Logger logger = LogService.getLogger();
-
-  private boolean usingJava8;
-  private Object serializationFilter;
-
-  private Class configClass; // ObjectInputFilter$Config
-  private Method setObjectInputFilterMethod; // method on ObjectInputFilter$Config or
-                                             // ObjectInputStream
-  private Method createFilterMethod; // method on ObjectInputFilter$Config
-
-  private Class filterClass; // ObjectInputFilter
-  private Object ALLOWED; // field on ObjectInputFilter
-  private Object REJECTED; // field on ObjectInputFilter
-  private Method checkInputMethod; // method on ObjectInputFilter
-
-  private Class filterInfoClass; // ObjectInputFilter$FilterInfo
-  private Method serialClassMethod; // method on ObjectInputFilter$FilterInfo
-
-  public ObjectInputStreamFilterWrapper(String serializationFilterSpec,
-      Collection<SanctionedSerializablesService> services) {
-
-    Set<String> sanctionedClasses = new HashSet<>(500);
-    for (SanctionedSerializablesService service : services) {
-      try {
-        Collection<String> classNames = service.getSerializationAcceptlist();
-        logger.info("loaded {} sanctioned serializables from {}", classNames.size(),
-            service.getClass().getSimpleName());
-        sanctionedClasses.addAll(classNames);
-      } catch (IOException e) {
-        throw new InternalGemFireException("error initializing serialization filter for " + service,
-            e);
-      }
-    }
-
-    logger.info("setting a serialization filter containing {}", serializationFilterSpec);
-
-    // try java8 - this will not throw an exception if ObjectInputFilter can't be found
-    if (createJava8Filter(serializationFilterSpec, sanctionedClasses)) {
-      return;
-    }
-
-    // try java9 - this throws an exception if it fails to create the filter
-    createJava9Filter(serializationFilterSpec, sanctionedClasses);
-  }
-
-  @Override
-  public void setFilterOn(ObjectInputStream objectInputStream) {
-    try {
-      if (usingJava8) {
-        setObjectInputFilterMethod.invoke(configClass, objectInputStream, serializationFilter);
-      } else {
-        setObjectInputFilterMethod.invoke(objectInputStream, serializationFilter);
-      }
-    } catch (IllegalAccessException | InvocationTargetException e) {
-      throw new InternalGemFireError("Unable to filter serialization", e);
-    }
-  }
-
-  /**
-   * java8 has sun.misc.ObjectInputFilter and uses ObjectInputFilter$Config.setObjectInputFilter()
-   */
-  private boolean createJava8Filter(String serializationFilterSpec,
-      Collection<String> sanctionedClasses) {
-    ClassPathLoader classPathLoader = ClassPathLoader.getLatest();
-    try {
-
-      filterInfoClass = classPathLoader.forName("sun.misc.ObjectInputFilter$FilterInfo");
-      serialClassMethod = filterInfoClass.getDeclaredMethod("serialClass");
-
-      filterClass = classPathLoader.forName("sun.misc.ObjectInputFilter");
-      checkInputMethod = filterClass.getDeclaredMethod("checkInput", filterInfoClass);
-
-      Class statusClass = classPathLoader.forName("sun.misc.ObjectInputFilter$Status");
-      ALLOWED = statusClass.getEnumConstants()[1];
-      REJECTED = statusClass.getEnumConstants()[2];
-      if (!ALLOWED.toString().equals("ALLOWED") || !REJECTED.toString().equals("REJECTED")) {
-        throw new GemFireConfigException(
-            "ObjectInputFilter$Status enumeration in this JDK is not as expected");
-      }
-
-      configClass = classPathLoader.forName("sun.misc.ObjectInputFilter$Config");
-      setObjectInputFilterMethod = configClass.getDeclaredMethod("setObjectInputFilter",
-          ObjectInputStream.class, filterClass);
-      createFilterMethod = configClass.getDeclaredMethod("createFilter", String.class);
-
-      serializationFilter = createSerializationFilter(serializationFilterSpec, sanctionedClasses);
-      usingJava8 = true;
-
-    } catch (ClassNotFoundException | InvocationTargetException | IllegalAccessException
-        | NoSuchMethodException e) {
-      if (filterInfoClass != null) {
-        throw new GemFireConfigException(
-            "A serialization filter has been specified but Geode was unable to configure a filter",
-            e);
-      }
-      return false;
-    }
-
-    return true;
-  }
-
-  /** java9 has java.io.ObjectInputFilter and uses ObjectInputStream.setObjectInputFilter() */
-  private void createJava9Filter(String serializationFilterSpec,
-      Collection<String> sanctionedClasses) {
-    try {
-      ClassPathLoader classPathLoader = ClassPathLoader.getLatest();
-
-      filterInfoClass = classPathLoader.forName("java.io.ObjectInputFilter$FilterInfo");
-      serialClassMethod = filterInfoClass.getDeclaredMethod("serialClass");
-
-
-      filterClass = classPathLoader.forName("java.io.ObjectInputFilter");
-      checkInputMethod = filterClass.getDeclaredMethod("checkInput", filterInfoClass);
-
-      Class statusClass = classPathLoader.forName("java.io.ObjectInputFilter$Status");
-      ALLOWED = statusClass.getEnumConstants()[1];
-      REJECTED = statusClass.getEnumConstants()[2];
-      if (!ALLOWED.toString().equals("ALLOWED") || !REJECTED.toString().equals("REJECTED")) {
-        throw new GemFireConfigException(
-            "ObjectInputFilter$Status enumeration in this JDK is not as expected");
-      }
-
-      configClass = classPathLoader.forName("java.io.ObjectInputFilter$Config");
-      setObjectInputFilterMethod =
-          ObjectInputStream.class.getDeclaredMethod("setObjectInputFilter", filterClass);
-      createFilterMethod = configClass.getDeclaredMethod("createFilter", String.class);
-
-      serializationFilter = createSerializationFilter(serializationFilterSpec, sanctionedClasses);
-
-    } catch (ClassNotFoundException | InvocationTargetException | IllegalAccessException
-        | NoSuchMethodException e) {
-      throw new GemFireConfigException(
-          "A serialization filter has been specified but Geode was unable to configure a filter",
-          e);
-    }
-  }
-
-
-  private Object createSerializationFilter(String serializationFilterSpec,
-      Collection<String> sanctionedClasses)
-      throws InvocationTargetException, IllegalAccessException {
-
-    /*
-     * create a user filter with the serialization acceptlist/denylist. This will be wrapped
-     * by a filter that accept-lists sanctioned classes
-     */
-    Object userFilter = createFilterMethod.invoke(null, serializationFilterSpec);
-
-    InvocationHandler handler = (proxy, method, args) -> {
-      switch (method.getName()) {
-        case "checkInput":
-          Object filterInfo = args[0];
-          Class<?> serialClass = (Class<?>) serialClassMethod.invoke(filterInfo);
-          if (serialClass == null) { // no class to check, so nothing to accept-list
-            return checkInputMethod.invoke(userFilter, filterInfo);
-          }
-          String className = serialClass.getName();
-          if (serialClass.isArray()) {
-            className = serialClass.getComponentType().getName();
-          }
-          if (sanctionedClasses.contains(className)) {
-            return ALLOWED;
-          }
-          Object status = checkInputMethod.invoke(userFilter, filterInfo);
-          if (status == REJECTED) {
-            logger.fatal("Serialization filter is rejecting class {}", className,
-                new InvalidClassException(className));
-          }
-          return status;
-        case "toString":
-          return userFilter.toString();
-        default:
-          throw new UnsupportedOperationException(
-              "ObjectInputFilter." + method.getName() + " is not implemented");
-      }
-    };
-
-    ClassPathLoader classPathLoader = ClassPathLoader.getLatest();
-    return Proxy.newProxyInstance(classPathLoader.asClassLoader(), new Class[] {filterClass},
-        handler);
-
-  }
-
-
-}
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiOpenTypesSerialFilter.java b/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiOpenTypesSerialFilter.java
deleted file mode 100644
index 803bc03..0000000
--- a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiOpenTypesSerialFilter.java
+++ /dev/null
@@ -1,148 +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.management.internal;
-
-import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.rmi.MarshalledObject;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-import javax.management.ObjectName;
-import javax.management.openmbean.ArrayType;
-import javax.management.openmbean.CompositeData;
-import javax.management.openmbean.CompositeType;
-import javax.management.openmbean.OpenType;
-import javax.management.openmbean.SimpleType;
-import javax.management.openmbean.TabularData;
-import javax.management.openmbean.TabularType;
-
-import org.apache.commons.lang3.JavaVersion;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.logging.log4j.Logger;
-
-import org.apache.geode.annotations.VisibleForTesting;
-import org.apache.geode.logging.internal.log4j.api.LogService;
-
-/**
- * Configure the “jmx.remote.rmi.server.serial.filter.pattern” system property if Java version is
- * greater than Java 8. The serial pattern will be configured to accept only standard JMX
- * open-types. If the system property already has a non-null value, then leave it as is.
- */
-public class JmxRmiOpenTypesSerialFilter implements JmxRmiSerialFilter {
-
-  private static final Logger logger = LogService.getLogger();
-
-  @VisibleForTesting
-  public static final String PROPERTY_NAME = "jmx.remote.rmi.server.serial.filter.pattern";
-
-  private final Consumer<String> infoLogger;
-  private final Supplier<Boolean> supportsSerialFilter;
-
-  JmxRmiOpenTypesSerialFilter() {
-    this(logger::info, () -> isJavaVersionAtLeast(JavaVersion.JAVA_9));
-  }
-
-  @VisibleForTesting
-  JmxRmiOpenTypesSerialFilter(Consumer<String> infoLogger, Supplier<Boolean> supportsSerialFilter) {
-    this.infoLogger = infoLogger;
-    this.supportsSerialFilter = supportsSerialFilter;
-  }
-
-  @Override
-  public void configureSerialFilter() {
-    if (supportsDedicatedSerialFilter()) {
-      setPropertyValueUnlessExists(createSerialFilterPattern());
-    }
-  }
-
-  @VisibleForTesting
-  boolean supportsDedicatedSerialFilter() {
-    return supportsSerialFilter.get();
-  }
-
-  /**
-   * Sets the value of the system property {@code jmx.remote.rmi.server.serial.filter.pattern}
-   * unless it exists with a value that is not null or empty ("").
-   */
-  @VisibleForTesting
-  void setPropertyValueUnlessExists(String value) {
-    String existingValue = System.getProperty(PROPERTY_NAME);
-    if (StringUtils.isNotEmpty(existingValue)) {
-      infoLogger.accept("System property " + PROPERTY_NAME + " is already configured.");
-      return;
-    }
-
-    System.setProperty(PROPERTY_NAME, value);
-    infoLogger.accept("System property " + PROPERTY_NAME + " is now configured with '"
-        + value + "'.");
-  }
-
-  /**
-   * Returns a serial filter pattern that accepts all open MBean data types and rejects everything
-   * not included in the pattern.
-   */
-  @VisibleForTesting
-  String createSerialFilterPattern() {
-    // note: java.util.* may also be needed
-    return new StringBuilder()
-        // accept all open MBean data types
-        .append(Boolean.class.getName())
-        .append(';')
-        .append(Byte.class.getName())
-        .append(';')
-        .append(Character.class.getName())
-        .append(';')
-        .append(Short.class.getName())
-        .append(';')
-        .append(Integer.class.getName())
-        .append(';')
-        .append(Long.class.getName())
-        .append(';')
-        .append(Float.class.getName())
-        .append(';')
-        .append(Double.class.getName())
-        .append(';')
-        .append(String.class.getName())
-        .append(';')
-        .append(BigInteger.class.getName())
-        .append(';')
-        .append(BigDecimal.class.getName())
-        .append(';')
-        .append(ObjectName.class.getName())
-        .append(';')
-        .append(OpenType.class.getName())
-        .append(';')
-        .append(CompositeData.class.getName())
-        .append(';')
-        .append(TabularData.class.getName())
-        .append(';')
-        .append(SimpleType.class.getName())
-        .append(';')
-        .append(CompositeType.class.getName())
-        .append(';')
-        .append(TabularType.class.getName())
-        .append(';')
-        .append(ArrayType.class.getName())
-        .append(';')
-        .append(MarshalledObject.class.getName())
-        .append(';')
-        // reject all other classes
-        .append("!*")
-        .toString();
-  }
-}
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java b/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java
index 5648ceb..22d50bd 100755
--- a/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java
@@ -65,6 +65,7 @@ import org.apache.geode.internal.net.SocketCreatorFactory;
 import org.apache.geode.internal.security.SecurableCommunicationChannel;
 import org.apache.geode.internal.security.SecurityService;
 import org.apache.geode.internal.security.shiro.JMXShiroAuthenticator;
+import org.apache.geode.internal.serialization.filter.FilterConfiguration;
 import org.apache.geode.internal.tcp.TCPConduit;
 import org.apache.geode.logging.internal.log4j.api.LogService;
 import org.apache.geode.management.ManagementException;
@@ -109,7 +110,7 @@ public class ManagementAgent {
   private int port;
   private RemoteStreamExporter remoteStreamExporter = null;
 
-  private final JmxRmiSerialFilter serialFilter;
+  private final FilterConfiguration filterConfiguration;
 
   /**
    * This system property is set to true when the embedded HTTP server is started so that the
@@ -122,20 +123,19 @@ public class ManagementAgent {
   private static final String PULSE_USESSL_LOCATOR = "pulse.useSSL.locator";
 
   public ManagementAgent(DistributionConfig config, InternalCache cache,
-      JmxRmiSerialFilter serialFilter) {
+      FilterConfiguration filterConfiguration) {
     this.config = config;
     this.cache = cache;
     this.securityService = cache.getSecurityService();
-    this.serialFilter = serialFilter;
+    this.filterConfiguration = filterConfiguration;
   }
 
   public synchronized boolean isRunning() {
     return this.running;
   }
 
-
   public synchronized void startAgent() {
-    serialFilter.configureSerialFilter();
+    filterConfiguration.configure();
     loadWebApplications();
 
     if (!this.running && this.config.getJmxManagerPort() != 0) {
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgentFactory.java b/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgentFactory.java
index bff205e..31c3652 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgentFactory.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgentFactory.java
@@ -16,10 +16,11 @@ package org.apache.geode.management.internal;
 
 import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.internal.serialization.filter.FilterConfiguration;
 
 @FunctionalInterface
 interface ManagementAgentFactory {
 
   ManagementAgent create(DistributionConfig config, InternalCache cache,
-      JmxRmiSerialFilter serialFilter);
+      FilterConfiguration filterConfiguration);
 }
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/SystemManagementService.java b/geode-core/src/main/java/org/apache/geode/management/internal/SystemManagementService.java
index 76f47c5..7d117fa 100755
--- a/geode-core/src/main/java/org/apache/geode/management/internal/SystemManagementService.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/SystemManagementService.java
@@ -45,6 +45,8 @@ import org.apache.geode.distributed.internal.ResourceEvent;
 import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
 import org.apache.geode.internal.cache.InternalCache;
 import org.apache.geode.internal.cache.InternalCacheForClientAccess;
+import org.apache.geode.internal.serialization.filter.FilterConfiguration;
+import org.apache.geode.internal.serialization.filter.SystemPropertyJmxSerialFilterConfigurationFactory;
 import org.apache.geode.internal.statistics.StatisticsClock;
 import org.apache.geode.logging.internal.executors.LoggingExecutors;
 import org.apache.geode.logging.internal.log4j.api.LogService;
@@ -178,9 +180,12 @@ public class SystemManagementService extends BaseManagementService {
     repo = new ManagementResourceRepo();
     notificationHub = notificationHubFactory.apply(repo);
 
+
     if (system.getConfig().getJmxManager()) {
-      agent = managementAgentFactory.create(system.getConfig(), cache,
-          new JmxRmiOpenTypesSerialFilter());
+      FilterConfiguration filterConfiguration =
+          new SystemPropertyJmxSerialFilterConfigurationFactory().create();
+
+      agent = managementAgentFactory.create(system.getConfig(), cache, filterConfiguration);
     } else {
       agent = null;
     }
diff --git a/geode-core/src/test/java/org/apache/geode/distributed/internal/DistributionConfigJUnitTest.java b/geode-core/src/test/java/org/apache/geode/distributed/internal/DistributionConfigJUnitTest.java
index f204a47..b33ee4a 100644
--- a/geode-core/src/test/java/org/apache/geode/distributed/internal/DistributionConfigJUnitTest.java
+++ b/geode-core/src/test/java/org/apache/geode/distributed/internal/DistributionConfigJUnitTest.java
@@ -63,6 +63,7 @@ import org.junit.experimental.categories.Category;
 
 import org.apache.geode.UnmodifiableException;
 import org.apache.geode.internal.ConfigSource;
+import org.apache.geode.internal.serialization.filter.SerializableObjectConfig;
 import org.apache.geode.security.TestPostProcessor;
 import org.apache.geode.security.TestSecurityManager;
 import org.apache.geode.test.junit.categories.MembershipTest;
@@ -494,4 +495,11 @@ public class DistributionConfigJUnitTest {
     DistributionConfig config = new DistributionConfigImpl(props);
     assertThat(config.getSecurityAuthTokenEnabledComponents()).containsExactly("MANAGEMENT");
   }
+
+  @Test
+  public void isASerializableObjectConfig() {
+    DistributionConfig config = new DistributionConfigImpl(new Properties());
+
+    assertThat(config).isInstanceOf(SerializableObjectConfig.class);
+  }
 }
diff --git a/geode-core/src/test/java/org/apache/geode/internal/DistributedSerializableObjectConfigTest.java b/geode-core/src/test/java/org/apache/geode/internal/DistributedSerializableObjectConfigTest.java
new file mode 100644
index 0000000..d528904
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/DistributedSerializableObjectConfigTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.internal;
+
+import static org.apache.geode.distributed.ConfigurationProperties.SERIALIZABLE_OBJECT_FILTER;
+import static org.apache.geode.distributed.ConfigurationProperties.VALIDATE_SERIALIZABLE_OBJECTS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.util.Properties;
+
+import org.junit.Test;
+
+import org.apache.geode.internal.serialization.filter.SerializableObjectConfig;
+
+public class DistributedSerializableObjectConfigTest {
+
+  @Test
+  public void getValidateSerializableObjects_returnsTrue_whenPropertyIsTrue() {
+    Properties properties = spy(new Properties());
+    SerializableObjectConfig config = new DistributedSerializableObjectConfig(properties);
+    when(properties.getProperty(VALIDATE_SERIALIZABLE_OBJECTS)).thenReturn("true");
+
+    boolean result = config.getValidateSerializableObjects();
+
+    assertThat(result).isTrue();
+  }
+
+  @Test
+  public void setValidateSerializableObjects_setsPropertyValue() {
+    Properties properties = spy(new Properties());
+    SerializableObjectConfig config = new DistributedSerializableObjectConfig(properties);
+
+    config.setValidateSerializableObjects(true);
+
+    assertThat(properties.getProperty(VALIDATE_SERIALIZABLE_OBJECTS)).isEqualTo("true");
+  }
+
+  @Test
+  public void getSerializableObjectFilter_returnsPropertyValue() {
+    Properties properties = spy(new Properties());
+    SerializableObjectConfig config = new DistributedSerializableObjectConfig(properties);
+    when(properties.getProperty(SERIALIZABLE_OBJECT_FILTER)).thenReturn("!*");
+
+    String result = config.getSerializableObjectFilter();
+
+    assertThat(result).isEqualTo("!*");
+  }
+}
diff --git a/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerSerializationAcceptlistTest.java b/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerSerializationAcceptlistTest.java
index edbb395..f186df9 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerSerializationAcceptlistTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerSerializationAcceptlistTest.java
@@ -17,14 +17,15 @@ package org.apache.geode.internal;
 import static java.util.Collections.emptySet;
 import static org.apache.geode.distributed.internal.DistributionConfig.SERIALIZABLE_OBJECT_FILTER_NAME;
 import static org.apache.geode.distributed.internal.DistributionConfig.VALIDATE_SERIALIZABLE_OBJECTS_NAME;
-import static org.apache.geode.internal.lang.utils.ClassUtils.isClassAvailable;
+import static org.apache.geode.internal.InternalDataSerializer.initializeSerializationFilter;
+import static org.apache.geode.internal.serialization.filter.ObjectInputFilterUtils.supportsObjectInputFilter;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.catchThrowable;
-import static org.junit.Assume.assumeTrue;
 
 import java.io.ByteArrayInputStream;
 import java.io.DataInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InvalidClassException;
 import java.io.Serializable;
 import java.util.Properties;
@@ -36,9 +37,9 @@ import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
 import org.apache.geode.DataSerializer;
-import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.distributed.internal.DistributionConfigImpl;
 import org.apache.geode.internal.serialization.KnownVersion;
+import org.apache.geode.internal.serialization.filter.SerializableObjectConfig;
 import org.apache.geode.test.junit.categories.SerializationTest;
 
 @Category(SerializationTest.class)
@@ -50,9 +51,9 @@ public class InternalDataSerializerSerializationAcceptlistTest {
 
   @BeforeClass
   public static void hasObjectInputFilter() {
-    assumeTrue("ObjectInputFilter is present in this JVM",
-        isClassAvailable("sun.misc.ObjectInputFilter") ||
-            isClassAvailable("java.io.ObjectInputFilter"));
+    assertThat(supportsObjectInputFilter())
+        .as("java.io.ObjectInputFilter or sun.misc.ObjectInputFilter is available")
+        .isTrue();
   }
 
   @Before
@@ -69,23 +70,37 @@ public class InternalDataSerializerSerializationAcceptlistTest {
 
   @Test
   public void distributionConfigDefaults() {
-    DistributionConfig distributionConfig = new DistributionConfigImpl(new Properties());
+    SerializableObjectConfig config = new DistributionConfigImpl(new Properties());
 
-    assertThat(distributionConfig.getValidateSerializableObjects()).isFalse();
-    assertThat(distributionConfig.getSerializableObjectFilter()).isEqualTo("!*");
+    assertThat(config.getValidateSerializableObjects()).isFalse();
+    assertThat(config.getSerializableObjectFilter()).isEqualTo("!*");
   }
 
   @Test
-  public void canSerializeWhenFilterIsDisabled() throws Exception {
-    trySerializingTestObject(new Properties());
+  public void canSerializeWhenFilterIsDisabled() throws ClassNotFoundException, IOException {
+    initializeSerializationFilter(emptyConfig(), emptySet());
+    DataSerializer.writeObject(testSerializable, outputStream);
+
+    Object deserializedObject;
+    try (InputStream in = new ByteArrayInputStream(outputStream.toByteArray());
+        DataInputStream dataInputStream = new DataInputStream(in)) {
+      deserializedObject = DataSerializer.readObject(dataInputStream);
+    }
+
+    assertThat(deserializedObject).isInstanceOf(testSerializable.getClass());
   }
 
   @Test
-  public void notAcceptlistedWithFilterCannotSerialize() {
+  public void notAcceptlistedWithFilterCannotSerialize() throws IOException {
     properties.setProperty(VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
+    initializeSerializationFilter(config(properties), emptySet());
+    DataSerializer.writeObject(testSerializable, outputStream);
 
     Throwable thrown = catchThrowable(() -> {
-      trySerializingTestObject(properties);
+      try (InputStream in = new ByteArrayInputStream(outputStream.toByteArray());
+          DataInputStream dataInputStream = new DataInputStream(in)) {
+        DataSerializer.readObject(dataInputStream);
+      }
     });
 
     assertThat(thrown).isInstanceOf(InvalidClassException.class);
@@ -95,51 +110,100 @@ public class InternalDataSerializerSerializationAcceptlistTest {
   public void acceptlistedWithFilterCanSerialize() throws Exception {
     properties.setProperty(VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
     properties.setProperty(SERIALIZABLE_OBJECT_FILTER_NAME, TestSerializable.class.getName());
+    initializeSerializationFilter(config(properties), emptySet());
+    DataSerializer.writeObject(testSerializable, outputStream);
+
+    Object deserializedObject;
+    try (InputStream in = new ByteArrayInputStream(outputStream.toByteArray());
+        DataInputStream dataInputStream = new DataInputStream(in)) {
+      deserializedObject = DataSerializer.readObject(dataInputStream);
+    }
 
-    trySerializingTestObject(properties);
+    assertThat(deserializedObject).isInstanceOf(testSerializable.getClass());
   }
 
   @Test
-  public void acceptlistedWithNonMatchingFilterCannotSerialize() {
+  public void acceptlistedWithNonMatchingFilterCannotSerialize() throws IOException {
+    properties.setProperty(VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
+    properties.setProperty(SERIALIZABLE_OBJECT_FILTER_NAME, "RabidMonkeyTurnip");
+    initializeSerializationFilter(config(properties), emptySet());
+    DataSerializer.writeObject(testSerializable, outputStream);
+
     Throwable thrown = catchThrowable(() -> {
-      trySerializingWithFilter("RabidMonkeyTurnip");
+      try (InputStream in = new ByteArrayInputStream(outputStream.toByteArray());
+          DataInputStream dataInputStream = new DataInputStream(in)) {
+        DataSerializer.readObject(dataInputStream);
+      }
     });
 
     assertThat(thrown).isInstanceOf(InvalidClassException.class);
   }
 
   @Test
-  public void acceptlistedWithPartialMatchingFilterCannotSerialize() {
+  public void acceptlistedWithPartialMatchingFilterCannotSerialize() throws IOException {
+    // Not fully qualified class name
+    properties.setProperty(VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
+    properties.setProperty(SERIALIZABLE_OBJECT_FILTER_NAME, "TestSerializable");
+    initializeSerializationFilter(config(properties), emptySet());
+    DataSerializer.writeObject(testSerializable, outputStream);
+
     Throwable thrown = catchThrowable(() -> {
-      trySerializingWithFilter("TestSerializable"); // Not fully qualified class name
+      try (InputStream in = new ByteArrayInputStream(outputStream.toByteArray());
+          DataInputStream dataInputStream = new DataInputStream(in)) {
+        DataSerializer.readObject(dataInputStream);
+      }
     });
 
     assertThat(thrown).isInstanceOf(InvalidClassException.class);
   }
 
   @Test
-  public void acceptlistedWithEmptyFilterCannotSerialize() {
+  public void acceptlistedWithEmptyFilterCannotSerialize() throws IOException {
+    properties.setProperty(VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
+    properties.setProperty(SERIALIZABLE_OBJECT_FILTER_NAME, "");
+    initializeSerializationFilter(config(properties), emptySet());
+    DataSerializer.writeObject(testSerializable, outputStream);
+
     Throwable thrown = catchThrowable(() -> {
-      trySerializingWithFilter("");
+      try (InputStream in = new ByteArrayInputStream(outputStream.toByteArray());
+          DataInputStream dataInputStream = new DataInputStream(in)) {
+        DataSerializer.readObject(dataInputStream);
+      }
     });
 
     assertThat(thrown).isInstanceOf(InvalidClassException.class);
   }
 
   @Test
-  public void acceptlistedWithIncorrectPathFilterCannotSerialize() {
+  public void acceptlistedWithIncorrectPathFilterCannotSerialize() throws IOException {
+    properties.setProperty(VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
+    properties.setProperty(SERIALIZABLE_OBJECT_FILTER_NAME,
+        "org.apache.commons.InternalDataSerializerSerializationAcceptlistTest$TestSerializable");
+    initializeSerializationFilter(config(properties), emptySet());
+    DataSerializer.writeObject(testSerializable, outputStream);
+
     Throwable thrown = catchThrowable(() -> {
-      trySerializingWithFilter(
-          "org.apache.commons.InternalDataSerializerSerializationAcceptlistTest$TestSerializable");
+      try (InputStream in = new ByteArrayInputStream(outputStream.toByteArray());
+          DataInputStream dataInputStream = new DataInputStream(in)) {
+        DataSerializer.readObject(dataInputStream);
+      }
     });
 
     assertThat(thrown).isInstanceOf(InvalidClassException.class);
   }
 
   @Test
-  public void acceptlistedWithWildcardPathFilterCannotSerialize() {
+  public void acceptlistedWithWildcardPathFilterCannotSerialize() throws IOException {
+    properties.setProperty(VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
+    properties.setProperty(SERIALIZABLE_OBJECT_FILTER_NAME, "org.apache.*");
+    initializeSerializationFilter(config(properties), emptySet());
+    DataSerializer.writeObject(testSerializable, outputStream);
+
     Throwable thrown = catchThrowable(() -> {
-      trySerializingWithFilter("org.apache.*");
+      try (InputStream in = new ByteArrayInputStream(outputStream.toByteArray());
+          DataInputStream dataInputStream = new DataInputStream(in)) {
+        DataSerializer.readObject(dataInputStream);
+      }
     });
 
     assertThat(thrown).isInstanceOf(InvalidClassException.class);
@@ -147,26 +211,26 @@ public class InternalDataSerializerSerializationAcceptlistTest {
 
   @Test
   public void acceptlistedWithWildcardSubpathFilterCanSerialize() throws Exception {
-    trySerializingWithFilter("org.apache.**");
-  }
-
-  private void trySerializingWithFilter(String filter) throws Exception {
     properties.setProperty(VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
-    properties.setProperty(SERIALIZABLE_OBJECT_FILTER_NAME, filter);
+    properties.setProperty(SERIALIZABLE_OBJECT_FILTER_NAME, "org.apache.**");
+    initializeSerializationFilter(config(properties), emptySet());
+    DataSerializer.writeObject(testSerializable, outputStream);
 
-    trySerializingTestObject(properties);
-  }
+    Object deserializedObject;
+    try (InputStream in = new ByteArrayInputStream(outputStream.toByteArray());
+        DataInputStream dataInputStream = new DataInputStream(in)) {
+      deserializedObject = DataSerializer.readObject(dataInputStream);
+    }
 
-  private void trySerializingTestObject(Properties properties)
-      throws IOException, ClassNotFoundException {
-    DistributionConfig distributionConfig = new DistributionConfigImpl(properties);
-    InternalDataSerializer.initializeSerializationFilter(distributionConfig, emptySet());
+    assertThat(deserializedObject).isInstanceOf(testSerializable.getClass());
+  }
 
-    DataSerializer.writeObject(testSerializable, outputStream);
+  private static SerializableObjectConfig emptyConfig() {
+    return config(new Properties());
+  }
 
-    // if this throws, we're good!
-    DataSerializer
-        .readObject(new DataInputStream(new ByteArrayInputStream(outputStream.toByteArray())));
+  private static SerializableObjectConfig config(Properties properties) {
+    return new DistributedSerializableObjectConfig(properties);
   }
 
   @SuppressWarnings("serial")
diff --git a/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerShiroAcceptListTest.java b/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerShiroAcceptListTest.java
index 59b7062..64f3415 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerShiroAcceptListTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerShiroAcceptListTest.java
@@ -16,9 +16,9 @@ package org.apache.geode.internal;
 
 import static java.util.Collections.emptySet;
 import static org.apache.geode.distributed.internal.DistributionConfig.VALIDATE_SERIALIZABLE_OBJECTS_NAME;
-import static org.apache.geode.internal.lang.utils.ClassUtils.isClassAvailable;
 import static org.apache.geode.internal.serialization.KnownVersion.CURRENT;
-import static org.junit.Assume.assumeTrue;
+import static org.apache.geode.internal.serialization.filter.ObjectInputFilterUtils.supportsObjectInputFilter;
+import static org.assertj.core.api.Assertions.assertThat;
 
 import java.io.ByteArrayInputStream;
 import java.io.DataInputStream;
@@ -44,8 +44,7 @@ import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
 import org.apache.geode.DataSerializer;
-import org.apache.geode.distributed.internal.DistributionConfig;
-import org.apache.geode.distributed.internal.DistributionConfigImpl;
+import org.apache.geode.internal.serialization.filter.SerializableObjectConfig;
 import org.apache.geode.test.junit.categories.SecurityTest;
 import org.apache.geode.test.junit.categories.SerializationTest;
 
@@ -54,9 +53,9 @@ public class InternalDataSerializerShiroAcceptListTest {
 
   @BeforeClass
   public static void hasObjectInputFilter() {
-    assumeTrue("ObjectInputFilter is present in this JVM",
-        isClassAvailable("sun.misc.ObjectInputFilter") ||
-            isClassAvailable("java.io.ObjectInputFilter"));
+    assertThat(supportsObjectInputFilter())
+        .as("java.io.ObjectInputFilter or sun.misc.ObjectInputFilter is available")
+        .isTrue();
   }
 
   @After
@@ -138,17 +137,9 @@ public class InternalDataSerializerShiroAcceptListTest {
         propertiesWithoutFilter());
   }
 
-  private static Properties propertiesWithoutFilter() {
-    Properties properties = new Properties();
-    properties.setProperty(VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
-
-    return properties;
-  }
-
   private static void trySerializingObject(Object object, Properties properties)
       throws IOException, ClassNotFoundException {
-    DistributionConfig distributionConfig = new DistributionConfigImpl(properties);
-    InternalDataSerializer.initializeSerializationFilter(distributionConfig, emptySet());
+    InternalDataSerializer.initializeSerializationFilter(config(properties), emptySet());
     HeapDataOutputStream outputStream = new HeapDataOutputStream(CURRENT);
 
     DataSerializer.writeObject(object, outputStream);
@@ -158,4 +149,15 @@ public class InternalDataSerializerShiroAcceptListTest {
       DataSerializer.readObject(dis);
     }
   }
+
+  private static SerializableObjectConfig config(Properties properties) {
+    return new DistributedSerializableObjectConfig(properties);
+  }
+
+  private static Properties propertiesWithoutFilter() {
+    Properties properties = new Properties();
+    properties.setProperty(VALIDATE_SERIALIZABLE_OBJECTS_NAME, "true");
+
+    return properties;
+  }
 }
diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/JmxRmiOpenTypesSerialFilterTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/JmxRmiOpenTypesSerialFilterTest.java
deleted file mode 100644
index c8f8ea9..0000000
--- a/geode-core/src/test/java/org/apache/geode/management/internal/JmxRmiOpenTypesSerialFilterTest.java
+++ /dev/null
@@ -1,351 +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.management.internal;
-
-import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
-import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
-import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assumptions.assumeThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.rmi.MarshalledObject;
-import java.util.function.Consumer;
-
-import javax.management.ObjectName;
-import javax.management.openmbean.ArrayType;
-import javax.management.openmbean.CompositeData;
-import javax.management.openmbean.CompositeType;
-import javax.management.openmbean.SimpleType;
-import javax.management.openmbean.TabularData;
-import javax.management.openmbean.TabularType;
-
-import org.apache.commons.lang3.JavaVersion;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.contrib.java.lang.system.RestoreSystemProperties;
-
-public class JmxRmiOpenTypesSerialFilterTest {
-
-  private Consumer<String> infoLogger;
-
-  @Rule
-  public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
-
-  @Before
-  public void setUp() {
-    infoLogger = uncheckedCast(mock(Consumer.class));
-  }
-
-  @Test
-  public void propertyValueIsNullByDefault() {
-    String propertyValue = System.getProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME);
-
-    assertThat(propertyValue).isNull();
-  }
-
-  @Test
-  public void supportsDedicatedSerialFilter_returnsTrue_atLeastJava9() {
-    assumeThat(isJavaVersionAtLeast(JavaVersion.JAVA_9)).isTrue();
-
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    boolean result = jmxRmiOpenTypesSerialFilter.supportsDedicatedSerialFilter();
-
-    assertThat(result).isTrue();
-  }
-
-  @Test
-  public void supportsDedicatedSerialFilter_returnsFalse_atMostJava8() {
-    assumeThat(isJavaVersionAtMost(JavaVersion.JAVA_1_8)).isTrue();
-
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    boolean result = jmxRmiOpenTypesSerialFilter.supportsDedicatedSerialFilter();
-
-    assertThat(result).isFalse();
-  }
-
-  @Test
-  public void setPropertyValue_setsValue_ifExistingValueIsNull() {
-    String value = "value-of-property";
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    jmxRmiOpenTypesSerialFilter.setPropertyValueUnlessExists(value);
-
-    String propertyValue = System.getProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME);
-    assertThat(propertyValue).isEqualTo(value);
-  }
-
-  @Test
-  public void setPropertyValue_setsValue_ifExistingValueIsEmpty() {
-    System.setProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME, "");
-    String value = "value-of-property";
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    jmxRmiOpenTypesSerialFilter.setPropertyValueUnlessExists(value);
-
-    String propertyValue = System.getProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME);
-    assertThat(propertyValue).isEqualTo(value);
-  }
-
-  @Test
-  public void setPropertyValue_logsMessage_ifExistingValueIsEmpty() {
-    System.setProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME, "");
-    String value = "value-of-property";
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter =
-        new JmxRmiOpenTypesSerialFilter(infoLogger, () -> true);
-
-    jmxRmiOpenTypesSerialFilter.setPropertyValueUnlessExists(value);
-
-    String expectedLogMessage = "System property " + JmxRmiOpenTypesSerialFilter.PROPERTY_NAME +
-        " is now configured with '" + value + "'.";
-    verify(infoLogger).accept(expectedLogMessage);
-  }
-
-  @Test
-  public void setPropertyValue_leavesExistingValue_ifExistingValueIsNotEmpty() {
-    String existingValue = "existing-value-of-property";
-    System.setProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME, existingValue);
-    String value = "value-of-property";
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    jmxRmiOpenTypesSerialFilter.setPropertyValueUnlessExists(value);
-
-    String propertyValue = System.getProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME);
-    assertThat(propertyValue).isEqualTo(existingValue);
-  }
-
-  @Test
-  public void setPropertyValue_logsMessage_ifExistingValueIsNotEmpty() {
-    String existingValue = "existing-value-of-property";
-    System.setProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME, existingValue);
-    String value = "value-of-property";
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter =
-        new JmxRmiOpenTypesSerialFilter(infoLogger, () -> true);
-
-    jmxRmiOpenTypesSerialFilter.setPropertyValueUnlessExists(value);
-
-    String expectedLogMessage = "System property " + JmxRmiOpenTypesSerialFilter.PROPERTY_NAME +
-        " is already configured.";
-    verify(infoLogger).accept(expectedLogMessage);
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesBoolean() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(Boolean.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesByte() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(Byte.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesCharacter() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(Character.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesShort() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(Short.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesInteger() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(Integer.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesLong() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(Long.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesFloat() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(Float.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesDouble() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(Double.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesString() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(String.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesBigInteger() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(BigInteger.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesBigDecimal() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(BigDecimal.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesObjectName() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(ObjectName.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesCompositeData() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(CompositeData.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesTabularData() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(TabularData.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesSimpleType() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(SimpleType.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesCompositeType() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(CompositeType.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesTabularType() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(TabularType.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesArrayType() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(ArrayType.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_includesMarshalledObject() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).contains(MarshalledObject.class.getName());
-  }
-
-  @Test
-  public void createSerialFilterPattern_rejectsAllOtherTypes() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter = new JmxRmiOpenTypesSerialFilter();
-
-    String result = jmxRmiOpenTypesSerialFilter.createSerialFilterPattern();
-
-    assertThat(result).endsWith("!*");
-  }
-
-  @Test
-  public void configureSerialFilterIfEmpty_setsPropertyValue_atLeastJava9() {
-    JmxRmiOpenTypesSerialFilter jmxRmiOpenTypesSerialFilter =
-        new JmxRmiOpenTypesSerialFilter(infoLogger, () -> true);
-
-    jmxRmiOpenTypesSerialFilter.configureSerialFilter();
-
-    String propertyValue = System.getProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME);
-    assertThat(propertyValue).isNotEmpty();
-  }
-
-  @Test
-  public void configureSerialFilterIfEmpty_setsPropertyValue_atMostJava8() {
-    JmxRmiSerialFilter jmxRmiOpenTypesSerialFilter =
-        new JmxRmiOpenTypesSerialFilter(infoLogger, () -> false);
-
-    jmxRmiOpenTypesSerialFilter.configureSerialFilter();
-
-    String propertyValue = System.getProperty(JmxRmiOpenTypesSerialFilter.PROPERTY_NAME);
-    assertThat(propertyValue).isNull();
-  }
-}
diff --git a/geode-core/src/test/resources/expected-pom.xml b/geode-core/src/test/resources/expected-pom.xml
index c131a25..480c2c0 100644
--- a/geode-core/src/test/resources/expected-pom.xml
+++ b/geode-core/src/test/resources/expected-pom.xml
@@ -78,6 +78,11 @@
     </dependency>
     <dependency>
       <groupId>org.apache.geode</groupId>
+      <artifactId>geode-serialization</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.geode</groupId>
       <artifactId>geode-management</artifactId>
       <scope>compile</scope>
     </dependency>
@@ -229,11 +234,6 @@
     </dependency>
     <dependency>
       <groupId>org.apache.geode</groupId>
-      <artifactId>geode-serialization</artifactId>
-      <scope>runtime</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.geode</groupId>
       <artifactId>geode-tcp-server</artifactId>
       <scope>runtime</scope>
     </dependency>
diff --git a/geode-http-service/build.gradle b/geode-http-service/build.gradle
index c9e0580..5af9232 100755
--- a/geode-http-service/build.gradle
+++ b/geode-http-service/build.gradle
@@ -33,6 +33,7 @@ dependencies {
   compileOnly(project(':geode-common')) {
     exclude module: 'junit'
   }
+  compileOnly(project(':geode-serialization'))
 
   testImplementation(project(':geode-core'))
   testImplementation(project(':geode-common'))
diff --git a/geode-junit/src/main/java/org/apache/geode/codeAnalysis/AnalyzeSerializablesTestBase.java b/geode-junit/src/main/java/org/apache/geode/codeAnalysis/AnalyzeSerializablesTestBase.java
index 2d5dd3a..c9d6d57 100644
--- a/geode-junit/src/main/java/org/apache/geode/codeAnalysis/AnalyzeSerializablesTestBase.java
+++ b/geode-junit/src/main/java/org/apache/geode/codeAnalysis/AnalyzeSerializablesTestBase.java
@@ -16,6 +16,7 @@ package org.apache.geode.codeAnalysis;
 
 import static org.apache.geode.distributed.ConfigurationProperties.SERIALIZABLE_OBJECT_FILTER;
 import static org.apache.geode.distributed.ConfigurationProperties.VALIDATE_SERIALIZABLE_OBJECTS;
+import static org.apache.geode.internal.InternalDataSerializer.initializeSerializationFilter;
 import static org.assertj.core.api.Assertions.fail;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -52,8 +53,7 @@ import org.apache.geode.DataSerializable;
 import org.apache.geode.DataSerializer;
 import org.apache.geode.codeAnalysis.decode.CompiledClass;
 import org.apache.geode.codeAnalysis.decode.CompiledField;
-import org.apache.geode.distributed.internal.DistributionConfigImpl;
-import org.apache.geode.internal.InternalDataSerializer;
+import org.apache.geode.internal.DistributedSerializableObjectConfig;
 import org.apache.geode.internal.serialization.BufferDataOutputStream;
 import org.apache.geode.internal.serialization.DataSerializableFixedID;
 import org.apache.geode.internal.serialization.KnownVersion;
@@ -333,7 +333,7 @@ public abstract class AnalyzeSerializablesTestBase
     properties.setProperty(VALIDATE_SERIALIZABLE_OBJECTS, "true");
     properties.setProperty(SERIALIZABLE_OBJECT_FILTER, "!*");
 
-    InternalDataSerializer.initializeSerializationFilter(new DistributionConfigImpl(properties));
+    initializeSerializationFilter(new DistributedSerializableObjectConfig(properties));
   }
 
   @Override
diff --git a/geode-serialization/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiSetFilterBlankIntegrationTest.java b/geode-serialization/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiSetFilterBlankIntegrationTest.java
new file mode 100644
index 0000000..6155c8b
--- /dev/null
+++ b/geode-serialization/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiSetFilterBlankIntegrationTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.apache.geode.internal.serialization.filter.ApiPackage.JAVA_IO;
+import static org.apache.geode.internal.serialization.filter.ApiPackage.SUN_MISC;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * This test calls the real setSerialFilter which can only be set once within a JVM, so it's in an
+ * integration test which forks a new JVM for the test class.
+ */
+public class ReflectiveObjectInputFilterApiSetFilterBlankIntegrationTest {
+
+  private ApiPackage apiPackage;
+
+  @Before
+  public void setUp() {
+    if (isJavaVersionAtLeast(JAVA_1_8) && isJavaVersionAtMost(JAVA_1_8)) {
+      apiPackage = SUN_MISC;
+    }
+
+    if (isJavaVersionAtLeast(JAVA_9)) {
+      apiPackage = JAVA_IO;
+    }
+
+  }
+
+  @Test
+  public void setsFilterGivenBlankPattern()
+      throws ClassNotFoundException, IllegalAccessException, InvocationTargetException,
+      NoSuchMethodException {
+    ObjectInputFilterApi api = new ReflectiveObjectInputFilterApi(apiPackage);
+    Object filter = api.createFilter(" ");
+
+    api.setSerialFilter(filter);
+
+    assertThat(api.getSerialFilter())
+        .as("ObjectInputFilter$Config.getSerialFilter()")
+        .isSameAs(filter);
+  }
+}
diff --git a/geode-serialization/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiSetFilterIntegrationTest.java b/geode-serialization/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiSetFilterIntegrationTest.java
new file mode 100644
index 0000000..619673b
--- /dev/null
+++ b/geode-serialization/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiSetFilterIntegrationTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * This test calls the real setSerialFilter which can only be set once within a JVM, so it's in an
+ * integration test which forks a new JVM for the test class.
+ */
+public class ReflectiveObjectInputFilterApiSetFilterIntegrationTest {
+
+  private ApiPackage apiPackage;
+
+  @Before
+  public void setUp() {
+    if (isJavaVersionAtLeast(JAVA_1_8) && isJavaVersionAtMost(JAVA_1_8)) {
+      apiPackage = ApiPackage.SUN_MISC;
+    }
+
+    if (isJavaVersionAtLeast(JAVA_9)) {
+      apiPackage = ApiPackage.JAVA_IO;
+    }
+  }
+
+  @Test
+  public void setsFilterGivenValidPattern()
+      throws ClassNotFoundException, IllegalAccessException, InvocationTargetException,
+      NoSuchMethodException {
+    ObjectInputFilterApi api = new ReflectiveObjectInputFilterApi(apiPackage);
+    Object filter = api.createFilter("!*");
+
+    api.setSerialFilter(filter);
+
+    assertThat(api.getSerialFilter())
+        .as("ObjectInputFilter$Config.getSerialFilter()")
+        .isSameAs(filter);
+  }
+}
diff --git a/geode-serialization/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiSetFilterNullIntegrationTest.java b/geode-serialization/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiSetFilterNullIntegrationTest.java
new file mode 100644
index 0000000..9b13b73
--- /dev/null
+++ b/geode-serialization/src/integrationTest/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiSetFilterNullIntegrationTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * This test calls the real setSerialFilter which can only be set once within a JVM, so it's in an
+ * integration test which forks a new JVM for the test class.
+ */
+public class ReflectiveObjectInputFilterApiSetFilterNullIntegrationTest {
+
+  private ApiPackage apiPackage;
+
+  @Before
+  public void setUp() {
+    if (isJavaVersionAtLeast(JAVA_1_8) && isJavaVersionAtMost(JAVA_1_8)) {
+      apiPackage = ApiPackage.SUN_MISC;
+    }
+
+    if (isJavaVersionAtLeast(JAVA_9)) {
+      apiPackage = ApiPackage.JAVA_IO;
+    }
+  }
+
+  @Test
+  public void throwsNullPointerExceptionGivenNull()
+      throws ClassNotFoundException, NoSuchMethodException {
+    ObjectInputFilterApi api = new ReflectiveObjectInputFilterApi(apiPackage);
+
+    Throwable thrown = catchThrowable(() -> {
+      api.setSerialFilter(null);
+    });
+
+    assertThat(thrown)
+        .isInstanceOf(InvocationTargetException.class)
+        .hasCauseInstanceOf(NullPointerException.class);
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ApiPackage.java
similarity index 63%
copy from geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
copy to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ApiPackage.java
index 1d139ce..549911e 100644
--- a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ApiPackage.java
@@ -14,20 +14,28 @@
  */
 package org.apache.geode.internal.serialization.filter;
 
-import java.io.IOException;
-import java.net.URL;
-import java.util.Collection;
+import static java.util.Objects.requireNonNull;
 
-@FunctionalInterface
-public interface SanctionedSerializablesService {
+/**
+ * The Java package containing {@code ObjectInputFilter}. On Java 8, it's {@code sun.misc}. On
+ * Java 9 or greater, it's {@code java.io}.
+ */
+enum ApiPackage {
+
+  JAVA_IO("java.io."),
+  SUN_MISC("sun.misc.");
 
-  URL getSanctionedSerializablesURL();
+  private final String prefix;
+
+  ApiPackage(String prefix) {
+    this.prefix = prefix;
+  }
 
-  default Class<?> getInterface() {
-    return getClass();
+  String getPrefix() {
+    return prefix;
   }
 
-  default Collection<String> getSerializationAcceptlist() throws IOException {
-    return SanctionedSerializables.loadClassNames(getSanctionedSerializablesURL());
+  String qualify(String className) {
+    return prefix + requireNonNull(className, "className is required");
   }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/FilterConfiguration.java
similarity index 75%
copy from geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java
copy to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/FilterConfiguration.java
index 4a187e0..9a51a28 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/FilterConfiguration.java
@@ -12,10 +12,16 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.management.internal;
+package org.apache.geode.internal.serialization.filter;
 
+/**
+ * Defines operation to configure a serialization filter.
+ */
 @FunctionalInterface
-public interface JmxRmiSerialFilter {
+public interface FilterConfiguration {
 
-  void configureSerialFilter();
+  /**
+   * Returns true if serialization filter was successfully configured.
+   */
+  boolean configure();
 }
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/FilterPattern.java
similarity index 69%
copy from geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java
copy to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/FilterPattern.java
index 4a187e0..79135b5 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/FilterPattern.java
@@ -12,10 +12,17 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.management.internal;
+package org.apache.geode.internal.serialization.filter;
 
+/**
+ * Defines operation to generate a serialization filter pattern. The syntax is defined in the
+ * javadocs of the JREs {@code (sun.misc. or java.io.)ObjectInputFilter$Config#createFilter}
+ */
 @FunctionalInterface
-public interface JmxRmiSerialFilter {
+public interface FilterPattern {
 
-  void configureSerialFilter();
+  /**
+   * Returns the serialization filter pattern.
+   */
+  String pattern();
 }
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilter.java
similarity index 74%
copy from geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java
copy to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilter.java
index 4a187e0..87c19bd 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilter.java
@@ -12,10 +12,16 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.management.internal;
+package org.apache.geode.internal.serialization.filter;
 
+/**
+ * Defines operation to set this serialization filter as the JVMs process-wide filter.
+ */
 @FunctionalInterface
-public interface JmxRmiSerialFilter {
+interface GlobalSerialFilter {
 
-  void configureSerialFilter();
+  /**
+   * Sets this serialization filter as the process-wide filter.
+   */
+  void setFilter();
 }
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilterConfiguration.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilterConfiguration.java
new file mode 100644
index 0000000..7d312ac
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilterConfiguration.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.internal.serialization.filter;
+
+import static java.util.Objects.nonNull;
+import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCause;
+import static org.apache.geode.internal.serialization.filter.SanctionedSerializables.loadSanctionedClassNames;
+import static org.apache.geode.internal.serialization.filter.SanctionedSerializables.loadSanctionedSerializablesServices;
+
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import org.apache.logging.log4j.Logger;
+
+import org.apache.geode.logging.internal.log4j.api.LogService;
+
+/**
+ * Implementation of {@code FilterConfiguration} that delegates to an {@code ObjectInputFilterApi}.
+ */
+class GlobalSerialFilterConfiguration implements FilterConfiguration {
+
+  private static final Logger LOGGER = LogService.getLogger();
+
+  private final SerializableObjectConfig serializableObjectConfig;
+  private final FilterPatternFactory filterPatternFactory;
+  private final Supplier<Set<String>> sanctionedClassesSupplier;
+  private final Consumer<String> logger;
+  private final GlobalSerialFilterFactory globalSerialFilterFactory;
+
+  /**
+   * Constructs instance with collaborators.
+   */
+  GlobalSerialFilterConfiguration(SerializableObjectConfig serializableObjectConfig) {
+    this(serializableObjectConfig,
+        new DefaultFilterPatternFactory(),
+        () -> loadSanctionedClassNames(loadSanctionedSerializablesServices()),
+        LOGGER::info,
+        (pattern, sanctionedClasses) -> new ReflectiveFacadeGlobalSerialFilterFactory()
+            .create(pattern, sanctionedClasses));
+  }
+
+  GlobalSerialFilterConfiguration(SerializableObjectConfig serializableObjectConfig,
+      Consumer<String> logger, GlobalSerialFilterFactory globalSerialFilterFactory) {
+    this(serializableObjectConfig,
+        new DefaultFilterPatternFactory(),
+        () -> loadSanctionedClassNames(loadSanctionedSerializablesServices()),
+        logger,
+        globalSerialFilterFactory);
+  }
+
+  private GlobalSerialFilterConfiguration(
+      SerializableObjectConfig serializableObjectConfig,
+      FilterPatternFactory filterPatternFactory,
+      Supplier<Set<String>> sanctionedClassesSupplier,
+      Consumer<String> logger,
+      GlobalSerialFilterFactory globalSerialFilterFactory) {
+    this.serializableObjectConfig = serializableObjectConfig;
+    this.filterPatternFactory = filterPatternFactory;
+    this.sanctionedClassesSupplier = sanctionedClassesSupplier;
+    this.logger = logger;
+    this.globalSerialFilterFactory = globalSerialFilterFactory;
+  }
+
+  @Override
+  public boolean configure() {
+    try {
+      // enable validate-serializable-objects
+      serializableObjectConfig.setValidateSerializableObjects(true);
+
+      // create a GlobalSerialFilter
+      String pattern = filterPatternFactory
+          .create(serializableObjectConfig.getSerializableObjectFilterIfEnabled());
+      Set<String> sanctionedClasses = sanctionedClassesSupplier.get();
+      GlobalSerialFilter globalSerialFilter =
+          globalSerialFilterFactory.create(pattern, sanctionedClasses);
+
+      // invoke setFilter on GlobalSerialFilter to set the process-wide filter
+      globalSerialFilter.setFilter();
+
+      // log statement that filter is now configured
+      logger.accept("Global serial filter is now configured.");
+      return true;
+
+    } catch (UnsupportedOperationException e) {
+      if (hasRootCauseWithMessage(e, IllegalStateException.class,
+          "Serial filter can only be set once")) {
+
+        // log statement that filter was already configured
+        logger.accept("Global serial filter is already configured.");
+      }
+      return false;
+    }
+  }
+
+  private static boolean hasRootCauseWithMessage(Throwable throwable,
+      Class<? extends Throwable> causeClass, String message) {
+    Throwable rootCause = getRootCause(throwable);
+    return isInstanceOf(rootCause, causeClass) && hasMessage(rootCause, message);
+  }
+
+  private static boolean isInstanceOf(Throwable throwable, Class<? extends Throwable> causeClass) {
+    return nonNull(throwable) && throwable.getClass().equals(causeClass);
+  }
+
+  private static boolean hasMessage(Throwable throwable, String message) {
+    return nonNull(throwable) && throwable.getMessage().equalsIgnoreCase(message);
+  }
+
+  /**
+   * Creates filter pattern string including the specified optional
+   * {@code serializable-object-filter}.
+   */
+  @FunctionalInterface
+  interface FilterPatternFactory {
+
+    String create(String optionalSerializableObjectFilter);
+  }
+
+  /**
+   * Default implementation of {@code FilterPatternFactory}.
+   */
+  static class DefaultFilterPatternFactory implements FilterPatternFactory {
+
+    @Override
+    public String create(String optionalSerializableObjectFilter) {
+      return new SanctionedSerializablesFilterPattern()
+          .append(optionalSerializableObjectFilter)
+          .pattern();
+    }
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilterConfigurationFactory.java
similarity index 68%
copy from geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
copy to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilterConfigurationFactory.java
index 1d139ce..6a85bfd 100644
--- a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilterConfigurationFactory.java
@@ -14,20 +14,16 @@
  */
 package org.apache.geode.internal.serialization.filter;
 
-import java.io.IOException;
-import java.net.URL;
-import java.util.Collection;
-
+/**
+ * Defines the factory for creating a {@code FilterConfiguration} to configure a
+ * {@code GlobalSerialFilter}.
+ */
 @FunctionalInterface
-public interface SanctionedSerializablesService {
-
-  URL getSanctionedSerializablesURL();
-
-  default Class<?> getInterface() {
-    return getClass();
-  }
+interface GlobalSerialFilterConfigurationFactory {
 
-  default Collection<String> getSerializationAcceptlist() throws IOException {
-    return SanctionedSerializables.loadClassNames(getSanctionedSerializablesURL());
-  }
+  /**
+   * Creates a {@code FilterConfiguration} to configure a {@code GlobalSerialFilter} using the
+   * specified {@code SerializableObjectConfig}.
+   */
+  FilterConfiguration create(SerializableObjectConfig serializableObjectConfig);
 }
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilterFactory.java
similarity index 70%
copy from geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
copy to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilterFactory.java
index 1d139ce..6745831 100644
--- a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilterFactory.java
@@ -14,20 +14,17 @@
  */
 package org.apache.geode.internal.serialization.filter;
 
-import java.io.IOException;
-import java.net.URL;
 import java.util.Collection;
 
+/**
+ * Defines the factory for creating a {@code GlobalSerialFilter}.
+ */
 @FunctionalInterface
-public interface SanctionedSerializablesService {
-
-  URL getSanctionedSerializablesURL();
-
-  default Class<?> getInterface() {
-    return getClass();
-  }
+interface GlobalSerialFilterFactory {
 
-  default Collection<String> getSerializationAcceptlist() throws IOException {
-    return SanctionedSerializables.loadClassNames(getSanctionedSerializablesURL());
-  }
+  /**
+   * Creates a {@code GlobalSerialFilter} using the specified filter pattern and collection of
+   * sanctioned classes. The pattern is generated by {@code FilterPattern}.
+   */
+  GlobalSerialFilter create(String pattern, Collection<String> sanctionedClasses);
 }
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/Java9ReflectiveObjectInputFilterApi.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/Java9ReflectiveObjectInputFilterApi.java
new file mode 100644
index 0000000..53e3652
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/Java9ReflectiveObjectInputFilterApi.java
@@ -0,0 +1,64 @@
+/*
+ * 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.internal.serialization.filter;
+
+import java.io.ObjectInputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Java 9 or greater needs to override some API paths in {@code ReflectiveObjectInputFilterApi}.
+ */
+class Java9ReflectiveObjectInputFilterApi extends ReflectiveObjectInputFilterApi {
+
+  private final Class<?> ObjectInputStream;
+  private final Method ObjectInputStream_setObjectInputFilter;
+  private final Method ObjectInputStream_getObjectInputFilter;
+
+  /**
+   * Constructs instance for the specified {@code ApiPackage}.
+   */
+  Java9ReflectiveObjectInputFilterApi(ApiPackage apiPackage)
+      throws ClassNotFoundException, NoSuchMethodException {
+    super(apiPackage);
+    ObjectInputStream = ObjectInputStream();
+    ObjectInputStream_setObjectInputFilter = ObjectInputStream_setObjectInputFilter();
+    ObjectInputStream_getObjectInputFilter = ObjectInputStream_getObjectInputFilter();
+  }
+
+  private Class<?> ObjectInputStream() throws ClassNotFoundException {
+    return Class.forName(apiPackage.qualify("ObjectInputStream"));
+  }
+
+  private Method ObjectInputStream_setObjectInputFilter() throws NoSuchMethodException {
+    return ObjectInputStream.getDeclaredMethod("setObjectInputFilter", ObjectInputFilter);
+  }
+
+  private Method ObjectInputStream_getObjectInputFilter() throws NoSuchMethodException {
+    return ObjectInputStream.getDeclaredMethod("getObjectInputFilter");
+  }
+
+  @Override
+  public Object getObjectInputFilter(ObjectInputStream inputStream)
+      throws InvocationTargetException, IllegalAccessException {
+    return ObjectInputStream_getObjectInputFilter.invoke(inputStream);
+  }
+
+  @Override
+  public void setObjectInputFilter(ObjectInputStream inputStream, Object objectInputFilter)
+      throws InvocationTargetException, IllegalAccessException {
+    ObjectInputStream_setObjectInputFilter.invoke(inputStream, objectInputFilter);
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/JmxSerialFilterConfiguration.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/JmxSerialFilterConfiguration.java
new file mode 100644
index 0000000..21b0908
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/JmxSerialFilterConfiguration.java
@@ -0,0 +1,72 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.commons.lang3.StringUtils.isBlank;
+
+import java.util.function.Consumer;
+
+import org.apache.logging.log4j.Logger;
+
+import org.apache.geode.annotations.VisibleForTesting;
+import org.apache.geode.logging.internal.log4j.api.LogService;
+
+/**
+ * Implementation of {@code FilterConfiguration} for configuring the JVMs JMX serialization filter.
+ *
+ * <p>
+ * Configure the “jmx.remote.rmi.server.serial.filter.pattern” system property if Java version is
+ * Java 9 or greater. The serial pattern will be configured to accept only standard JMX open-types.
+ * If the system property already has a non-null value, then leave it as is.
+ *
+ * <p>
+ * Configure the {@code jdk.serialFilter} system property if Java version is Java 8. The serial
+ * pattern will be configured to accept only geode sanctioned serializables and standard JMX
+ * open-types. If the system property already has a non-null value, then leave it as is.
+ */
+class JmxSerialFilterConfiguration implements FilterConfiguration {
+
+  private static final Logger LOGGER = LogService.getLogger();
+
+  private final String key;
+  private final String value;
+  private final Consumer<String> logger;
+
+  /**
+   * Constructs instance for the specified system property and filter pattern.
+   */
+  JmxSerialFilterConfiguration(String property, String pattern) {
+    this(property, pattern, LOGGER::info);
+  }
+
+  @VisibleForTesting
+  JmxSerialFilterConfiguration(String property, String pattern, Consumer<String> logger) {
+    key = property;
+    value = pattern;
+    this.logger = logger;
+  }
+
+  @Override
+  public boolean configure() {
+    if (isBlank(System.getProperty(key))) {
+      System.setProperty(key, value);
+      logger.accept("System property '" + key + "' is now configured with '" + value + "'.");
+      return true;
+    }
+
+    logger.accept("System property '" + key + "' is already configured.");
+    return false;
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/JmxSerialFilterConfigurationFactory.java
similarity index 70%
copy from geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java
copy to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/JmxSerialFilterConfigurationFactory.java
index 4a187e0..8062e0d 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/JmxSerialFilterConfigurationFactory.java
@@ -12,10 +12,17 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.management.internal;
+package org.apache.geode.internal.serialization.filter;
 
+/**
+ * Defines the factory for creating a {@code FilterConfiguration} to configure a
+ * {@code JmxSerialFilter}.
+ */
 @FunctionalInterface
-public interface JmxRmiSerialFilter {
+interface JmxSerialFilterConfigurationFactory {
 
-  void configureSerialFilter();
+  /**
+   * Creates a {@code FilterConfiguration} to configure a {@code JmxSerialFilter}.
+   */
+  FilterConfiguration create();
 }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/EmptyInputStreamFilter.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/NullObjectInputFilter.java
similarity index 77%
rename from geode-core/src/main/java/org/apache/geode/internal/EmptyInputStreamFilter.java
rename to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/NullObjectInputFilter.java
index 82369a0..e25c073 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/EmptyInputStreamFilter.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/NullObjectInputFilter.java
@@ -12,13 +12,17 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.internal;
+package org.apache.geode.internal.serialization.filter;
 
 import java.io.ObjectInputStream;
 
-public class EmptyInputStreamFilter implements InputStreamFilter {
+/**
+ * Implementation of {@code ObjectInputFilter} that does nothing.
+ */
+public class NullObjectInputFilter implements ObjectInputFilter {
+
   @Override
-  public void setFilterOn(ObjectInputStream ois) {
+  public void setFilterOn(ObjectInputStream objectInputStream) {
     // Do nothing, this is the case where we don't filter.
   }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/InputStreamFilter.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilter.java
similarity index 69%
copy from geode-core/src/main/java/org/apache/geode/internal/InputStreamFilter.java
copy to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilter.java
index 43b43e6..091f8f6 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/InputStreamFilter.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilter.java
@@ -12,13 +12,18 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.internal;
+package org.apache.geode.internal.serialization.filter;
 
 import java.io.ObjectInputStream;
 
-public interface InputStreamFilter {
-
-  /** establish a serialization filter on the given stream */
-  void setFilterOn(ObjectInputStream ois);
+/**
+ * Defines operation to set this serialization filter on an {@code ObjectInputStream}.
+ */
+@FunctionalInterface
+public interface ObjectInputFilter {
 
+  /**
+   * Sets this serialization filter on the specified {@code ObjectInputStream}.
+   */
+  void setFilterOn(ObjectInputStream objectInputStream);
 }
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterApi.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterApi.java
new file mode 100644
index 0000000..976c748
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterApi.java
@@ -0,0 +1,85 @@
+/*
+ * 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.internal.serialization.filter;
+
+import java.io.ObjectInputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+
+/**
+ * Adapter interface that defines a layer of indirection for JDK's {@code ObjectInputFilter} class.
+ *
+ * <p>
+ * {@code ObjectInputFilter} is absent in builds of Java 8 prior to 121. It is present in later
+ * builds of Java 8 as {@code sun.misc.ObjectInputFilter} and underwent changes in Java 9 including
+ * being moved to {@code java.io.ObjectInputFilter}.
+ */
+interface ObjectInputFilterApi {
+
+  /**
+   * Get the filter for classes being deserialized on the ObjectInputStream.
+   *
+   * <p>
+   * Invokes {@code getObjectInputFilter} on {@code ObjectInputFilter$Config}.
+   */
+  Object getObjectInputFilter(ObjectInputStream inputStream)
+      throws InvocationTargetException, IllegalAccessException;
+
+  /**
+   * Set the process-wide filter if it has not already been configured or set.
+   *
+   * <p>
+   * Invokes {@code setObjectInputFilter} on {@code ObjectInputFilter$Config}
+   */
+  void setObjectInputFilter(ObjectInputStream inputStream, Object objectInputFilter)
+      throws InvocationTargetException, IllegalAccessException;
+
+  /**
+   * Returns the process-wide serialization filter or null if not configured.
+   *
+   * <p>
+   * Invokes {@code getSerialFilter} on {@code ObjectInputFilter$Config}
+   */
+  Object getSerialFilter()
+      throws InvocationTargetException, IllegalAccessException;
+
+  /**
+   * Set the process-wide filter if it has not already been configured or set.
+   *
+   * <p>
+   * Invokes {@code setSerialFilter} on {@code ObjectInputFilter$Config}
+   */
+  void setSerialFilter(Object objectInputFilter)
+      throws InvocationTargetException, IllegalAccessException;
+
+  /**
+   * Returns an ObjectInputFilter from a string of patterns. The syntax is defined in the javadocs
+   * of the JREs {@code (sun.misc. or java.io.)ObjectInputFilter$Config}.
+   *
+   * <p>
+   * Invokes {@code createFilter} on {@code ObjectInputFilter$Config}.
+   */
+  Object createFilter(String pattern)
+      throws InvocationTargetException, IllegalAccessException;
+
+  /**
+   * Returns a Java Proxy with InvocationHandler to allow Geode to inject custom behavior before
+   * invoking the methods on the JREs {@code (sun.misc. or java.io.)ObjectInputFilter$Config}.
+   */
+  Object createObjectInputFilterProxy(String pattern, Collection<String> sanctionedClasses)
+      throws InvocationTargetException, IllegalAccessException;
+
+  Class<?> getObjectInputFilterClass();
+}
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterApiFactory.java
similarity index 78%
rename from geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java
rename to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterApiFactory.java
index 4a187e0..11957dd 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/JmxRmiSerialFilter.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterApiFactory.java
@@ -12,10 +12,13 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.management.internal;
+package org.apache.geode.internal.serialization.filter;
 
+/**
+ * Creates an instance of {@code ObjectInputFilterApi}.
+ */
 @FunctionalInterface
-public interface JmxRmiSerialFilter {
+public interface ObjectInputFilterApiFactory {
 
-  void configureSerialFilter();
+  ObjectInputFilterApi createObjectInputFilterApi();
 }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/InputStreamFilter.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterFactory.java
similarity index 75%
rename from geode-core/src/main/java/org/apache/geode/internal/InputStreamFilter.java
rename to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterFactory.java
index 43b43e6..c5fe4bf 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/InputStreamFilter.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterFactory.java
@@ -12,13 +12,13 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.internal;
+package org.apache.geode.internal.serialization.filter;
 
-import java.io.ObjectInputStream;
+import java.util.Set;
 
-public interface InputStreamFilter {
-
-  /** establish a serialization filter on the given stream */
-  void setFilterOn(ObjectInputStream ois);
+@FunctionalInterface
+public interface ObjectInputFilterFactory {
 
+  ObjectInputFilter create(SerializableObjectConfig serializableObjectConfig,
+      Set<String> sanctionedClasses);
 }
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterUtils.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterUtils.java
new file mode 100644
index 0000000..6a44f49
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterUtils.java
@@ -0,0 +1,61 @@
+/*
+ * 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.internal.serialization.filter;
+
+import java.util.function.Function;
+
+import org.apache.geode.annotations.VisibleForTesting;
+import org.apache.geode.internal.lang.utils.ClassUtils;
+
+/**
+ * Utility methods for {@code ObjectInputFilter}.
+ */
+public class ObjectInputFilterUtils {
+
+  private static final String UNSUPPORTED_MESSAGE = "ObjectInputFilter is not available.";
+
+  /**
+   * Returns true if this JVM supports {@code ObjectInputFilter}.
+   */
+  public static boolean supportsObjectInputFilter() {
+    return supportsObjectInputFilter(ClassUtils::isClassAvailable);
+  }
+
+  static void throwUnsupportedOperationException() {
+    throw new UnsupportedOperationException(UNSUPPORTED_MESSAGE, null);
+  }
+
+  static void throwUnsupportedOperationException(Throwable cause) {
+    throw new UnsupportedOperationException(UNSUPPORTED_MESSAGE, cause);
+  }
+
+  static void throwUnsupportedOperationException(String message) {
+    throw new UnsupportedOperationException(message, null);
+  }
+
+  static void throwUnsupportedOperationException(String message, Throwable cause) {
+    throw new UnsupportedOperationException(message, cause);
+  }
+
+  @VisibleForTesting
+  static boolean supportsObjectInputFilter(Function<String, Boolean> isClassAvailable) {
+    return isClassAvailable.apply("sun.misc.ObjectInputFilter") ||
+        isClassAvailable.apply("java.io.ObjectInputFilter");
+  }
+
+  private ObjectInputFilterUtils() {
+    // do not instantiate
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/OpenMBeanFilterPattern.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/OpenMBeanFilterPattern.java
new file mode 100644
index 0000000..14aa037
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/OpenMBeanFilterPattern.java
@@ -0,0 +1,68 @@
+/*
+ * 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.internal.serialization.filter;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.rmi.MarshalledObject;
+import java.util.StringJoiner;
+
+import javax.management.ObjectName;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularType;
+
+/**
+ * Defines a serial filter pattern that accepts all open MBean data types and rejects everything
+ * not included in the pattern.
+ */
+public class OpenMBeanFilterPattern implements FilterPattern {
+
+  @Override
+  public String pattern() {
+    // note: java.util.* may also be needed
+    return new StringJoiner(";")
+
+        // accept all open MBean data types
+        .add(Boolean.class.getName())
+        .add(Byte.class.getName())
+        .add(Character.class.getName())
+        .add(Short.class.getName())
+        .add(Integer.class.getName())
+        .add(Long.class.getName())
+        .add(Float.class.getName())
+        .add(Double.class.getName())
+        .add(String.class.getName())
+        .add(BigInteger.class.getName())
+        .add(BigDecimal.class.getName())
+        .add(ObjectName.class.getName())
+        .add(OpenType.class.getName())
+        .add(CompositeData.class.getName())
+        .add(TabularData.class.getName())
+        .add(SimpleType.class.getName())
+        .add(CompositeType.class.getName())
+        .add(TabularType.class.getName())
+        .add(ArrayType.class.getName())
+        .add(MarshalledObject.class.getName())
+
+        // reject all others
+        .add("!*")
+        .toString();
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeGlobalSerialFilter.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeGlobalSerialFilter.java
new file mode 100644
index 0000000..4a1152d
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeGlobalSerialFilter.java
@@ -0,0 +1,71 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.util.Collections.unmodifiableCollection;
+import static org.apache.geode.internal.serialization.filter.ObjectInputFilterUtils.throwUnsupportedOperationException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+
+/**
+ * Implementation of {@code GlobalSerialFilter} that delegates to {@code ObjectInputFilterApi} to
+ * maintain independence from the JRE version.
+ */
+class ReflectiveFacadeGlobalSerialFilter implements GlobalSerialFilter {
+
+  private final ObjectInputFilterApi api;
+  private final String pattern;
+  private final Collection<String> sanctionedClasses;
+
+  /**
+   * Constructs instance with the specified collaborators.
+   */
+  ReflectiveFacadeGlobalSerialFilter(ObjectInputFilterApi api, String pattern,
+      Collection<String> sanctionedClasses) {
+    this.api = api;
+    this.pattern = pattern;
+    this.sanctionedClasses = unmodifiableCollection(sanctionedClasses);
+  }
+
+  /**
+   * Invokes interface-defined operation to set this as the process-wide filter.
+   */
+  @Override
+  public void setFilter() {
+    try {
+      // create the ObjectInputFilter to set as the global serial filter
+      Object objectInputFilter = api.createObjectInputFilterProxy(pattern, sanctionedClasses);
+
+      // set the global serial filter
+      api.setSerialFilter(objectInputFilter);
+
+    } catch (IllegalAccessException | InvocationTargetException e) {
+      throwUnsupportedOperationException(
+          "Geode was unable to configure a global serialization filter",
+          e);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return new StringBuilder(getClass().getSimpleName())
+        .append('{')
+        .append("api=").append(api)
+        .append(", pattern='").append(pattern).append('\'')
+        .append('}')
+        .toString();
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeGlobalSerialFilterFactory.java
similarity index 50%
copy from geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
copy to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeGlobalSerialFilterFactory.java
index 1d139ce..022c766 100644
--- a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeGlobalSerialFilterFactory.java
@@ -14,20 +14,31 @@
  */
 package org.apache.geode.internal.serialization.filter;
 
-import java.io.IOException;
-import java.net.URL;
+import static java.util.Objects.requireNonNull;
+
 import java.util.Collection;
 
-@FunctionalInterface
-public interface SanctionedSerializablesService {
+import org.apache.geode.annotations.VisibleForTesting;
+
+/**
+ * Creates an instance of {@code GlobalSerialFilter} that delegates to {@code ObjectInputFilterApi}
+ * to maintain independence from the JRE version.
+ */
+class ReflectiveFacadeGlobalSerialFilterFactory implements GlobalSerialFilterFactory {
 
-  URL getSanctionedSerializablesURL();
+  private final ObjectInputFilterApi api;
+
+  ReflectiveFacadeGlobalSerialFilterFactory() {
+    this(new ReflectiveObjectInputFilterApiFactory().createObjectInputFilterApi());
+  }
 
-  default Class<?> getInterface() {
-    return getClass();
+  @VisibleForTesting
+  ReflectiveFacadeGlobalSerialFilterFactory(ObjectInputFilterApi api) {
+    this.api = requireNonNull(api, "ObjectInputFilterApi is required");
   }
 
-  default Collection<String> getSerializationAcceptlist() throws IOException {
-    return SanctionedSerializables.loadClassNames(getSanctionedSerializablesURL());
+  @Override
+  public GlobalSerialFilter create(String pattern, Collection<String> sanctionedClasses) {
+    return new ReflectiveFacadeGlobalSerialFilter(api, pattern, sanctionedClasses);
   }
 }
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeObjectInputFilter.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeObjectInputFilter.java
new file mode 100644
index 0000000..08bae44
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeObjectInputFilter.java
@@ -0,0 +1,78 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.util.Collections.unmodifiableCollection;
+import static org.apache.geode.internal.serialization.filter.ObjectInputFilterUtils.throwUnsupportedOperationException;
+
+import java.io.ObjectInputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+
+/**
+ * Implementation of {@code ObjectInputFilter} that delegates to {@code ObjectInputFilterApi} to
+ * maintain independence from the JRE version.
+ */
+class ReflectiveFacadeObjectInputFilter implements ObjectInputFilter {
+
+  private final ObjectInputFilterApi api;
+  private final String pattern;
+  private final Collection<String> sanctionedClasses;
+
+  /**
+   * Constructs instance with the specified collaborators.
+   */
+  ReflectiveFacadeObjectInputFilter(ObjectInputFilterApi api, String pattern,
+      Collection<String> sanctionedClasses) {
+    this.pattern = pattern;
+    this.sanctionedClasses = unmodifiableCollection(sanctionedClasses);
+    this.api = api;
+  }
+
+  /**
+   * Invokes interface-defined operation to set this serialization filter on the specified target
+   * {@code ObjectInputStream}.
+   */
+  @Override
+  public void setFilterOn(ObjectInputStream objectInputStream) {
+    try {
+      // create the ObjectInputFilter to set as the global serial filter
+      Object objectInputFilter = api.createObjectInputFilterProxy(pattern, sanctionedClasses);
+
+      // set the global serial filter
+      api.setObjectInputFilter(objectInputStream, objectInputFilter);
+
+    } catch (IllegalAccessException | InvocationTargetException e) {
+      throwUnsupportedOperationException(
+          "Geode was unable to configure a serialization filter on input stream '"
+              + objectInputStream.hashCode() + "'",
+          e);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return new StringBuilder(getClass().getSimpleName())
+        .append('{')
+        .append("api=").append(api)
+        .append(", pattern='").append(pattern).append('\'')
+        .append('}')
+        .toString();
+  }
+
+  ObjectInputFilterApi getObjectInputFilterApi() {
+    return api;
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeObjectInputFilterFactory.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeObjectInputFilterFactory.java
new file mode 100644
index 0000000..a38416c
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeObjectInputFilterFactory.java
@@ -0,0 +1,61 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.geode.internal.serialization.filter.ObjectInputFilterUtils.supportsObjectInputFilter;
+import static org.apache.geode.internal.serialization.filter.ObjectInputFilterUtils.throwUnsupportedOperationException;
+
+import java.util.Set;
+
+/**
+ * Creates an instance of {@code ObjectInputFilter} that delegates to {@code ObjectInputFilterApi}
+ * to maintain independence from the JRE version.
+ */
+public class ReflectiveFacadeObjectInputFilterFactory implements ObjectInputFilterFactory {
+
+  private static final String UNSUPPORTED_MESSAGE =
+      "A serialization filter has been specified but this version of Java does not support serialization filters - ObjectInputFilter is not available";
+
+  private final ObjectInputFilterApi api;
+
+  public ReflectiveFacadeObjectInputFilterFactory() {
+    this(new ReflectiveObjectInputFilterApiFactory().createObjectInputFilterApi());
+  }
+
+  private ReflectiveFacadeObjectInputFilterFactory(ObjectInputFilterApi api) {
+    this.api = requireNonNull(api, "ObjectInputFilterApi is required");
+  }
+
+  @Override
+  public ObjectInputFilter create(SerializableObjectConfig config, Set<String> sanctionedClasses) {
+    if (config.getValidateSerializableObjects()) {
+      requireObjectInputFilter();
+
+      String pattern = new SanctionedSerializablesFilterPattern()
+          .append(config.getSerializableObjectFilter())
+          .pattern();
+
+      return new ReflectiveFacadeObjectInputFilter(api, pattern, sanctionedClasses);
+    }
+    return new NullObjectInputFilter();
+  }
+
+  public static void requireObjectInputFilter() {
+    if (!supportsObjectInputFilter()) {
+      throwUnsupportedOperationException(UNSUPPORTED_MESSAGE);
+    }
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApi.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApi.java
new file mode 100644
index 0000000..10c3aad
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApi.java
@@ -0,0 +1,269 @@
+/*
+ * 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.internal.serialization.filter;
+
+import java.io.InvalidClassException;
+import java.io.ObjectInputStream;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collection;
+
+import org.apache.logging.log4j.Logger;
+
+import org.apache.geode.annotations.VisibleForTesting;
+import org.apache.geode.logging.internal.log4j.api.LogService;
+
+/**
+ * Implementation of {@code ObjectInputFilterApi} that uses reflection and a dynamic proxy to wrap
+ * the JREs ObjectInputFilter API in both Java 8 and Java 9 or greater.
+ */
+public class ReflectiveObjectInputFilterApi implements ObjectInputFilterApi {
+
+  private static final Logger logger = LogService.getLogger();
+
+  protected final ApiPackage apiPackage;
+
+  // api.package.ObjectInputFilter
+  protected final Class<?> ObjectInputFilter;
+  private final Method ObjectInputFilter_checkInput;
+  private final Object ObjectInputFilter_Status_ALLOWED;
+  private final Object ObjectInputFilter_Status_REJECTED;
+
+  // api.package.ObjectInputFilter$Config
+  private final Class<?> ObjectInputFilter_Config;
+  private final Method ObjectInputFilter_Config_createFilter;
+  private final Method ObjectInputFilter_Config_getObjectInputFilter;
+  private final Method ObjectInputFilter_Config_setObjectInputFilter;
+  private final Method ObjectInputFilter_Config_getSerialFilter;
+  private final Method ObjectInputFilter_Config_setSerialFilter;
+
+  // api.package.ObjectInputFilter$FilterInfo
+  private final Class<?> ObjectInputFilter_FilterInfo;
+  private final Method ObjectInputFilter_FilterInfo_serialClass;
+
+  /**
+   * Use reflection to look up the classes and methods for the API.
+   */
+  public ReflectiveObjectInputFilterApi(ApiPackage apiPackage)
+      throws ClassNotFoundException, NoSuchMethodException {
+    this.apiPackage = apiPackage;
+
+    ObjectInputFilter = ObjectInputFilter();
+    ObjectInputFilter_Config = ObjectInputFilter_Config();
+    ObjectInputFilter_FilterInfo = ObjectInputFilter_FilterInfo();
+    Class<?> ObjectInputFilter_Status = ObjectInputFilter_Status();
+    ObjectInputFilter_Status_ALLOWED = ObjectInputFilter_Status.getEnumConstants()[1];
+    ObjectInputFilter_Status_REJECTED = ObjectInputFilter_Status.getEnumConstants()[2];
+
+    // Status enum includes UNDECIDED, ALLOWED, and REJECTED
+    if (!ObjectInputFilter_Status_ALLOWED.toString().equals("ALLOWED")
+        || !ObjectInputFilter_Status_REJECTED.toString().equals("REJECTED")) {
+      throw new UnsupportedOperationException(
+          "ObjectInputFilter$Status enumeration in this JDK is not as expected");
+    }
+
+    ObjectInputFilter_checkInput = ObjectInputFilter_checkInput();
+
+    ObjectInputFilter_Config_createFilter = ObjectInputFilter_Config_createFilter();
+    if (apiPackage == ApiPackage.SUN_MISC) {
+      ObjectInputFilter_Config_getObjectInputFilter =
+          ObjectInputFilter_Config_getObjectInputFilter();
+      ObjectInputFilter_Config_setObjectInputFilter =
+          ObjectInputFilter_Config_setObjectInputFilter();
+    } else {
+      ObjectInputFilter_Config_getObjectInputFilter = null;
+      ObjectInputFilter_Config_setObjectInputFilter = null;
+    }
+    ObjectInputFilter_Config_getSerialFilter = ObjectInputFilter_Config_getSerialFilter();
+    ObjectInputFilter_Config_setSerialFilter = ObjectInputFilter_Config_setSerialFilter();
+
+    ObjectInputFilter_FilterInfo_serialClass = ObjectInputFilter_FilterInfo_serialClass();
+  }
+
+  @Override
+  public Object getObjectInputFilter(ObjectInputStream inputStream)
+      throws InvocationTargetException, IllegalAccessException {
+    return ObjectInputFilter_Config_getObjectInputFilter.invoke(ObjectInputFilter_Config,
+        inputStream);
+  }
+
+  @Override
+  public void setObjectInputFilter(ObjectInputStream inputStream, Object objectInputFilter)
+      throws InvocationTargetException, IllegalAccessException {
+    ObjectInputFilter_Config_setObjectInputFilter.invoke(ObjectInputFilter_Config, inputStream,
+        objectInputFilter);
+  }
+
+  @Override
+  public Object getSerialFilter()
+      throws InvocationTargetException, IllegalAccessException {
+    return ObjectInputFilter_Config_getSerialFilter.invoke(ObjectInputFilter_Config);
+  }
+
+  @Override
+  public void setSerialFilter(Object objectInputFilter)
+      throws InvocationTargetException, IllegalAccessException {
+    ObjectInputFilter_Config_setSerialFilter.invoke(ObjectInputFilter_Config, objectInputFilter);
+  }
+
+  @Override
+  public Object createFilter(String pattern)
+      throws InvocationTargetException, IllegalAccessException {
+    return ObjectInputFilter_Config_createFilter.invoke(null, pattern);
+  }
+
+  @Override
+  public Object createObjectInputFilterProxy(String pattern, Collection<String> sanctionedClasses)
+      throws InvocationTargetException, IllegalAccessException {
+    Object objectInputFilter =
+        ObjectInputFilter_Config_createFilter.invoke(ObjectInputFilter_Config, pattern);
+
+    /*
+     * Members first connect to each other with sockets that are restricted to the Geode sanctioned
+     * serializables which sets the Filter on that stream (an ObjectInputStream can only set a
+     * Filter once in its lifetime). Geode then loads up its ConfigurationProperties and needs
+     * to widen the Filter to include the User's sanctioned serializables if they provided any
+     * (serializable-object-filter).
+     *
+     * Wrap the Filter in a dynamic Proxy to provide an InvocationHandler in order to decorate
+     * before-calls to checkInput (checks the class against the filter). This allows us to
+     * effectively override the call to widen the filter to include the User's addition to the set
+     * of sanctioned serializables.
+     */
+    InvocationHandler invocationHandler = (proxy, method, args) -> {
+      if (!"checkInput".equals(method.getName())) {
+        throw new UnsupportedOperationException(
+            "ObjectInputFilter." + method.getName() + " is not implemented");
+      }
+
+      // fetch the class of the serialized instance
+      Object objectInputFilter_filterInfo = args[0];
+      Class<?> serialClass =
+          (Class<?>) ObjectInputFilter_FilterInfo_serialClass.invoke(objectInputFilter_filterInfo);
+      if (serialClass == null) { // no class to check, so nothing to accept-list
+        return ObjectInputFilter_checkInput.invoke(objectInputFilter, objectInputFilter_filterInfo);
+      }
+
+      // check sanctionedClasses to determine if the name of the class is ALLOWED
+      String serialClassName = serialClass.getName();
+      if (serialClass.isArray()) {
+        serialClassName = serialClass.getComponentType().getName();
+      }
+      if (sanctionedClasses.contains(serialClassName)) {
+        return ObjectInputFilter_Status_ALLOWED;
+      }
+
+      // check the filter to determine if the class is ALLOWED
+      Object objectInputFilter_Status =
+          ObjectInputFilter_checkInput.invoke(objectInputFilter, objectInputFilter_filterInfo);
+      if (objectInputFilter_Status == ObjectInputFilter_Status_REJECTED) {
+        logger.fatal("Serialization filter is rejecting class {}", serialClassName,
+            new InvalidClassException(serialClassName));
+      }
+      return objectInputFilter_Status;
+    };
+
+    // wrap the filter within a proxy to inject the above invocation handler
+    return Proxy.newProxyInstance(
+        ObjectInputFilter.getClassLoader(),
+        new Class[] {ObjectInputFilter},
+        invocationHandler);
+  }
+
+  @Override
+  public String toString() {
+    final StringBuilder sb = new StringBuilder("ReflectiveObjectInputFilterApi{");
+    sb.append("apiPackage='").append(apiPackage).append('\'');
+    sb.append('}');
+    return sb.toString();
+  }
+
+  @Override
+  public Class<?> getObjectInputFilterClass() {
+    return ObjectInputFilter;
+  }
+
+  @VisibleForTesting
+  public ApiPackage getApiPackage() {
+    return apiPackage;
+  }
+
+  /** {@code public interface ObjectInputFilter} */
+  private Class<?> ObjectInputFilter() throws ClassNotFoundException {
+    return Class.forName(apiPackage.qualify("ObjectInputFilter"));
+  }
+
+  /** {@code public Status checkInput(FilterInfo filterInfo)} */
+  private Method ObjectInputFilter_checkInput()
+      throws NoSuchMethodException {
+    return ObjectInputFilter
+        .getDeclaredMethod("checkInput", ObjectInputFilter_FilterInfo);
+  }
+
+  /** {@code public enum Status} */
+  private Class<?> ObjectInputFilter_Status() throws ClassNotFoundException {
+    return Class.forName(apiPackage.qualify("ObjectInputFilter$Status"));
+  }
+
+  /** {@code public final class Config} */
+  private Class<?> ObjectInputFilter_Config() throws ClassNotFoundException {
+    return Class.forName(apiPackage.qualify("ObjectInputFilter$Config"));
+  }
+
+  /** {@code public static ObjectInputFilter createFilter(String pattern)} */
+  private Method ObjectInputFilter_Config_createFilter()
+      throws NoSuchMethodException {
+    return ObjectInputFilter_Config.getDeclaredMethod("createFilter", String.class);
+  }
+
+  /** {@code public static ObjectInputFilter getSerialFilter()} */
+  private Method ObjectInputFilter_Config_getObjectInputFilter()
+      throws NoSuchMethodException {
+    return ObjectInputFilter_Config.getDeclaredMethod("getObjectInputFilter",
+        ObjectInputStream.class);
+  }
+
+  /** {@code public static setSerialFilter(ObjectInputFilter filter)} */
+  private Method ObjectInputFilter_Config_setObjectInputFilter()
+      throws NoSuchMethodException {
+    return ObjectInputFilter_Config.getDeclaredMethod("setObjectInputFilter",
+        ObjectInputStream.class, ObjectInputFilter);
+  }
+
+  /** {@code public static ObjectInputFilter getSerialFilter()} */
+  private Method ObjectInputFilter_Config_getSerialFilter()
+      throws NoSuchMethodException {
+    return ObjectInputFilter_Config.getDeclaredMethod("getSerialFilter");
+  }
+
+  /** {@code public static setSerialFilter(ObjectInputFilter filter)} */
+  private Method ObjectInputFilter_Config_setSerialFilter()
+      throws NoSuchMethodException {
+    return ObjectInputFilter_Config.getDeclaredMethod("setSerialFilter", ObjectInputFilter);
+  }
+
+  /** {@code public interface FilterInfo} */
+  private Class<?> ObjectInputFilter_FilterInfo() throws ClassNotFoundException {
+    return Class.forName(apiPackage.qualify("ObjectInputFilter$FilterInfo"));
+  }
+
+  /** {@code public Class<?> serialClass()} */
+  private Method ObjectInputFilter_FilterInfo_serialClass()
+      throws NoSuchMethodException {
+    return ObjectInputFilter_FilterInfo.getDeclaredMethod("serialClass");
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiFactory.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiFactory.java
new file mode 100644
index 0000000..d50c15d
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiFactory.java
@@ -0,0 +1,45 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.geode.internal.serialization.filter.ApiPackage.JAVA_IO;
+import static org.apache.geode.internal.serialization.filter.ApiPackage.SUN_MISC;
+import static org.apache.geode.internal.serialization.filter.ObjectInputFilterUtils.throwUnsupportedOperationException;
+
+/**
+ * Implementation of {@code ObjectInputFilterApiFactory} that creates a reflection based
+ * {@code ObjectInputFilterApi}.
+ */
+public class ReflectiveObjectInputFilterApiFactory implements ObjectInputFilterApiFactory {
+
+  @Override
+  public ObjectInputFilterApi createObjectInputFilterApi() {
+    try {
+      if (isJavaVersionAtLeast(JAVA_9)) {
+        return new Java9ReflectiveObjectInputFilterApi(JAVA_IO);
+      }
+      if (isJavaVersionAtLeast(JAVA_1_8)) {
+        return new ReflectiveObjectInputFilterApi(SUN_MISC);
+      }
+    } catch (ClassNotFoundException | NoSuchMethodException e) {
+      throwUnsupportedOperationException(e);
+    }
+    throwUnsupportedOperationException();
+    return null; // unreachable
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializables.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializables.java
index a493c58..6c68010 100644
--- a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializables.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializables.java
@@ -32,14 +32,13 @@ import org.apache.logging.log4j.Logger;
 
 import org.apache.geode.logging.internal.log4j.api.LogService;
 
+/**
+ * Loads {@code SanctionedSerializablesService}s and sanctioned class data.
+ */
 public class SanctionedSerializables {
 
   private static final Logger logger = LogService.getLogger();
 
-  private SanctionedSerializables() {
-    // do not instantiate
-  }
-
   /**
    * Loads all SanctionedSerializablesServices on the classpath.
    */
@@ -57,7 +56,7 @@ public class SanctionedSerializables {
    * Loads class names of sanctioned serializables from a resource. Caller will add these to the
    * serialization filter acceptlist.
    */
-  public static Collection<String> loadClassNames(URL sanctionedSerializables) throws IOException {
+  static Collection<String> loadClassNames(URL sanctionedSerializables) throws IOException {
     if (sanctionedSerializables == null) {
       return emptyList();
     }
@@ -93,4 +92,8 @@ public class SanctionedSerializables {
     }
     return sanctionedClasses;
   }
+
+  private SanctionedSerializables() {
+    // do not instantiate
+  }
 }
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesFilterPattern.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesFilterPattern.java
new file mode 100644
index 0000000..b40ef0e
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesFilterPattern.java
@@ -0,0 +1,111 @@
+/*
+ * 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.internal.serialization.filter;
+
+import java.util.StringJoiner;
+
+/**
+ * This list contains classes that Geode's classes subclass, such as antlr AST classes which are
+ * used by our Object Query Language. It also contains certain classes that are DataSerializable
+ * but end up being serialized as part of other serializable objects. VersionedObjectList, for
+ * instance, is serialized as part of a partial putAll exception object.
+ *
+ * <p>
+ * Do not java-serialize objects that Geode does not have complete control over. This leaves us
+ * open to security attacks such as Gadget Chains and compromises the ability to do a rolling
+ * upgrade from one version of Geode to the next.
+ *
+ * <p>
+ * In general, you shouldn't use java serialization, and you should implement
+ * DataSerializableFixedID for internal Geode objects. This gives you better control over
+ * backward-compatibility.
+ *
+ * <p>
+ * Do not add to this list unless absolutely necessary. Instead, put your classes either in the
+ * sanctionedSerializables file for your module or in its excludedClasses file. Run
+ * AnalyzeSerializables to generate the content for the file.
+ */
+public class SanctionedSerializablesFilterPattern implements FilterPattern {
+
+  private final StringJoiner stringJoiner = dependenciesPattern();
+
+  @Override
+  public String pattern() {
+    return new StringJoiner(";")
+        .merge(stringJoiner)
+
+        // reject all others
+        .add("!*")
+
+        .toString();
+  }
+
+  /**
+   * Used to append the user specified serializable-object-filter
+   */
+  public SanctionedSerializablesFilterPattern append(CharSequence chars) {
+    stringJoiner.add(chars);
+    return this;
+  }
+
+  private static StringJoiner dependenciesPattern() {
+    return new StringJoiner(";")
+
+        // accept all open MBean data types
+        .add("java.**")
+        .add("javax.management.**")
+
+        // used for some old enums
+        .add("javax.print.attribute.EnumSyntax")
+
+        // query AST objects
+        .add("antlr.**")
+
+        // old Admin API
+        .add("org.apache.commons.modeler.AttributeInfo")
+        .add("org.apache.commons.modeler.FeatureInfo")
+        .add("org.apache.commons.modeler.ManagedBean")
+        .add("org.apache.geode.distributed.internal.DistributionConfigSnapshot")
+        .add("org.apache.geode.distributed.internal.RuntimeDistributionConfigImpl")
+        .add("org.apache.geode.distributed.internal.DistributionConfigImpl")
+
+        // WindowedExportFunction, RegionSnapshotService
+        .add("org.apache.geode.distributed.internal.membership.InternalDistributedMember")
+
+        // putAll
+        .add("org.apache.geode.internal.cache.persistence.PersistentMemberID")
+        .add("org.apache.geode.internal.cache.persistence.DiskStoreID")
+        .add("org.apache.geode.internal.cache.tier.sockets.VersionedObjectList")
+
+        // security services
+        .add("org.apache.shiro.**")
+
+        // export logs
+        .add("org.apache.logging.log4j.Level")
+        .add("org.apache.logging.log4j.spi.StandardLevel")
+
+        // jar deployment
+        .add("com.sun.proxy.$Proxy*")
+        .add("com.healthmarketscience.rmiio.RemoteInputStream")
+        .add("javax.rmi.ssl.SslRMIClientSocketFactory")
+        .add("javax.net.ssl.SSLHandshakeException")
+        .add("javax.net.ssl.SSLException")
+        .add("sun.security.validator.ValidatorException")
+        .add("sun.security.provider.certpath.SunCertPathBuilderException")
+
+        // geode-modules
+        .add("org.apache.geode.modules.util.SessionCustomExpiry");
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
index 1d139ce..9306934 100644
--- a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
@@ -18,9 +18,15 @@ import java.io.IOException;
 import java.net.URL;
 import java.util.Collection;
 
+/**
+ * Service interface to define a module's sanctioned serializables.
+ */
 @FunctionalInterface
 public interface SanctionedSerializablesService {
 
+  /**
+   * Returns a URL for loading a module's collection of sanctioned serializables.
+   */
   URL getSanctionedSerializablesURL();
 
   default Class<?> getInterface() {
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SerializableObjectConfig.java
similarity index 67%
copy from geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
copy to geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SerializableObjectConfig.java
index 1d139ce..40ab638 100644
--- a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SerializableObjectConfig.java
@@ -14,20 +14,18 @@
  */
 package org.apache.geode.internal.serialization.filter;
 
-import java.io.IOException;
-import java.net.URL;
-import java.util.Collection;
+/**
+ * Defines the configuration properties used by serialization filter in Geode.
+ */
+public interface SerializableObjectConfig {
 
-@FunctionalInterface
-public interface SanctionedSerializablesService {
+  default String getSerializableObjectFilterIfEnabled() {
+    return getValidateSerializableObjects() ? getSerializableObjectFilter() : null;
+  }
 
-  URL getSanctionedSerializablesURL();
+  boolean getValidateSerializableObjects();
 
-  default Class<?> getInterface() {
-    return getClass();
-  }
+  void setValidateSerializableObjects(boolean value);
 
-  default Collection<String> getSerializationAcceptlist() throws IOException {
-    return SanctionedSerializables.loadClassNames(getSanctionedSerializablesURL());
-  }
+  String getSerializableObjectFilter();
 }
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SystemPropertyGlobalSerialFilterConfigurationFactory.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SystemPropertyGlobalSerialFilterConfigurationFactory.java
new file mode 100644
index 0000000..4c592d3
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SystemPropertyGlobalSerialFilterConfigurationFactory.java
@@ -0,0 +1,52 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.commons.lang3.StringUtils.isBlank;
+
+import org.apache.geode.internal.lang.SystemProperty;
+
+/**
+ * Creates an instance of {@code GlobalSerialFilterConfiguration} that is enabled only if certain
+ * conditions are met. The system property {@code jdk.serialFilter} must be blank, and the system
+ * property {@code geode.enableGlobalSerialFilter} must be set to true.
+ */
+public class SystemPropertyGlobalSerialFilterConfigurationFactory
+    implements GlobalSerialFilterConfigurationFactory {
+
+  private final boolean enabled;
+
+  public SystemPropertyGlobalSerialFilterConfigurationFactory() {
+    // enable GlobalSerialFilter only under these conditions:
+    // (1) jdk.serialFilter must be blank
+    // (2) geode.enableGlobalSerialFilter must be set "true"
+    this(isBlank(System.getProperty("jdk.serialFilter")) &&
+        SystemProperty
+            .getProductBooleanProperty("enableGlobalSerialFilter")
+            .orElse(false));
+  }
+
+  private SystemPropertyGlobalSerialFilterConfigurationFactory(boolean enabled) {
+    this.enabled = enabled;
+  }
+
+  @Override
+  public FilterConfiguration create(SerializableObjectConfig serializableObjectConfig) {
+    if (enabled) {
+      return new GlobalSerialFilterConfiguration(serializableObjectConfig);
+    }
+    return () -> false;
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SystemPropertyJmxSerialFilterConfigurationFactory.java b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SystemPropertyJmxSerialFilterConfigurationFactory.java
new file mode 100644
index 0000000..6f9fbd7
--- /dev/null
+++ b/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SystemPropertyJmxSerialFilterConfigurationFactory.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+
+import org.apache.geode.annotations.VisibleForTesting;
+
+/**
+ * Creates an instance of {@code JmxSerialFilterConfiguration} that is enabled only if certain
+ * conditions are met. The system property {@code jmx.remote.rmi.server.serial.filter.pattern} must
+ * be blank, and the JRE must be Java 9 or greater.
+ */
+public class SystemPropertyJmxSerialFilterConfigurationFactory
+    implements JmxSerialFilterConfigurationFactory {
+
+  private static final String PROPERTY_NAME = "jmx.remote.rmi.server.serial.filter.pattern";
+
+  private final boolean enabled;
+  private final String pattern;
+
+  public SystemPropertyJmxSerialFilterConfigurationFactory() {
+    // JmxSerialFilter requires Java 9 or greater
+    this(isJavaVersionAtLeast(JAVA_9) && isBlank(System.getProperty(PROPERTY_NAME)),
+        new OpenMBeanFilterPattern().pattern());
+  }
+
+  @VisibleForTesting
+  SystemPropertyJmxSerialFilterConfigurationFactory(boolean enabled, String pattern) {
+    this.enabled = enabled;
+    this.pattern = pattern;
+  }
+
+  @Override
+  public FilterConfiguration create() {
+    if (enabled) {
+      return new JmxSerialFilterConfiguration(PROPERTY_NAME, pattern);
+    }
+    return () -> false;
+  }
+}
diff --git a/geode-serialization/src/main/resources/org/apache/geode/internal/serialization/sanctioned-geode-serialization-serializables.txt b/geode-serialization/src/main/resources/org/apache/geode/internal/serialization/sanctioned-geode-serialization-serializables.txt
index 4e9c598..3133970 100644
--- a/geode-serialization/src/main/resources/org/apache/geode/internal/serialization/sanctioned-geode-serialization-serializables.txt
+++ b/geode-serialization/src/main/resources/org/apache/geode/internal/serialization/sanctioned-geode-serialization-serializables.txt
@@ -1,3 +1,4 @@
 org/apache/geode/internal/serialization/DSCODE,false,value:byte
 org/apache/geode/internal/serialization/DSFIDNotFoundException,true,130596009484324655,dsfid:int,versionOrdinal:short
 org/apache/geode/internal/serialization/UnsupportedSerializationVersionException,true,3572445857994216007
+org/apache/geode/internal/serialization/filter/ApiPackage,false,prefix:java/lang/String
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/SerializationDependenciesTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/SerializationDependenciesTest.java
index 3806564..4dfb4c5 100644
--- a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/SerializationDependenciesTest.java
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/SerializationDependenciesTest.java
@@ -43,7 +43,7 @@ public class SerializationDependenciesTest {
           resideInAPackage("org.apache.geode.internal.serialization..")
               .or(not(resideInAPackage("org.apache.geode..")))
               .or(resideInAPackage("org.apache.geode.annotations.."))
-              .or(resideInAPackage("org.apache.geode.internal.lang.utils.."))
+              .or(resideInAPackage("org.apache.geode.internal.lang.."))
               .or(resideInAPackage("org.apache.geode.logging.internal.log4j.api.."))
               .or(resideInAPackage("org.apache.geode.test..")));
 }
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ApiPackageTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ApiPackageTest.java
new file mode 100644
index 0000000..19c159b
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ApiPackageTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.geode.internal.serialization.filter.ApiPackage.JAVA_IO;
+import static org.apache.geode.internal.serialization.filter.ApiPackage.SUN_MISC;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+
+import org.junit.Test;
+
+public class ApiPackageTest {
+
+  @Test
+  public void JAVA_IO_prefix() {
+    assertThat(JAVA_IO.getPrefix()).isEqualTo("java.io.");
+  }
+
+  @Test
+  public void SUN_MISC_prefix() {
+    assertThat(SUN_MISC.getPrefix()).isEqualTo("sun.misc.");
+  }
+
+  @Test
+  public void qualifyEmptyStringEqualsPrefix() {
+    assertThat(JAVA_IO.qualify("")).isEqualTo(JAVA_IO.getPrefix());
+  }
+
+  @Test
+  public void qualifyNullStringThrowsNullPointerException() {
+    Throwable thrown = catchThrowable(() -> {
+      JAVA_IO.qualify(null);
+    });
+
+    assertThat(thrown)
+        .isInstanceOf(NullPointerException.class)
+        .hasMessage("className is required");
+  }
+
+  @Test
+  public void qualifyReturnsPrefixedClassName() {
+    ApiPackage apiPackage = JAVA_IO;
+    String className = "className";
+
+    String qualifiedClassName = apiPackage.qualify(className);
+
+    assertThat(qualifiedClassName).isEqualTo(apiPackage.getPrefix() + className);
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilterConfigurationTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilterConfigurationTest.java
new file mode 100644
index 0000000..e31126b
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/GlobalSerialFilterConfigurationTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+import java.util.function.Consumer;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class GlobalSerialFilterConfigurationTest {
+
+  private SerializableObjectConfig config;
+  private GlobalSerialFilter globalSerialFilter;
+  private Consumer<String> logger;
+
+  @Before
+  public void setUp() {
+    config = mock(SerializableObjectConfig.class);
+    globalSerialFilter = mock(GlobalSerialFilter.class);
+    logger = uncheckedCast(mock(Consumer.class));
+  }
+
+  @Test
+  public void configureLogs_whenUnsupportedOperationExceptionIsThrown_withCause() {
+    doThrow(new UnsupportedOperationException(
+        new IllegalStateException("Serial filter can only be set once")))
+            .when(globalSerialFilter).setFilter();
+    FilterConfiguration filterConfiguration = new GlobalSerialFilterConfiguration(
+        config, logger, (pattern, sanctionedClasses) -> globalSerialFilter);
+
+    filterConfiguration.configure();
+
+    verify(logger).accept("Global serial filter is already configured.");
+  }
+
+  @Test
+  public void configureDoesNotLog_whenUnsupportedOperationExceptionIsThrown_withoutCause() {
+    doThrow(new UnsupportedOperationException("testing with no root cause"))
+        .when(globalSerialFilter).setFilter();
+    FilterConfiguration filterConfiguration = new GlobalSerialFilterConfiguration(
+        config, logger, (pattern, sanctionedClasses) -> globalSerialFilter);
+
+    filterConfiguration.configure();
+
+    verifyNoInteractions(logger);
+  }
+
+  @Test
+  public void configureSetsValidateSerializableObjects() {
+    SerializableObjectConfig serializableObjectConfig = mock(SerializableObjectConfig.class);
+    FilterConfiguration filterConfiguration =
+        new GlobalSerialFilterConfiguration(serializableObjectConfig);
+
+    filterConfiguration.configure();
+
+    verify(serializableObjectConfig).setValidateSerializableObjects(true);
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/JmxSerialFilterConfigurationFactoryTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/JmxSerialFilterConfigurationFactoryTest.java
new file mode 100644
index 0000000..e7e72f6
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/JmxSerialFilterConfigurationFactoryTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.contrib.java.lang.system.RestoreSystemProperties;
+
+public class JmxSerialFilterConfigurationFactoryTest {
+
+  @Rule
+  public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
+
+  @Test
+  public void createsConditionalJmxSerialFilterConfiguration_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
+    JmxSerialFilterConfigurationFactory factory =
+        new SystemPropertyJmxSerialFilterConfigurationFactory();
+
+    FilterConfiguration filterConfiguration = factory.create();
+
+    assertThat(filterConfiguration).isInstanceOf(JmxSerialFilterConfiguration.class);
+  }
+
+  @Test
+  public void createsNoOp_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
+    JmxSerialFilterConfigurationFactory factory =
+        new SystemPropertyJmxSerialFilterConfigurationFactory();
+
+    FilterConfiguration filterConfiguration = factory.create();
+
+    assertThat(filterConfiguration).isNotInstanceOf(JmxSerialFilterConfiguration.class);
+  }
+
+  @Test
+  public void createsNoOp_whenJmxSerialFilter_isSet() {
+    System.setProperty("jmx.remote.rmi.server.serial.filter.pattern", "*");
+    JmxSerialFilterConfigurationFactory factory =
+        new SystemPropertyJmxSerialFilterConfigurationFactory();
+
+    FilterConfiguration filterConfiguration = factory.create();
+
+    assertThat(filterConfiguration).isNotInstanceOf(JmxSerialFilterConfiguration.class);
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/JmxSerialFilterConfigurationTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/JmxSerialFilterConfigurationTest.java
new file mode 100644
index 0000000..ad05630
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/JmxSerialFilterConfigurationTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.function.Consumer;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.contrib.java.lang.system.RestoreSystemProperties;
+
+public class JmxSerialFilterConfigurationTest {
+
+  private static final String SYSTEM_PROPERTY = "system.property.name";
+
+  private String pattern;
+  private Consumer<String> loggerConsumer;
+
+  @Rule
+  public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
+
+  @Before
+  public void setUp() {
+    pattern = "the-filter-pattern";
+    loggerConsumer = uncheckedCast(mock(Consumer.class));
+  }
+
+  @Test
+  public void propertyValue_isNullByDefault() {
+    assertThat(System.getProperty(SYSTEM_PROPERTY))
+        .as(SYSTEM_PROPERTY)
+        .isNull();
+  }
+
+  @Test
+  public void setsPropertyValue() {
+    FilterConfiguration filterConfiguration =
+        new JmxSerialFilterConfiguration(SYSTEM_PROPERTY, pattern);
+
+    filterConfiguration.configure();
+
+    assertThat(System.getProperty(SYSTEM_PROPERTY))
+        .as(SYSTEM_PROPERTY)
+        .isNotEmpty();
+  }
+
+  @Test
+  public void setsPropertyValue_ifExistingValueIsNull() {
+    System.clearProperty(SYSTEM_PROPERTY);
+    FilterConfiguration filterConfiguration =
+        new JmxSerialFilterConfiguration(SYSTEM_PROPERTY, pattern);
+
+    filterConfiguration.configure();
+
+    assertThat(System.getProperty(SYSTEM_PROPERTY))
+        .as(SYSTEM_PROPERTY)
+        .isEqualTo(pattern);
+  }
+
+  @Test
+  public void setsPropertyValue_ifExistingValueIsEmpty() {
+    System.setProperty(SYSTEM_PROPERTY, "");
+    FilterConfiguration filterConfiguration =
+        new JmxSerialFilterConfiguration(SYSTEM_PROPERTY, pattern);
+
+    filterConfiguration.configure();
+
+    assertThat(System.getProperty(SYSTEM_PROPERTY))
+        .as(SYSTEM_PROPERTY)
+        .isEqualTo(pattern);
+  }
+
+  @Test
+  public void setsPropertyValue_ifExistingValueIsBlank() {
+    System.setProperty(SYSTEM_PROPERTY, " ");
+    FilterConfiguration filterConfiguration =
+        new JmxSerialFilterConfiguration(SYSTEM_PROPERTY, pattern);
+
+    filterConfiguration.configure();
+
+    assertThat(System.getProperty(SYSTEM_PROPERTY))
+        .as(SYSTEM_PROPERTY)
+        .isEqualTo(pattern);
+  }
+
+  @Test
+  public void logsNowConfigured_ifExistingValueIsEmpty() {
+    System.setProperty(SYSTEM_PROPERTY, "");
+    FilterConfiguration filterConfiguration =
+        new JmxSerialFilterConfiguration(SYSTEM_PROPERTY, pattern, loggerConsumer);
+
+    filterConfiguration.configure();
+
+    verify(loggerConsumer)
+        .accept("System property '" + SYSTEM_PROPERTY + "' is now configured with '" +
+            pattern + "'.");
+  }
+
+  @Test
+  public void logsNowConfigured_ifExistingValueIsBlank() {
+    System.setProperty(SYSTEM_PROPERTY, " ");
+    FilterConfiguration filterConfiguration =
+        new JmxSerialFilterConfiguration(SYSTEM_PROPERTY, pattern, loggerConsumer);
+
+    filterConfiguration.configure();
+
+    verify(loggerConsumer)
+        .accept("System property '" + SYSTEM_PROPERTY + "' is now configured with '" +
+            pattern + "'.");
+  }
+
+  @Test
+  public void doesNotSetPropertyValue_ifExistingValueIsNotEmpty() {
+    String existingValue = "existing-value-of-property";
+    System.setProperty(SYSTEM_PROPERTY, existingValue);
+    FilterConfiguration filterConfiguration =
+        new JmxSerialFilterConfiguration(SYSTEM_PROPERTY, pattern);
+
+    filterConfiguration.configure();
+
+    assertThat(System.getProperty(SYSTEM_PROPERTY))
+        .as(SYSTEM_PROPERTY)
+        .isEqualTo(existingValue);
+  }
+
+  @Test
+  public void logsAlreadyConfiguredMessage_ifExistingPropertyValueIsNotEmpty() {
+    String existingValue = "existing-value-of-property";
+    System.setProperty(SYSTEM_PROPERTY, existingValue);
+    FilterConfiguration filterConfiguration =
+        new JmxSerialFilterConfiguration(SYSTEM_PROPERTY, pattern, loggerConsumer);
+
+    filterConfiguration.configure();
+
+    verify(loggerConsumer)
+        .accept("System property '" + SYSTEM_PROPERTY + "' is already configured.");
+  }
+}
diff --git a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/NullObjectInputFilterTest.java
similarity index 65%
copy from geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
copy to geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/NullObjectInputFilterTest.java
index 1d139ce..2ab18a9 100644
--- a/geode-serialization/src/main/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesService.java
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/NullObjectInputFilterTest.java
@@ -14,20 +14,22 @@
  */
 package org.apache.geode.internal.serialization.filter;
 
-import java.io.IOException;
-import java.net.URL;
-import java.util.Collection;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
 
-@FunctionalInterface
-public interface SanctionedSerializablesService {
+import java.io.ObjectInputStream;
 
-  URL getSanctionedSerializablesURL();
+import org.junit.Test;
 
-  default Class<?> getInterface() {
-    return getClass();
-  }
+public class NullObjectInputFilterTest {
+
+  @Test
+  public void doesNothing() {
+    ObjectInputStream objectInputStream = mock(ObjectInputStream.class);
+    ObjectInputFilter filter = new NullObjectInputFilter();
+
+    filter.setFilterOn(objectInputStream);
 
-  default Collection<String> getSerializationAcceptlist() throws IOException {
-    return SanctionedSerializables.loadClassNames(getSanctionedSerializablesURL());
+    verifyNoInteractions(objectInputStream);
   }
 }
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterUtilsTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterUtilsTest.java
new file mode 100644
index 0000000..fc0d480
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ObjectInputFilterUtilsTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.apache.geode.internal.serialization.filter.ObjectInputFilterUtils.supportsObjectInputFilter;
+import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.function.Function;
+
+import org.junit.Test;
+
+public class ObjectInputFilterUtilsTest {
+
+  @Test
+  public void supportsObjectInputFilter_delegatesToClassUtils() {
+    Function<String, Boolean> isClassAvailable = uncheckedCast(mock(Function.class));
+    when(isClassAvailable.apply(anyString())).thenReturn(true);
+
+    supportsObjectInputFilter(isClassAvailable);
+
+    verify(isClassAvailable).apply(anyString());
+  }
+
+  @Test
+  public void supportsObjectInputFilter_checksSunMisc() {
+    Function<String, Boolean> isClassAvailable = uncheckedCast(mock(Function.class));
+    when(isClassAvailable.apply(anyString())).thenReturn(false);
+
+    supportsObjectInputFilter(isClassAvailable);
+
+    verify(isClassAvailable).apply("sun.misc.ObjectInputFilter");
+  }
+
+  @Test
+  public void supportsObjectInputFilter_checksJavaIo() {
+    Function<String, Boolean> isClassAvailable = uncheckedCast(mock(Function.class));
+    when(isClassAvailable.apply(anyString())).thenReturn(false);
+
+    supportsObjectInputFilter(isClassAvailable);
+
+    verify(isClassAvailable).apply("java.io.ObjectInputFilter");
+  }
+
+  @Test
+  public void supportsObjectInputFilter_checksSunMiscAndJavaIo() {
+    Function<String, Boolean> isClassAvailable = uncheckedCast(mock(Function.class));
+    when(isClassAvailable.apply(anyString())).thenReturn(false);
+
+    supportsObjectInputFilter(isClassAvailable);
+
+    verify(isClassAvailable).apply("sun.misc.ObjectInputFilter");
+    verify(isClassAvailable).apply("java.io.ObjectInputFilter");
+  }
+
+  @Test
+  public void supportsObjectInputFilter_returnsTrue_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
+
+    boolean result = supportsObjectInputFilter();
+
+    assertThat(result).isTrue();
+  }
+
+  @Test
+  public void supportsObjectInputFilter_returnsTrue_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
+
+    boolean result = supportsObjectInputFilter();
+
+    assertThat(result).isTrue();
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/OpenMBeanFilterPatternTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/OpenMBeanFilterPatternTest.java
new file mode 100644
index 0000000..bfb8809
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/OpenMBeanFilterPatternTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.rmi.MarshalledObject;
+
+import javax.management.ObjectName;
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularType;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class OpenMBeanFilterPatternTest {
+
+  private String defaultPattern;
+
+  @Before
+  public void setUp() {
+    defaultPattern = new OpenMBeanFilterPattern().pattern();
+  }
+
+  @Test
+  public void includesBoolean() {
+    assertThat(defaultPattern).contains(Boolean.class.getName());
+  }
+
+  @Test
+  public void includesByte() {
+    assertThat(defaultPattern).contains(Byte.class.getName());
+  }
+
+  @Test
+  public void includesCharacter() {
+    assertThat(defaultPattern).contains(Character.class.getName());
+  }
+
+  @Test
+  public void includesShort() {
+    assertThat(defaultPattern).contains(Short.class.getName());
+  }
+
+  @Test
+  public void includesInteger() {
+    assertThat(defaultPattern).contains(Integer.class.getName());
+  }
+
+  @Test
+  public void includesLong() {
+    assertThat(defaultPattern).contains(Long.class.getName());
+  }
+
+  @Test
+  public void includesFloat() {
+    assertThat(defaultPattern).contains(Float.class.getName());
+  }
+
+  @Test
+  public void includesDouble() {
+    assertThat(defaultPattern).contains(Double.class.getName());
+  }
+
+  @Test
+  public void includesString() {
+    assertThat(defaultPattern).contains(String.class.getName());
+  }
+
+  @Test
+  public void includesBigInteger() {
+    assertThat(defaultPattern).contains(BigInteger.class.getName());
+  }
+
+  @Test
+  public void includesBigDecimal() {
+    assertThat(defaultPattern).contains(BigDecimal.class.getName());
+  }
+
+  @Test
+  public void includesObjectName() {
+    assertThat(defaultPattern).contains(ObjectName.class.getName());
+  }
+
+  @Test
+  public void includesCompositeData() {
+    assertThat(defaultPattern).contains(CompositeData.class.getName());
+  }
+
+  @Test
+  public void includesTabularData() {
+    assertThat(defaultPattern).contains(TabularData.class.getName());
+  }
+
+  @Test
+  public void includesSimpleType() {
+    assertThat(defaultPattern).contains(SimpleType.class.getName());
+  }
+
+  @Test
+  public void includesCompositeType() {
+    assertThat(defaultPattern).contains(CompositeType.class.getName());
+  }
+
+  @Test
+  public void includesTabularType() {
+    assertThat(defaultPattern).contains(TabularType.class.getName());
+  }
+
+  @Test
+  public void includesArrayType() {
+    assertThat(defaultPattern).contains(ArrayType.class.getName());
+  }
+
+  @Test
+  public void includesMarshalledObject() {
+    assertThat(defaultPattern).contains(MarshalledObject.class.getName());
+  }
+
+  @Test
+  public void rejectsAllOtherTypes() {
+    assertThat(defaultPattern).endsWith(";!*");
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeGlobalSerialFilterFactoryTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeGlobalSerialFilterFactoryTest.java
new file mode 100644
index 0000000..34c83c4
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeGlobalSerialFilterFactoryTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.util.Collections.emptySet;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.junit.Test;
+
+public class ReflectiveFacadeGlobalSerialFilterFactoryTest {
+
+  @Test
+  public void constructsDelegatingGlobalSerialFilter() {
+    ObjectInputFilterApi api = mock(ObjectInputFilterApi.class);
+    GlobalSerialFilterFactory factory = new ReflectiveFacadeGlobalSerialFilterFactory(api);
+
+    GlobalSerialFilter filter = factory.create("pattern", emptySet());
+
+    assertThat(filter).isInstanceOf(ReflectiveFacadeGlobalSerialFilter.class);
+  }
+
+  @Test
+  public void delegatesToObjectInputFilterApiToCreateObjectInputFilter()
+      throws InvocationTargetException, IllegalAccessException {
+    ObjectInputFilterApi api = mock(ObjectInputFilterApi.class);
+    GlobalSerialFilterFactory factory = new ReflectiveFacadeGlobalSerialFilterFactory(api);
+    GlobalSerialFilter filter = factory.create("pattern", emptySet());
+    Object objectInputFilter = mock(Object.class);
+
+    when(api.createObjectInputFilterProxy(any(), any()))
+        .thenReturn(objectInputFilter);
+
+    filter.setFilter();
+
+    verify(api).createObjectInputFilterProxy(any(), any());
+  }
+
+  @Test
+  public void delegatesToObjectInputFilterApiToSetSerialFilter()
+      throws InvocationTargetException, IllegalAccessException {
+    ObjectInputFilterApi api = mock(ObjectInputFilterApi.class);
+    GlobalSerialFilterFactory factory = new ReflectiveFacadeGlobalSerialFilterFactory(api);
+    GlobalSerialFilter filter = factory.create("pattern", emptySet());
+    Object objectInputFilter = mock(Object.class);
+
+    when(api.createObjectInputFilterProxy(any(), any()))
+        .thenReturn(objectInputFilter);
+
+    filter.setFilter();
+
+    verify(api).setSerialFilter(objectInputFilter);
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeGlobalSerialFilterTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeGlobalSerialFilterTest.java
new file mode 100644
index 0000000..5966d1b
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeGlobalSerialFilterTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singleton;
+import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+import static org.mockito.ArgumentCaptor.forClass;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+public class ReflectiveFacadeGlobalSerialFilterTest {
+
+  private ObjectInputFilterApi api;
+
+  @Before
+  public void setUp() {
+    api = mock(ObjectInputFilterApi.class);
+  }
+
+  @Test
+  public void createsObjectInputFilterProxy()
+      throws InvocationTargetException, IllegalAccessException {
+    String pattern = "the-pattern";
+    Collection<String> sanctionedClasses = asList("class-name-one", "class-name-two");
+    GlobalSerialFilter globalSerialFilter =
+        new ReflectiveFacadeGlobalSerialFilter(api, pattern, sanctionedClasses);
+
+    globalSerialFilter.setFilter();
+
+    ArgumentCaptor<Collection<String>> captor = uncheckedCast(forClass(Collection.class));
+    verify(api).createObjectInputFilterProxy(eq(pattern), captor.capture());
+    assertThat(captor.getValue()).containsExactlyElementsOf(sanctionedClasses);
+  }
+
+  @Test
+  public void setsSerialFilter() throws InvocationTargetException, IllegalAccessException {
+    GlobalSerialFilter globalSerialFilter =
+        new ReflectiveFacadeGlobalSerialFilter(api, "the-pattern", singleton("class-name"));
+
+    globalSerialFilter.setFilter();
+
+    verify(api).setSerialFilter(any());
+  }
+
+  @Test
+  public void propagatesIllegalAccessExceptionInUnsupportedOperationException()
+      throws InvocationTargetException, IllegalAccessException {
+    IllegalAccessException exception = new IllegalAccessException("testing");
+    doThrow(exception).when(api).setSerialFilter(any());
+    GlobalSerialFilter globalSerialFilter =
+        new ReflectiveFacadeGlobalSerialFilter(api, "the-pattern", singleton("class-name"));
+
+    Throwable thrown = catchThrowable(() -> {
+      globalSerialFilter.setFilter();
+    });
+
+    assertThat(thrown)
+        .isInstanceOf(UnsupportedOperationException.class)
+        .hasRootCause(exception);
+  }
+
+  @Test
+  public void propagatesInvocationTargetExceptionInUnsupportedOperationException()
+      throws InvocationTargetException, IllegalAccessException {
+    InvocationTargetException exception =
+        new InvocationTargetException(new Exception("testing"), "testing");
+    doThrow(exception).when(api).setSerialFilter(any());
+    GlobalSerialFilter globalSerialFilter =
+        new ReflectiveFacadeGlobalSerialFilter(api, "the-pattern", singleton("class-name"));
+
+    Throwable thrown = catchThrowable(() -> {
+      globalSerialFilter.setFilter();
+    });
+
+    assertThat(thrown)
+        .isInstanceOf(UnsupportedOperationException.class)
+        .hasCause(exception);
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeObjectInputFilterFactoryTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeObjectInputFilterFactoryTest.java
new file mode 100644
index 0000000..e35b5bd
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeObjectInputFilterFactoryTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.util.Collections.emptySet;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.apache.geode.internal.serialization.filter.ApiPackage.JAVA_IO;
+import static org.apache.geode.internal.serialization.filter.ApiPackage.SUN_MISC;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Set;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class ReflectiveFacadeObjectInputFilterFactoryTest {
+
+  private static final Set<String> SANCTIONED_CLASSES = emptySet();
+
+  private SerializableObjectConfig config;
+
+  @Before
+  public void setUp() {
+    config = mock(SerializableObjectConfig.class);
+    when(config.getValidateSerializableObjects()).thenReturn(true);
+  }
+
+  @Test
+  public void createsJava9InputStreamFilter_onJava9orGreater() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
+
+    // arrange
+    ObjectInputFilterFactory factory = new ReflectiveFacadeObjectInputFilterFactory();
+
+    // act
+    ObjectInputFilter objectInputFilter = factory.create(config, SANCTIONED_CLASSES);
+
+    // assert
+    assertThat(getApiPackage(getObjectInputFilterApi(objectInputFilter))).isEqualTo(JAVA_IO);
+  }
+
+  @Test
+  public void createsJava8InputStreamFilter_onJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
+
+    // arrange
+    ObjectInputFilterFactory factory = new ReflectiveFacadeObjectInputFilterFactory();
+
+    // act
+    ObjectInputFilter objectInputFilter = factory.create(config, SANCTIONED_CLASSES);
+
+    // assert
+    assertThat(getApiPackage(getObjectInputFilterApi(objectInputFilter))).isEqualTo(SUN_MISC);
+  }
+
+  private static ObjectInputFilterApi getObjectInputFilterApi(ObjectInputFilter result) {
+    ReflectiveFacadeObjectInputFilter impl = (ReflectiveFacadeObjectInputFilter) result;
+    ObjectInputFilterApi objectInputFilterApi = impl.getObjectInputFilterApi();
+    assertThat(objectInputFilterApi).isInstanceOf(ReflectiveObjectInputFilterApi.class);
+    return objectInputFilterApi;
+  }
+
+  private static ApiPackage getApiPackage(ObjectInputFilterApi reflectionObjectInputFilterApi) {
+    return ((ReflectiveObjectInputFilterApi) reflectionObjectInputFilterApi).getApiPackage();
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeObjectInputFilterTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeObjectInputFilterTest.java
new file mode 100644
index 0000000..cca697f
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveFacadeObjectInputFilterTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
+import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+import static org.mockito.ArgumentCaptor.forClass;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ObjectInputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+public class ReflectiveFacadeObjectInputFilterTest {
+
+  private ObjectInputFilterApi api;
+  private ObjectInputStream objectInputStream;
+
+  @Before
+  public void setUp() {
+    api = mock(ObjectInputFilterApi.class);
+    objectInputStream = mock(ObjectInputStream.class);
+  }
+
+  @Test
+  public void createsObjectInputFilterProxy()
+      throws InvocationTargetException, IllegalAccessException {
+    String pattern = "the-pattern";
+    Collection<String> sanctionedClasses = asList("class-name-one", "class-name-two");
+    ObjectInputFilter objectInputFilter =
+        new ReflectiveFacadeObjectInputFilter(api, pattern, sanctionedClasses);
+
+    objectInputFilter.setFilterOn(objectInputStream);
+
+    ArgumentCaptor<Collection<String>> captor = uncheckedCast(forClass(Collection.class));
+    verify(api).createObjectInputFilterProxy(eq(pattern), captor.capture());
+    assertThat(captor.getValue()).containsExactlyElementsOf(sanctionedClasses);
+  }
+
+  @Test
+  public void setsSerialFilter() throws InvocationTargetException, IllegalAccessException {
+    ObjectInputFilter objectInputFilter =
+        new ReflectiveFacadeObjectInputFilter(api, "the-pattern", singleton("class-name"));
+
+    objectInputFilter.setFilterOn(objectInputStream);
+
+    verify(api).setObjectInputFilter(same(objectInputStream), any());
+  }
+
+  @Test
+  public void propagatesIllegalAccessExceptionInUnsupportedOperationException()
+      throws InvocationTargetException, IllegalAccessException {
+    IllegalAccessException exception = new IllegalAccessException("testing");
+    doThrow(exception).when(api).setObjectInputFilter(same(objectInputStream), any());
+    ObjectInputFilter objectInputFilter =
+        new ReflectiveFacadeObjectInputFilter(api, "the-pattern", singleton("class-name"));
+
+    Throwable thrown = catchThrowable(() -> {
+      objectInputFilter.setFilterOn(objectInputStream);
+    });
+
+    assertThat(thrown)
+        .isInstanceOf(UnsupportedOperationException.class)
+        .hasRootCause(exception);
+  }
+
+  @Test
+  public void propagatesInvocationTargetExceptionInUnsupportedOperationException()
+      throws InvocationTargetException, IllegalAccessException {
+    InvocationTargetException exception =
+        new InvocationTargetException(new Exception("testing"), "testing");
+    doThrow(exception).when(api).setObjectInputFilter(same(objectInputStream), any());
+    ObjectInputFilter objectInputFilter =
+        new ReflectiveFacadeObjectInputFilter(api, "the-pattern", singleton("class-name"));
+
+    Throwable thrown = catchThrowable(() -> {
+      objectInputFilter.setFilterOn(objectInputStream);
+    });
+
+    assertThat(thrown)
+        .isInstanceOf(UnsupportedOperationException.class)
+        .hasCause(exception);
+  }
+
+  @Test
+  public void delegatesToObjectInputFilterApiToCreateObjectInputFilter()
+      throws InvocationTargetException, IllegalAccessException {
+    ObjectInputFilterApi api = mock(ObjectInputFilterApi.class);
+    ObjectInputFilter filter = new ReflectiveFacadeObjectInputFilter(api, "pattern", emptySet());
+    Object objectInputFilter = mock(Object.class);
+    ObjectInputStream objectInputStream = mock(ObjectInputStream.class);
+
+    when(api.createObjectInputFilterProxy(any(), any()))
+        .thenReturn(objectInputFilter);
+
+    filter.setFilterOn(objectInputStream);
+
+    verify(api).createObjectInputFilterProxy(any(), any());
+  }
+
+  @Test
+  public void delegatesToObjectInputFilterApiToSetFilterOnObjectInputStream()
+      throws InvocationTargetException, IllegalAccessException {
+    ObjectInputFilterApi api = mock(ObjectInputFilterApi.class);
+    ObjectInputFilter filter = new ReflectiveFacadeObjectInputFilter(api, "pattern", emptySet());
+    Object objectInputFilter = mock(Object.class);
+    ObjectInputStream objectInputStream = mock(ObjectInputStream.class);
+
+    when(api.createObjectInputFilterProxy(any(), any()))
+        .thenReturn(objectInputFilter);
+
+    filter.setFilterOn(objectInputStream);
+
+    verify(api).setObjectInputFilter(objectInputStream, objectInputFilter);
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiFactoryTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiFactoryTest.java
new file mode 100644
index 0000000..b3ac61f
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiFactoryTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtMost;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
+
+import org.junit.Test;
+
+public class ReflectiveObjectInputFilterApiFactoryTest {
+
+  @Test
+  public void createsInstanceOfReflectionObjectInputFilterApi() {
+    ObjectInputFilterApiFactory factory = new ReflectiveObjectInputFilterApiFactory();
+
+    ObjectInputFilterApi api = factory.createObjectInputFilterApi();
+
+    assertThat(api).isInstanceOf(ReflectiveObjectInputFilterApi.class);
+  }
+
+  @Test
+  public void usesJavaIO_inJava9() {
+    assumeThat(isJavaVersionAtLeast(JAVA_9)).isTrue();
+
+    ObjectInputFilterApiFactory factory = new ReflectiveObjectInputFilterApiFactory();
+
+    ReflectiveObjectInputFilterApi api =
+        (ReflectiveObjectInputFilterApi) factory.createObjectInputFilterApi();
+
+    assertThat(api.getApiPackage()).isEqualTo(ApiPackage.JAVA_IO);
+  }
+
+  @Test
+  public void usesSunMisc_inJava8() {
+    assumeThat(isJavaVersionAtMost(JAVA_1_8)).isTrue();
+
+    ObjectInputFilterApiFactory factory = new ReflectiveObjectInputFilterApiFactory();
+
+    ReflectiveObjectInputFilterApi api =
+        (ReflectiveObjectInputFilterApi) factory.createObjectInputFilterApi();
+
+    assertThat(api.getApiPackage()).isEqualTo(ApiPackage.SUN_MISC);
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiTest.java
new file mode 100644
index 0000000..8cf1f8c
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/ReflectiveObjectInputFilterApiTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singleton;
+import static java.util.Objects.hash;
+import static java.util.Objects.requireNonNull;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_8;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.SerializationUtils.serialize;
+import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InvalidClassException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class ReflectiveObjectInputFilterApiTest {
+
+  private ObjectInputFilterApi api;
+
+  @Before
+  public void setUp() throws ClassNotFoundException, NoSuchMethodException {
+    if (isJavaVersionAtLeast(JAVA_9)) {
+      api = new Java9ReflectiveObjectInputFilterApi(ApiPackage.JAVA_IO);
+    } else if (isJavaVersionAtLeast(JAVA_1_8)) {
+      api = new ReflectiveObjectInputFilterApi(ApiPackage.SUN_MISC);
+    }
+  }
+
+  @Test
+  public void createFilterGivenValidPatternReturnsNewFilter()
+      throws IllegalAccessException, InvocationTargetException {
+
+    Object filter = api.createFilter("!*");
+
+    assertThat(filter)
+        .as("ObjectInputFilter$Config.createFilter(\"!*\")")
+        .isNotNull()
+        .isInstanceOf(api.getObjectInputFilterClass());
+  }
+
+  @Test
+  public void createFilterGivenEmptyPatternReturnsNull()
+      throws IllegalAccessException, InvocationTargetException {
+
+    Object filter = api.createFilter("");
+
+    assertThat(filter)
+        .as("ObjectInputFilter$Config.createFilter(\"\")")
+        .isNull();
+  }
+
+  @Test
+  public void createFilterGivenBlankPatternReturnsNewFilter()
+      throws IllegalAccessException, InvocationTargetException {
+
+    Object filter = api.createFilter(" ");
+
+    assertThat(filter)
+        .as("ObjectInputFilter$Config.createFilter(\" \")")
+        .isNotNull()
+        .isInstanceOf(api.getObjectInputFilterClass());
+  }
+
+  @Test
+  public void getSerialFilterReturnsNullWhenFilterDoesNotExist()
+      throws IllegalAccessException, InvocationTargetException {
+
+    Object filter = api.getSerialFilter();
+
+    assertThat(filter)
+        .as("ObjectInputFilter$Config.getSerialFilter()")
+        .isNull();
+  }
+
+  @Test
+  public void getObjectInputFilterReturnsNullWhenFilterDoesNotExist()
+      throws IllegalAccessException, InvocationTargetException, IOException {
+    Serializable object = "hello";
+    ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream(object));
+
+    Object filter = api.getObjectInputFilter(inputStream);
+
+    assertThat(filter).isNull();
+  }
+
+  @Test
+  public void getObjectInputFilterReturnsExistingFilter()
+      throws IllegalAccessException, InvocationTargetException, IOException {
+    Object existingFilter = api.createFilter("*");
+    Serializable object = "hello";
+    ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream(object));
+    api.setObjectInputFilter(inputStream, existingFilter);
+
+    Object filter = api.getObjectInputFilter(inputStream);
+
+    assertThat(filter).isSameAs(existingFilter);
+  }
+
+  @Test
+  public void setObjectInputFilterGivenAcceptFilterReadsObject()
+      throws ClassNotFoundException, IllegalAccessException, InvocationTargetException,
+      IOException {
+    Serializable object = new SerializableClass("hello");
+    Object filter = api.createFilter(object.getClass().getName() + ";!*");
+
+    try (ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream(object))) {
+      api.setObjectInputFilter(inputStream, filter);
+
+      assertThat(inputStream.readObject()).isEqualTo(object);
+    }
+  }
+
+  @Test
+  public void setObjectInputFilterGivenDenyFilterThrowsInvalidClassException()
+      throws IllegalAccessException, InvocationTargetException, IOException {
+    Serializable object = new SerializableClass("hello");
+    Object filter = api.createFilter("!" + object.getClass().getName());
+
+    try (ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream(object))) {
+      api.setObjectInputFilter(inputStream, filter);
+
+      Throwable thrown = catchThrowable(() -> {
+        assertThat(inputStream.readObject()).isEqualTo(object);
+      });
+
+      assertThat(thrown).isInstanceOf(InvalidClassException.class);
+    }
+  }
+
+  @Test
+  public void createObjectInputFilterProxyGivenAcceptFilterReadsObject()
+      throws ClassNotFoundException, IllegalAccessException, InvocationTargetException,
+      IOException {
+    Serializable object = new SerializableClass("hello");
+    String className = object.getClass().getName();
+    Object filter = api.createObjectInputFilterProxy(className + ";!*", singleton(className));
+
+    try (ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream(object))) {
+      api.setObjectInputFilter(inputStream, filter);
+
+      assertThat(inputStream.readObject()).isEqualTo(object);
+    }
+  }
+
+  @Test
+  public void createObjectInputFilterProxyGivenRejectFilterThrowsInvalidClassException()
+      throws IllegalAccessException, InvocationTargetException, IOException {
+    Serializable object = new SerializableClass("hello");
+    String className = object.getClass().getName();
+    Object filter = api.createObjectInputFilterProxy("!" + className + ";*", emptyList());
+
+    try (ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream(object))) {
+      api.setObjectInputFilter(inputStream, filter);
+
+      Throwable thrown = catchThrowable(() -> {
+        assertThat(inputStream.readObject()).isEqualTo(object);
+      });
+
+      assertThat(thrown).isInstanceOf(InvalidClassException.class);
+    }
+  }
+
+  private static ByteArrayInputStream byteArrayInputStream(Serializable object) {
+    return new ByteArrayInputStream(serialize(object));
+  }
+
+  private static class SerializableClass implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final String value;
+
+    private SerializableClass(String value) {
+      this.value = requireNonNull(value);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      SerializableClass that = (SerializableClass) o;
+      return value.equals(that.value);
+    }
+
+    @Override
+    public int hashCode() {
+      return hash(value);
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder("SerializableClass{");
+      sb.append("value='").append(value).append('\'');
+      sb.append('}');
+      return sb.toString();
+    }
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesFilterPatternTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesFilterPatternTest.java
new file mode 100644
index 0000000..177e2c9
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/SanctionedSerializablesFilterPatternTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.StringJoiner;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class SanctionedSerializablesFilterPatternTest {
+
+  private String defaultPattern;
+
+  @Before
+  public void setUp() {
+    defaultPattern = new SanctionedSerializablesFilterPattern().pattern();
+  }
+
+  @Test
+  public void includesJavaWithSubpackages() {
+    assertThat(defaultPattern).contains("java.**");
+  }
+
+  @Test
+  public void includesJavaxManagementWithSubpackages() {
+    assertThat(defaultPattern).contains("javax.management.**");
+  }
+
+  @Test
+  public void includesEnumSyntax() {
+    assertThat(defaultPattern).contains("javax.print.attribute.EnumSyntax");
+  }
+
+  @Test
+  public void includesAntlrWithSubpackages() {
+    assertThat(defaultPattern).contains("antlr.**");
+  }
+
+  @Test
+  public void includesCommonsModelerAttributeInfo() {
+    assertThat(defaultPattern).contains("org.apache.commons.modeler.AttributeInfo");
+  }
+
+  @Test
+  public void includesCommonsModelerFeatureInfo() {
+    assertThat(defaultPattern).contains("org.apache.commons.modeler.FeatureInfo");
+  }
+
+  @Test
+  public void includesCommonsModelerManagedBean() {
+    assertThat(defaultPattern).contains("org.apache.commons.modeler.ManagedBean");
+  }
+
+  @Test
+  public void includesDistributionConfigSnapshot() {
+    assertThat(defaultPattern)
+        .contains("org.apache.geode.distributed.internal.DistributionConfigSnapshot");
+  }
+
+  @Test
+  public void includesRuntimeDistributionConfigImpl() {
+    assertThat(defaultPattern)
+        .contains("org.apache.geode.distributed.internal.RuntimeDistributionConfigImpl");
+  }
+
+  @Test
+  public void includesDistributionConfigImpl() {
+    assertThat(defaultPattern)
+        .contains("org.apache.geode.distributed.internal.DistributionConfigImpl");
+  }
+
+  @Test
+  public void includesInternalDistributedMember() {
+    assertThat(defaultPattern)
+        .contains("org.apache.geode.distributed.internal.membership.InternalDistributedMember");
+  }
+
+  @Test
+  public void includesPersistentMemberID() {
+    assertThat(defaultPattern)
+        .contains("org.apache.geode.internal.cache.persistence.PersistentMemberID");
+  }
+
+  @Test
+  public void includesDiskStoreID() {
+    assertThat(defaultPattern).contains("org.apache.geode.internal.cache.persistence.DiskStoreID");
+  }
+
+  @Test
+  public void includesVersionedObjectList() {
+    assertThat(defaultPattern)
+        .contains("org.apache.geode.internal.cache.tier.sockets.VersionedObjectList");
+  }
+
+  @Test
+  public void includesShiroPackage() {
+    assertThat(defaultPattern).contains("org.apache.shiro.**");
+  }
+
+  @Test
+  public void includesLog4jLevel() {
+    assertThat(defaultPattern).contains("org.apache.logging.log4j.Level");
+  }
+
+  @Test
+  public void includesLog4jStandardLevel() {
+    assertThat(defaultPattern).contains("org.apache.logging.log4j.spi.StandardLevel");
+  }
+
+  @Test
+  public void includesSunProxy() {
+    assertThat(defaultPattern).contains("com.sun.proxy.$Proxy*");
+  }
+
+  @Test
+  public void includesRmiioRemoteInputStream() {
+    assertThat(defaultPattern).contains("com.healthmarketscience.rmiio.RemoteInputStream");
+  }
+
+  @Test
+  public void includesSslRMIClientSocketFactory() {
+    assertThat(defaultPattern).contains("javax.rmi.ssl.SslRMIClientSocketFactory");
+  }
+
+  @Test
+  public void includesSSLHandshakeException() {
+    assertThat(defaultPattern).contains("javax.net.ssl.SSLHandshakeException");
+  }
+
+  @Test
+  public void includesSSLException() {
+    assertThat(defaultPattern).contains("javax.net.ssl.SSLException");
+  }
+
+  @Test
+  public void includesSunValidatorException() {
+    assertThat(defaultPattern).contains("sun.security.validator.ValidatorException");
+  }
+
+  @Test
+  public void includesSunSunCertPathBuilderException() {
+    assertThat(defaultPattern)
+        .contains("sun.security.provider.certpath.SunCertPathBuilderException");
+  }
+
+  @Test
+  public void includesSessionCustomExpiry() {
+    assertThat(defaultPattern).contains("org.apache.geode.modules.util.SessionCustomExpiry");
+  }
+
+  @Test
+  public void rejectsAllOtherTypes() {
+    assertThat(defaultPattern).endsWith(";!*");
+  }
+
+  @Test
+  public void appendsToPattern() {
+    String extraToAppend = "extra";
+    String pattern = new SanctionedSerializablesFilterPattern()
+        .append(extraToAppend)
+        .pattern();
+
+    assertThat(pattern).contains(extraToAppend);
+  }
+
+  @Test
+  public void appendsJustBeforeRejectPattern() {
+    String extraToAppend = "extra";
+    String pattern = new SanctionedSerializablesFilterPattern()
+        .append(extraToAppend)
+        .pattern();
+
+    String expectedEnd = new StringJoiner(";")
+        .add(extraToAppend)
+        .add("!*")
+        .toString();
+
+    assertThat(pattern).endsWith(expectedEnd);
+  }
+
+  @Test
+  public void patternReturnsSameValueMultipleTimes() {
+    FilterPattern sanctionedSerializablesFilterPattern =
+        new SanctionedSerializablesFilterPattern();
+    String expectedPattern = sanctionedSerializablesFilterPattern
+        .pattern();
+
+    assertThat(sanctionedSerializablesFilterPattern.pattern()).isEqualTo(expectedPattern);
+    assertThat(sanctionedSerializablesFilterPattern.pattern()).isEqualTo(expectedPattern);
+    assertThat(sanctionedSerializablesFilterPattern.pattern()).isEqualTo(expectedPattern);
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/SerializableObjectConfigTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/SerializableObjectConfigTest.java
new file mode 100644
index 0000000..22fbded
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/SerializableObjectConfigTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import org.junit.Test;
+
+public class SerializableObjectConfigTest {
+
+  @Test
+  public void getSerializableObjectFilterIfEnabled_returnsFilter_ifValidateIsTrue() {
+    SerializableObjectConfig config = spy(SerializableObjectConfig.class);
+    when(config.getSerializableObjectFilter()).thenReturn("filter-string");
+    when(config.getValidateSerializableObjects()).thenReturn(true);
+
+    String result = config.getSerializableObjectFilterIfEnabled();
+
+    assertThat(result).isEqualTo("filter-string");
+  }
+
+  @Test
+  public void getSerializableObjectFilterIfEnabled_returnsNull_ifValidateIsFalse() {
+    SerializableObjectConfig config = spy(SerializableObjectConfig.class);
+    when(config.getSerializableObjectFilter()).thenReturn("filter-string");
+    when(config.getValidateSerializableObjects()).thenReturn(false);
+
+    String result = config.getSerializableObjectFilterIfEnabled();
+
+    assertThat(result).isNull();
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/SystemPropertyGlobalSerialFilterConfigurationFactoryTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/SystemPropertyGlobalSerialFilterConfigurationFactoryTest.java
new file mode 100644
index 0000000..45070a6
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/SystemPropertyGlobalSerialFilterConfigurationFactoryTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.internal.serialization.filter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.contrib.java.lang.system.RestoreSystemProperties;
+
+public class SystemPropertyGlobalSerialFilterConfigurationFactoryTest {
+
+  @Rule
+  public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
+
+  @Test
+  public void createsConditionalGlobalSerialFilterConfiguration_whenEnableGlobalSerialFilter_isSet() {
+    System.setProperty("geode.enableGlobalSerialFilter", "true");
+    GlobalSerialFilterConfigurationFactory factory =
+        new SystemPropertyGlobalSerialFilterConfigurationFactory();
+
+    FilterConfiguration filterConfiguration = factory.create(mock(SerializableObjectConfig.class));
+
+    assertThat(filterConfiguration).isInstanceOf(GlobalSerialFilterConfiguration.class);
+  }
+
+  @Test
+  public void createsNoOp_whenEnableGlobalSerialFilter_isNotSet() {
+    GlobalSerialFilterConfigurationFactory factory =
+        new SystemPropertyGlobalSerialFilterConfigurationFactory();
+
+    FilterConfiguration filterConfiguration = factory.create(mock(SerializableObjectConfig.class));
+
+    assertThat(filterConfiguration)
+        .isNotInstanceOf(GlobalSerialFilterConfiguration.class);
+  }
+
+  @Test
+  public void createsNoOp_whenJdkSerialFilter_isSet() {
+    System.setProperty("jdk.serialFilter", "*");
+    GlobalSerialFilterConfigurationFactory factory =
+        new SystemPropertyGlobalSerialFilterConfigurationFactory();
+
+    FilterConfiguration filterConfiguration = factory.create(mock(SerializableObjectConfig.class));
+
+    assertThat(filterConfiguration)
+        .isNotInstanceOf(GlobalSerialFilterConfiguration.class);
+  }
+
+  @Test
+  public void createsNoOp_whenJdkSerialFilter_andEnableGlobalSerialFilter_areBothSet() {
+    System.setProperty("jdk.serialFilter", "*");
+    System.setProperty("geode.enableGlobalSerialFilter", "true");
+    GlobalSerialFilterConfigurationFactory factory =
+        new SystemPropertyGlobalSerialFilterConfigurationFactory();
+
+    FilterConfiguration filterConfiguration = factory.create(mock(SerializableObjectConfig.class));
+
+    assertThat(filterConfiguration)
+        .isNotInstanceOf(GlobalSerialFilterConfiguration.class);
+  }
+}
diff --git a/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/SystemPropertyJmxSerialFilterConfigurationFactoryTest.java b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/SystemPropertyJmxSerialFilterConfigurationFactoryTest.java
new file mode 100644
index 0000000..d95ef9c
--- /dev/null
+++ b/geode-serialization/src/test/java/org/apache/geode/internal/serialization/filter/SystemPropertyJmxSerialFilterConfigurationFactoryTest.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.internal.serialization.filter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Map;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.contrib.java.lang.system.RestoreSystemProperties;
+
+public class SystemPropertyJmxSerialFilterConfigurationFactoryTest {
+
+  private static final String PROPERTY_NAME = "jmx.remote.rmi.server.serial.filter.pattern";
+  private static final String PATTERN = "*";
+
+  @Rule
+  public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
+
+  @Test
+  public void jmxSerialFilterProperty_isBlankByDefault() {
+    assertThat(System.getProperty(PROPERTY_NAME)).isBlank();
+  }
+
+  @Test
+  public void returnsNewJmxSerialFilterConfiguration_whenFilteringIsEnabled() {
+    JmxSerialFilterConfigurationFactory factory =
+        new SystemPropertyJmxSerialFilterConfigurationFactory(true, PATTERN);
+
+    FilterConfiguration filterConfiguration = factory.create();
+
+    assertThat(filterConfiguration).isInstanceOf(JmxSerialFilterConfiguration.class);
+  }
+
+  @Test
+  public void returnsEnabledConfiguration_whenFilteringIsEnabled() {
+    JmxSerialFilterConfigurationFactory factory =
+        new SystemPropertyJmxSerialFilterConfigurationFactory(true, PATTERN);
+
+    FilterConfiguration filterConfiguration = factory.create();
+
+    assertThat(filterConfiguration.configure()).isTrue();
+  }
+
+  @Test
+  public void returnsDisabledConfiguration_whenFilteringIsDisabled() {
+    JmxSerialFilterConfigurationFactory factory =
+        new SystemPropertyJmxSerialFilterConfigurationFactory(false, PATTERN);
+
+    FilterConfiguration filterConfiguration = factory.create();
+
+    assertThat(filterConfiguration.configure()).isFalse();
+  }
+
+  @Test
+  public void enabledConfiguration_setsJmxSerialFilterProperty() {
+    JmxSerialFilterConfigurationFactory factory =
+        new SystemPropertyJmxSerialFilterConfigurationFactory(true, PATTERN);
+    FilterConfiguration filterConfiguration = factory.create();
+
+    filterConfiguration.configure();
+
+    assertThat(System.getProperty(PROPERTY_NAME)).isEqualTo(PATTERN);
+  }
+
+  @Test
+  public void disabledConfiguration_doesNotSetAnySystemProperties() {
+    Map<?, ?> propertiesBefore = System.getProperties();
+    JmxSerialFilterConfigurationFactory factory =
+        new SystemPropertyJmxSerialFilterConfigurationFactory(true, PATTERN);
+    FilterConfiguration filterConfiguration = factory.create();
+
+    filterConfiguration.configure();
+
+    assertThat(System.getProperties()).containsExactlyEntriesOf(propertiesBefore);
+  }
+}