Allow to disable JSON-B using System properties

Signed-off-by: jansupol <jan.supol@oracle.com>
diff --git a/core-common/src/main/java/org/glassfish/jersey/ApplicationSupplier.java b/core-common/src/main/java/org/glassfish/jersey/ApplicationSupplier.java
new file mode 100644
index 0000000..1eb9ca5
--- /dev/null
+++ b/core-common/src/main/java/org/glassfish/jersey/ApplicationSupplier.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+
+package org.glassfish.jersey;
+
+import javax.ws.rs.core.Application;
+
+/**
+ * Implementation of this interface is capable of returning {@link Application}.
+ */
+public interface ApplicationSupplier {
+    /**
+     * Get Application.
+     *
+     * @return Application.
+     */
+    Application getApplication();
+
+}
diff --git a/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java b/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java
index f1a713e..efe56c7 100644
--- a/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java
+++ b/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java
@@ -131,6 +131,19 @@
     public static final String JSON_BINDING_FEATURE_DISABLE_SERVER = "jersey.config.server.disableJsonBinding";
 
     /**
+     * Disables configuration of Json Binding (JSR-367) feature for {@link javax.ws.rs.core.Application} subclasses whose
+     * package names are specified as a value. The value is comma-separated string defining prefixes of the application
+     * package names.
+     * <p>
+     * By default, Json Binding is automatically enabled.
+     * <p>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     * </p>
+     * @since 2.45
+     */
+    public static final String JSON_BINDING_FEATURE_DISABLE_APPLICATION = "jersey.config.application.disableJsonBinding";
+
+    /**
      * If {@code true} then disable configuration of Json Processing (JSR-353) feature.
      * <p>
      * By default, Json Processing is automatically enabled. The value of this property may be overridden by the client/server
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/util/PropertiesHelper.java b/core-common/src/main/java/org/glassfish/jersey/internal/util/PropertiesHelper.java
index cb1c87b..45d2827 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/util/PropertiesHelper.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/util/PropertiesHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -390,6 +390,27 @@
     }
 
     /**
+     * Converts the property value to {@code boolean} and checks it is {@code true} or empty.
+     * Returns {@code true} if the value is {@code true} or empty but not {@code null}.
+     *
+     * <p>
+     *     The rationale behind this is that system property {@code -Dprop=true} is the same as {@code -Dprop}.
+     *     The property {@code -Dprop=false} behaves as if the {@code -Dprop} is not set at all.
+     * </p>
+     *
+     * @param value property value.
+     * @return {@code boolean} property value or {@code true} if the property value is not set or {@code false} if the property
+     *         is otherwise not convertible.
+     */
+    public static boolean isPropertyOrNotSet(final Object value) {
+        if (value instanceof Boolean) {
+            return Boolean.class.cast(value);
+        } else {
+            return value != null && ("".equals(value.toString()) || Boolean.parseBoolean(value.toString()));
+        }
+    }
+
+    /**
      * Faster replacement of {@code RuntimeType#name().toLowerCase(Locale.ROOT)}
      * @param runtimeType The runtime type to lower case
      * @return the lower-cased variant of the {@link RuntimeType}.
diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/PropertiesHelperTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/PropertiesHelperTest.java
index 68f6aac..69acabf 100644
--- a/core-common/src/test/java/org/glassfish/jersey/internal/util/PropertiesHelperTest.java
+++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/PropertiesHelperTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -199,4 +199,15 @@
 
     }
 
+    @Test
+    public void isPropertyOrNotSetTest() {
+        assertEquals(false, PropertiesHelper.isPropertyOrNotSet((Boolean) null));
+        assertEquals(true, PropertiesHelper.isPropertyOrNotSet(Boolean.TRUE));
+        assertEquals(false, PropertiesHelper.isPropertyOrNotSet(Boolean.FALSE));
+        assertEquals(false, PropertiesHelper.isPropertyOrNotSet((String) null));
+        assertEquals(true, PropertiesHelper.isPropertyOrNotSet(""));
+        assertEquals(false, PropertiesHelper.isPropertyOrNotSet("treu")); // false for non-boolean values
+        assertEquals(true, PropertiesHelper.isPropertyOrNotSet("TRUE"));
+        assertEquals(false, PropertiesHelper.isPropertyOrNotSet("false"));
+    }
 }
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ResourceConfig.java b/core-server/src/main/java/org/glassfish/jersey/server/ResourceConfig.java
index 1b13d05..359363e 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/ResourceConfig.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ResourceConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -37,6 +37,7 @@
 import javax.ws.rs.core.Configuration;
 import javax.ws.rs.core.Feature;
 
+import org.glassfish.jersey.ApplicationSupplier;
 import org.glassfish.jersey.internal.Errors;
 import org.glassfish.jersey.internal.config.ExternalPropertiesConfigurationFactory;
 import org.glassfish.jersey.internal.inject.Binder;
@@ -67,7 +68,7 @@
  * @author Michal Gajdos
  * @author Marek Potociar
  */
-public class ResourceConfig extends Application implements Configurable<ResourceConfig>, ServerConfig {
+public class ResourceConfig extends Application implements Configurable<ResourceConfig>, ServerConfig, ApplicationSupplier {
 
     private static final Logger LOGGER = Logger.getLogger(ResourceConfig.class.getName());
 
diff --git a/docs/src/main/docbook/appendix-properties.xml b/docs/src/main/docbook/appendix-properties.xml
index 13df68b..f131fa7 100644
--- a/docs/src/main/docbook/appendix-properties.xml
+++ b/docs/src/main/docbook/appendix-properties.xml
@@ -81,6 +81,24 @@
                         <entry>
                             <para>
                                 Disables configuration of Json Binding (JSR-367) feature. Default value is <literal>false</literal>.
+                                Can also be set as a system property.
+                            </para>
+                            <para>
+                                Since 2.45.
+                            </para>
+                        </entry>
+                    </row>
+                    <row>
+                        <entry>&jersey.common.CommonProperties.JSON_BINDING_FEATURE_DISABLE_APPLICATION; </entry>
+                        <entry><literal>jersey.config.application.disableJsonBinding</literal></entry>
+                        <entry>
+                            <para>
+                                Disables configuration of Json Binding (JSR-367) feature for <literal>javax.ws.rs.core.Application</literal>
+                                subclasses whose package names are specified as a value. The value is comma-separated string
+                                defining prefixes of the application package names. Can also be set as a system property.
+                            </para>
+                            <para>
+                                By default, Json Binding is automatically enabled.
                             </para>
                             <para>
                                 Since 2.45.
diff --git a/docs/src/main/docbook/jersey.ent b/docs/src/main/docbook/jersey.ent
index c8022b9..4cb70cc 100644
--- a/docs/src/main/docbook/jersey.ent
+++ b/docs/src/main/docbook/jersey.ent
@@ -392,6 +392,7 @@
 <!ENTITY jersey.common.CommonProperties.JSON_BINDING_FEATURE_DISABLE "<link xlink:href='&jersey.javadoc.uri.prefix;/CommonProperties.html#JSON_BINDING_FEATURE_DISABLE'>CommonProperties.JSON_BINDING_FEATURE_DISABLE</link>" >
 <!ENTITY jersey.common.CommonProperties.JSON_BINDING_FEATURE_DISABLE_CLIENT "<link xlink:href='&jersey.javadoc.uri.prefix;/CommonProperties.html#JSON_BINDING_FEATURE_DISABLE_CLIENT'>CommonProperties.JSON_BINDING_FEATURE_DISABLE_CLIENT</link>" >
 <!ENTITY jersey.common.CommonProperties.JSON_BINDING_FEATURE_DISABLE_SERVER "<link xlink:href='&jersey.javadoc.uri.prefix;/CommonProperties.html#JSON_BINDING_FEATURE_DISABLE_SERVER'>CommonProperties.JSON_BINDING_FEATURE_DISABLE_SERVER</link>" >
+<!ENTITY jersey.common.CommonProperties.JSON_BINDING_FEATURE_DISABLE_APPLICATION "<link xlink:href='&jersey.javadoc.uri.prefix;/CommonProperties.html#JSON_BINDING_FEATURE_DISABLE_APPLICATION'>CommonProperties.JSON_BINDING_FEATURE_DISABLE_APPLICATION</link>" >
 <!ENTITY jersey.common.CommonProperties.JSON_PROCESSING_FEATURE_DISABLE "<link xlink:href='&jersey.javadoc.uri.prefix;/CommonProperties.html#JSON_PROCESSING_FEATURE_DISABLE'>CommonProperties.JSON_PROCESSING_FEATURE_DISABLE</link>" >
 <!ENTITY jersey.common.CommonProperties.JSON_PROCESSING_FEATURE_DISABLE_CLIENT "<link xlink:href='&jersey.javadoc.uri.prefix;/CommonProperties.html#JSON_PROCESSING_FEATURE_DISABLE_CLIENT'>CommonProperties.JSON_PROCESSING_FEATURE_DISABLE_CLIENT</link>" >
 <!ENTITY jersey.common.CommonProperties.JSON_PROCESSING_FEATURE_DISABLE_SERVER "<link xlink:href='&jersey.javadoc.uri.prefix;/CommonProperties.html#JSON_PROCESSING_FEATURE_DISABLE_SERVER'>CommonProperties.JSON_PROCESSING_FEATURE_DISABLE_SERVER</link>" >
diff --git a/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/JsonBindingFeature.java b/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/JsonBindingFeature.java
index 0056138..40a56c4 100644
--- a/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/JsonBindingFeature.java
+++ b/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/JsonBindingFeature.java
@@ -16,16 +16,25 @@
 
 package org.glassfish.jersey.jsonb;
 
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Application;
 import javax.ws.rs.core.Configuration;
 import javax.ws.rs.core.Feature;
 import javax.ws.rs.core.FeatureContext;
 
+import org.glassfish.jersey.ApplicationSupplier;
 import org.glassfish.jersey.CommonProperties;
 import org.glassfish.jersey.internal.InternalProperties;
+import org.glassfish.jersey.internal.inject.InjectionManagerSupplier;
 import org.glassfish.jersey.internal.util.PropertiesHelper;
 import org.glassfish.jersey.jsonb.internal.JsonBindingAutoDiscoverable;
 import org.glassfish.jersey.jsonb.internal.JsonBindingProvider;
 
+import java.security.AccessController;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Logger;
+
 /**
  * Feature used to register JSON-B providers.
  * <p>
@@ -50,17 +59,85 @@
  */
 public class JsonBindingFeature implements Feature {
 
+    private static final Logger LOGGER = Logger.getLogger(JsonBindingFeature.class.getName());
     private static final String JSON_FEATURE = JsonBindingFeature.class.getSimpleName();
 
     @Override
     public boolean configure(final FeatureContext context) {
         final Configuration config = context.getConfiguration();
 
-        if (CommonProperties.getValue(config.getProperties(), config.getRuntimeType(),
-                CommonProperties.JSON_BINDING_FEATURE_DISABLE, Boolean.FALSE, Boolean.class)) {
+        // ---- Allow to disable for compatibility with Pre JAX-RS 2.1 Jersey.
+
+        /* Either system properties */
+        final String bindingDisabledBySystemProperty = AccessController.doPrivileged(
+                PropertiesHelper.getSystemProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE));
+
+        final String bindingDisabledBySystemPropertyClient = AccessController.doPrivileged(
+                PropertiesHelper.getSystemProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_CLIENT));
+
+        final String bindingDisabledBySystemPropertyServer = AccessController.doPrivileged(
+                PropertiesHelper.getSystemProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_SERVER));
+
+        final RuntimeType runtimeType = config.getRuntimeType();
+
+        boolean bindingDisabledBySystem = PropertiesHelper.isPropertyOrNotSet(bindingDisabledBySystemProperty)
+                || (runtimeType == RuntimeType.CLIENT
+                    && PropertiesHelper.isPropertyOrNotSet(bindingDisabledBySystemPropertyClient))
+                || (runtimeType == RuntimeType.SERVER
+                    && PropertiesHelper.isPropertyOrNotSet(bindingDisabledBySystemPropertyServer));
+
+        /* Or config property */
+        final Boolean bindingDisabled = CommonProperties.getValue(config.getProperties(), runtimeType,
+                CommonProperties.JSON_BINDING_FEATURE_DISABLE, Boolean.class);
+
+        /* Config property takes precedence */
+        if ((bindingDisabledBySystem && !Boolean.FALSE.equals(bindingDisabled)) || Boolean.TRUE.equals(bindingDisabled)) {
             return false;
         }
 
+        final Set<String> disabledPackageNames = new HashSet<>();
+
+        /* Only a certain package names */
+        final String bindingDisabledPackageBySystemProperty = RuntimeType.SERVER == runtimeType
+                ? AccessController.doPrivileged(PropertiesHelper.getSystemProperty(
+                        CommonProperties.JSON_BINDING_FEATURE_DISABLE_APPLICATION))
+                : null;
+
+        final String bindingDisabledPackage = RuntimeType.SERVER == runtimeType
+                ? CommonProperties.getValue(config.getProperties(), runtimeType,
+                        CommonProperties.JSON_BINDING_FEATURE_DISABLE_APPLICATION, String.class)
+                : null;
+
+        separatePackageNames(disabledPackageNames, bindingDisabledPackageBySystemProperty);
+        separatePackageNames(disabledPackageNames, bindingDisabledPackage);
+
+        if (!disabledPackageNames.isEmpty() && !Boolean.FALSE.equals(bindingDisabled)) {
+            try {
+                Application app = null;
+                if (InjectionManagerSupplier.class.isInstance(context)) {
+                    app = ((InjectionManagerSupplier) context).getInjectionManager().getInstance(Application.class);
+                    if (app != null) {
+                        while (ApplicationSupplier.class.isInstance(app) && ((ApplicationSupplier) app).getApplication() != app) {
+                            app = ((ApplicationSupplier) app).getApplication();
+                        }
+                        for (String disabledPackageName : disabledPackageNames) {
+                            if (app.getClass().getName().startsWith(disabledPackageName)) {
+                                return false;
+                            }
+                        }
+                    }
+                }
+                if (app == null) {
+                    LOGGER.warning(LocalizationMessages.ERROR_JSONB_DETECTING_APPLICATION(
+                            LocalizationMessages.ERROR_JSONB_APPLICATION_NOT_FOUND()));
+                }
+            } catch (Throwable throwable) {
+                LOGGER.warning(LocalizationMessages.ERROR_JSONB_DETECTING_APPLICATION(throwable.getMessage()));
+            }
+        }
+
+        // ---- End of disabling for compatibility with Pre JAX-RS 2.1 Jersey.
+
         final String jsonFeature = CommonProperties.getValue(
                 config.getProperties(),
                 config.getRuntimeType(),
@@ -79,4 +156,12 @@
 
         return true;
     }
+
+    private static void separatePackageNames(Set<String> set, String packages) {
+        if (packages != null) {
+            for (String packageName : packages.split(",")) {
+                set.add(packageName.trim());
+            }
+        }
+    }
 }
diff --git a/media/json-binding/src/main/resources/org/glassfish/jersey/jsonb/localization.properties b/media/json-binding/src/main/resources/org/glassfish/jersey/jsonb/localization.properties
index 4eee493..371e641 100644
--- a/media/json-binding/src/main/resources/org/glassfish/jersey/jsonb/localization.properties
+++ b/media/json-binding/src/main/resources/org/glassfish/jersey/jsonb/localization.properties
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2017, 2024 Oracle and/or its affiliates. All rights reserved.
 #
 # This program and the accompanying materials are made available under the
 # terms of the Eclipse Public License v. 2.0, which is available at
@@ -14,6 +14,8 @@
 # SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 #
 
+error.jsonb.application.not.found=Application not found.
+error.jsonb.detecting.application=JSON-B could not detect the application name: {0}.
 error.jsonb.serialization=Error writing JSON-B serialized object.
 error.jsonb.deserialization=Error deserializing object from entity stream.
 error.jsonb.emptystream=JSON-B cannot parse empty input stream.
diff --git a/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonbDisabledTest.java b/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonbDisabledTest.java
index 4882fe8..c2b6377 100644
--- a/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonbDisabledTest.java
+++ b/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonbDisabledTest.java
@@ -17,6 +17,10 @@
 package org.glassfish.jersey.jsonb.internal;
 
 import org.glassfish.jersey.CommonProperties;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.InjectionManagerSupplier;
+import org.glassfish.jersey.internal.inject.Injections;
 import org.glassfish.jersey.jsonb.JsonBindingFeature;
 import org.glassfish.jersey.model.internal.CommonConfig;
 import org.glassfish.jersey.model.internal.ComponentBag;
@@ -24,6 +28,7 @@
 import org.junit.jupiter.api.Test;
 
 import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Application;
 import javax.ws.rs.core.FeatureContext;
 import java.lang.reflect.Proxy;
 import java.util.concurrent.atomic.AtomicReference;
@@ -32,24 +37,177 @@
     @Test
     public void testDisabled() {
         AtomicReference<CommonConfig> configReference = new AtomicReference<>();
-        FeatureContext featureContext1 = (FeatureContext) Proxy.newProxyInstance(
-                getClass().getClassLoader(),
-                new Class[] {FeatureContext.class}, (proxy, method, args) -> {
-                    switch (method.getName()) {
-                        case "getConfiguration":
-                            return configReference.get();
-                    }
-                    return null;
-                });
+        FeatureContext featureContext1 = featureContextForConfig(configReference);
 
         CommonConfig config1 = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
         configReference.set(config1);
         Assertions.assertTrue(new JsonBindingFeature().configure(featureContext1));
 
-
         CommonConfig config2 = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
         config2.property(CommonProperties.JSON_BINDING_FEATURE_DISABLE, true);
         configReference.set(config2);
         Assertions.assertFalse(new JsonBindingFeature().configure(featureContext1));
     }
+
+    @Test
+    public void testDisabledBySystemProperty() {
+        AtomicReference<CommonConfig> configReference = new AtomicReference<>();
+        FeatureContext featureContext1 = featureContextForConfig(configReference);
+
+        CommonConfig config1 = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
+        configReference.set(config1);
+        Assertions.assertTrue(new JsonBindingFeature().configure(featureContext1));
+
+        CommonConfig config2 = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
+        System.setProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_SERVER, "true");
+        configReference.set(config2);
+        Assertions.assertFalse(new JsonBindingFeature().configure(featureContext1));
+        System.clearProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_SERVER);
+    }
+
+    @Test
+    public void testDisabledBySystemPropertyOverridenByConfigProperty() {
+        AtomicReference<CommonConfig> configReference = new AtomicReference<>();
+        FeatureContext featureContext1 = featureContextForConfig(configReference);
+
+        CommonConfig config1 = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
+        configReference.set(config1);
+        Assertions.assertTrue(new JsonBindingFeature().configure(featureContext1));
+
+        CommonConfig config2 = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
+        System.setProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE, "true");
+        config2.property(CommonProperties.JSON_BINDING_FEATURE_DISABLE, false);
+        configReference.set(config2);
+        Assertions.assertTrue(new JsonBindingFeature().configure(featureContext1));
+        System.clearProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE);
+    }
+
+    @Test
+    public void testDisabledByPropertyApplicationPackage() {
+        Application application = new Application() {
+        };
+        InjectionManager injectionManager = Injections.createInjectionManager();
+        injectionManager.register(new AbstractBinder() {
+            @Override
+            protected void configure() {
+                bind(application).to(Application.class);
+            }
+        });
+        AtomicReference<InjectionManager> injectionManagerReference = new AtomicReference<>(injectionManager);
+        AtomicReference<CommonConfig> configReference = new AtomicReference<>();
+        FeatureContext featureContext1 = featureContextForConfig(configReference, injectionManagerReference);
+
+        CommonConfig config1 = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
+        configReference.set(config1);
+        Assertions.assertTrue(new JsonBindingFeature().configure(featureContext1));
+
+        System.setProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_APPLICATION,
+                "some.does.not.matter, org.glassfish.jersey.jsonb.internal");
+        CommonConfig config2 = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
+        configReference.set(config2);
+        Assertions.assertFalse(new JsonBindingFeature().configure(featureContext1));
+        System.clearProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_APPLICATION);
+    }
+
+    @Test
+    public void disableOnClientOnlyTest() {
+        AtomicReference<CommonConfig> configReference = new AtomicReference<>();
+        FeatureContext featureContext1 = featureContextForConfig(configReference);
+
+        CommonConfig config1 = new CommonConfig(RuntimeType.CLIENT, ComponentBag.INCLUDE_ALL);
+        configReference.set(config1);
+        Assertions.assertTrue(new JsonBindingFeature().configure(featureContext1));
+
+        config1.property(CommonProperties.JSON_BINDING_FEATURE_DISABLE_SERVER, true);
+        Assertions.assertTrue(new JsonBindingFeature().configure(featureContext1));
+
+        config1.property(CommonProperties.JSON_BINDING_FEATURE_DISABLE_CLIENT, true);
+        Assertions.assertFalse(new JsonBindingFeature().configure(featureContext1));
+
+        CommonConfig config2 = new CommonConfig(RuntimeType.CLIENT, ComponentBag.INCLUDE_ALL);
+        System.setProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_SERVER, "true");
+        configReference.set(config2);
+        Assertions.assertTrue(new JsonBindingFeature().configure(featureContext1));
+
+        System.setProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_CLIENT, "true");
+        Assertions.assertFalse(new JsonBindingFeature().configure(featureContext1));
+
+        System.clearProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_SERVER);
+        System.clearProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_CLIENT);
+    }
+
+    @Test
+    public void testDisabledBySystemPropertyApplicationPackage() {
+        Application application = new Application() {
+        };
+        InjectionManager injectionManager = Injections.createInjectionManager();
+        injectionManager.register(new AbstractBinder() {
+            @Override
+            protected void configure() {
+                bind(application).to(Application.class);
+            }
+        });
+        AtomicReference<InjectionManager> injectionManagerReference = new AtomicReference<>(injectionManager);
+        AtomicReference<CommonConfig> configReference = new AtomicReference<>();
+        FeatureContext featureContext1 = featureContextForConfig(configReference, injectionManagerReference);
+
+        System.setProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_APPLICATION,
+                "some.does.not.matter, org.glassfish.jersey.jsonb.internal");
+
+        CommonConfig config1 = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
+        configReference.set(config1);
+        Assertions.assertFalse(new JsonBindingFeature().configure(featureContext1));
+
+        CommonConfig config2 = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
+        configReference.set(config2);
+        Assertions.assertFalse(new JsonBindingFeature().configure(featureContext1));
+        System.clearProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_APPLICATION);
+    }
+
+    @Test
+    public void testDisabledBySystemPropertyApplicationPackageEnabledByProperty() {
+        Application application = new Application() {
+        };
+        InjectionManager injectionManager = Injections.createInjectionManager();
+        injectionManager.register(new AbstractBinder() {
+            @Override
+            protected void configure() {
+                bind(application).to(Application.class);
+            }
+        });
+        AtomicReference<InjectionManager> injectionManagerReference = new AtomicReference<>(injectionManager);
+        AtomicReference<CommonConfig> configReference = new AtomicReference<>();
+        FeatureContext featureContext1 = featureContextForConfig(configReference, injectionManagerReference);
+
+        CommonConfig config1 = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
+        configReference.set(config1);
+        Assertions.assertTrue(new JsonBindingFeature().configure(featureContext1));
+
+        System.setProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_APPLICATION,
+                "some.does.not.matter, org.glassfish.jersey.jsonb.internal");
+
+        CommonConfig config2 = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
+        config2.property(CommonProperties.JSON_BINDING_FEATURE_DISABLE, Boolean.FALSE);
+        configReference.set(config2);
+        Assertions.assertTrue(new JsonBindingFeature().configure(featureContext1));
+        System.clearProperty(CommonProperties.JSON_BINDING_FEATURE_DISABLE_APPLICATION);
+    }
+
+    private static FeatureContext featureContextForConfig(AtomicReference<CommonConfig> configReference) {
+        return featureContextForConfig(configReference, new AtomicReference<>());
+    }
+    private static FeatureContext featureContextForConfig(AtomicReference<CommonConfig> configReference,
+                                                          AtomicReference<InjectionManager> injectionManager) {
+        return (FeatureContext) Proxy.newProxyInstance(
+                JsonbDisabledTest.class.getClassLoader(),
+                new Class[] {FeatureContext.class, InjectionManagerSupplier.class}, (proxy, method, args) -> {
+                    switch (method.getName()) {
+                        case "getConfiguration":
+                            return configReference.get();
+                        case "getInjectionManager":
+                            return injectionManager.get();
+                    }
+                    return null;
+                });
+    }
 }