feat/configuration-contains-value (#412)

* feat: contains value initial implementation

* test: tests adjusted

* chore: remove temp class

* chore: lint fix

* fix: MapConfiguration containsValue

test: moved test

* chore: minor cleanup, renaming

* test: improved err message

* fix: moved contains impl to AbstractConfiguration

* change: simplified contains in AbstractConfiguration.java

* change: adjusted containsValueInternal in DatabaseConfiguration

test: containsValue() in TestDatabaseConfiguration

* docs: add javadoc

* fix: incompatibility issue

* docs: minor javadoc update

* test: nullity tests

* fix: add null validation to contains

test: nullity test adjusted

* docs: javadoc contains in AbstractConfiguration

* docs: javadoc adaption

* feat: add impl to containsValue in ImmutableConfiguration

chore: add final to contains in AbstractConfiguration
chore: remove white space in DatabaseConfiguration
chore: add missing public and final to TestAbstractConfiguration
test: given existing value test containsValue with default impl

* docs: javadoc NonCloneableConfiguration

* docs: complete javadocs

* test: adjusted containsValue tests, test each containsValue implementation

* test: remove string messages from tests

* chore: changed tests signature names

* docs: adjusted javadoc

* docs: adjusted since javadoc version

* docs: add missing javadoc

* workflows update

* Revert "workflows update"

This reverts commit 56fafc9e972aa901c34c3157d9ea72537db8b1b4.

* Add Javadoc since tag

---------

Co-authored-by: Gary Gregory <garydgregory@users.noreply.github.com>
diff --git a/src/main/java/org/apache/commons/configuration2/AbstractConfiguration.java b/src/main/java/org/apache/commons/configuration2/AbstractConfiguration.java
index e0c0ddc..ba32c59 100644
--- a/src/main/java/org/apache/commons/configuration2/AbstractConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/AbstractConfiguration.java
@@ -420,6 +420,20 @@
     }
 
     /**
+     * {@inheritDoc} This implementation handles synchronization and delegates to {@code containsKeyInternal()}.
+     * @since 2.11.0
+     */
+    @Override
+    public final boolean containsValue(final String value) {
+        beginRead(false);
+        try {
+            return containsValueInternal(value);
+        } finally {
+            endRead();
+        }
+    }
+
+    /**
      * Actually checks whether the specified key is contained in this configuration. This method is called by
      * {@code containsKey()}. It has to be defined by concrete subclasses.
      *
@@ -430,6 +444,22 @@
     protected abstract boolean containsKeyInternal(String key);
 
     /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first
+     * match but may be more expensive than the {@link #containsKeyInternal containsKey} method.
+     * <p>The implementation of this method will be different depending on the type of Configuration used.</p>
+     *
+     * <p>Note that this method is identical in functionality to
+     * {@link #containsValue containsValue}, (which is part of the {@link ImmutableConfiguration} interface).</p>
+     *
+     * @param value a value to search for
+     * @return {@code true} if and only if some key maps to the {@code value} argument in this hashtable as determined
+     * by the {@code equals} method; {@code false} otherwise.
+     * @throws NullPointerException if the value is {@code null}
+     * @since 2.11.0
+     */
+    protected abstract boolean containsValueInternal(String value);
+
+    /**
      * Helper method for obtaining a property value with a type conversion.
      *
      * @param <T> the target type of the conversion
@@ -1517,6 +1547,23 @@
         return size;
     }
 
+    /**
+     * Checks if the specified value exists in the properties structure mapped by the provided keys.
+     *
+     * @param keys an Iterator of String keys to search for the value
+     * @param value the String value to search for in the properties
+     * @return true if the value is found in the properties, false otherwise
+     * @since 2.11.0
+     */
+    protected boolean contains(final Iterator<String> keys, final String value) {
+        while (keys.hasNext()) {
+            if (value.equals(getProperty(keys.next()))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public Configuration subset(final String prefix) {
         return new SubsetConfiguration(this, prefix, ".");
diff --git a/src/main/java/org/apache/commons/configuration2/AbstractHierarchicalConfiguration.java b/src/main/java/org/apache/commons/configuration2/AbstractHierarchicalConfiguration.java
index 2093ff0..0f5edb8 100644
--- a/src/main/java/org/apache/commons/configuration2/AbstractHierarchicalConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/AbstractHierarchicalConfiguration.java
@@ -456,6 +456,16 @@
     }
 
     /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first
+     * match but may be more expensive than the containsKey method.
+     * @since 2.11.0
+     */
+    @Override
+    protected boolean containsValueInternal(final String value) {
+        return contains(getKeys(), value);
+    }
+
+    /**
      * Helper method for resolving the specified key.
      *
      * @param key the key
diff --git a/src/main/java/org/apache/commons/configuration2/BaseConfiguration.java b/src/main/java/org/apache/commons/configuration2/BaseConfiguration.java
index da2750a..f6697c3 100644
--- a/src/main/java/org/apache/commons/configuration2/BaseConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/BaseConfiguration.java
@@ -137,6 +137,16 @@
     }
 
     /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first
+     * match but may be more expensive than the containsKey method.
+     * @since 2.11.0
+     */
+    @Override
+    protected boolean containsValueInternal(String value) {
+        return store.containsValue(value);
+    }
+
+    /**
      * Gets the list of the keys contained in the configuration repository.
      *
      * @return An Iterator.
diff --git a/src/main/java/org/apache/commons/configuration2/CompositeConfiguration.java b/src/main/java/org/apache/commons/configuration2/CompositeConfiguration.java
index e7eba9f..c70ba27 100644
--- a/src/main/java/org/apache/commons/configuration2/CompositeConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/CompositeConfiguration.java
@@ -299,6 +299,16 @@
     }
 
     /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first
+     * match but may be more expensive than the containsKey method.
+     * @since 2.11.0
+     */
+    @Override
+    protected boolean containsValueInternal(final String value) {
+        return configList.stream().anyMatch(config -> config.containsValue(value));
+    }
+
+    /**
      * Gets the configuration at the specified index.
      *
      * @param index The index of the configuration to retrieve
diff --git a/src/main/java/org/apache/commons/configuration2/DataConfiguration.java b/src/main/java/org/apache/commons/configuration2/DataConfiguration.java
index ffd73c8..d63fc39 100644
--- a/src/main/java/org/apache/commons/configuration2/DataConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/DataConfiguration.java
@@ -214,6 +214,16 @@
     }
 
     /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first
+     * match but may be more expensive than the containsKey method.
+     * @since 2.11.0
+     */
+    @Override
+    protected boolean containsValueInternal(final String value) {
+        return configuration.containsValue(value);
+    }
+
+    /**
      * Gets an array of BigDecimals associated with the given configuration key. If the key doesn't map to an existing object
      * an empty array is returned.
      *
diff --git a/src/main/java/org/apache/commons/configuration2/DatabaseConfiguration.java b/src/main/java/org/apache/commons/configuration2/DatabaseConfiguration.java
index e454795..6517e81 100644
--- a/src/main/java/org/apache/commons/configuration2/DatabaseConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/DatabaseConfiguration.java
@@ -479,6 +479,25 @@
     }
 
     /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first
+     * match but may be more expensive than the containsKey method.
+     * @since 2.11.0
+     */
+    @Override
+    protected boolean containsValueInternal(final String value) {
+        final AbstractJdbcOperation<Boolean> op = new AbstractJdbcOperation<Boolean>(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, value, null) {
+            @Override
+            protected Boolean performOperation() throws SQLException {
+                try (ResultSet rs = openResultSet(String.format(SQL_GET_PROPERTY, table, valueColumn), false, value)) {
+                    return rs.next();
+                }
+            }
+        };
+        final Boolean result = op.execute();
+        return result != null && result.booleanValue();
+    }
+
+    /**
      * Extracts the value of a property from the given result set. The passed in {@code ResultSet} was created by a SELECT
      * statement on the underlying database table. This implementation reads the value of the column determined by the
      * {@code valueColumn} property. Normally the contained value is directly returned. However, if it is of type
diff --git a/src/main/java/org/apache/commons/configuration2/DynamicCombinedConfiguration.java b/src/main/java/org/apache/commons/configuration2/DynamicCombinedConfiguration.java
index 1acb20b..5c4a6d3 100644
--- a/src/main/java/org/apache/commons/configuration2/DynamicCombinedConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/DynamicCombinedConfiguration.java
@@ -359,6 +359,16 @@
     }
 
     /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first
+     * match but may be more expensive than the containsKey method.
+     * @since 2.11.0
+     */
+    @Override
+    protected boolean containsValueInternal(String value) {
+        return this.getCurrentConfig().contains(getKeys(), value);
+    }
+
+    /**
      * Creates a new, uninitialized child configuration.
      *
      * @return the new child configuration
diff --git a/src/main/java/org/apache/commons/configuration2/ImmutableConfiguration.java b/src/main/java/org/apache/commons/configuration2/ImmutableConfiguration.java
index a765933..57c97ca 100644
--- a/src/main/java/org/apache/commons/configuration2/ImmutableConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/ImmutableConfiguration.java
@@ -63,6 +63,24 @@
     boolean containsKey(String key);
 
     /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first
+     * match but may be more expensive than the {@link #containsKey containsKey} method.
+     *
+     * @param value value whose presence in this configuration is to be tested
+     * @return {@code true} if this configuration maps one or more keys to the specified value, false otherwise.
+     * @since 2.11.0
+     */
+    default boolean containsValue(final String value) {
+        final Iterator<String> keys = getKeys();
+        while (keys.hasNext()) {
+            if (value.equals(getProperty(keys.next()))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Gets an object of the specified type associated with the given configuration key. If the key doesn't map to an
      * existing object, the method returns null unless {@link AbstractConfiguration#isThrowExceptionOnMissing()} is set to
      * {@code true}.
diff --git a/src/main/java/org/apache/commons/configuration2/JNDIConfiguration.java b/src/main/java/org/apache/commons/configuration2/JNDIConfiguration.java
index 07bc02d..9cc1a27 100644
--- a/src/main/java/org/apache/commons/configuration2/JNDIConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/JNDIConfiguration.java
@@ -148,6 +148,16 @@
     }
 
     /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first match
+     * but may be more expensive than the containsKey method.
+     * @since 2.11.0
+     */
+    @Override
+    protected boolean containsValueInternal(final String value) {
+        return contains(getKeys(), value);
+    }
+
+    /**
      * Gets the base context with the prefix applied.
      *
      * @return the base context
diff --git a/src/main/java/org/apache/commons/configuration2/MapConfiguration.java b/src/main/java/org/apache/commons/configuration2/MapConfiguration.java
index d4cb8d0..be0df0a 100644
--- a/src/main/java/org/apache/commons/configuration2/MapConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/MapConfiguration.java
@@ -172,6 +172,16 @@
         return map.containsKey(key);
     }
 
+    /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first match
+     * but may be more expensive than the containsKey method.
+     * @since 2.11.0
+     */
+    @Override
+    protected boolean containsValueInternal(final String value) {
+        return map.containsValue(value);
+    }
+
     @Override
     protected Iterator<String> getKeysInternal() {
         return map.keySet().iterator();
diff --git a/src/main/java/org/apache/commons/configuration2/PatternSubtreeConfigurationWrapper.java b/src/main/java/org/apache/commons/configuration2/PatternSubtreeConfigurationWrapper.java
index 79c0096..94a678f 100644
--- a/src/main/java/org/apache/commons/configuration2/PatternSubtreeConfigurationWrapper.java
+++ b/src/main/java/org/apache/commons/configuration2/PatternSubtreeConfigurationWrapper.java
@@ -131,6 +131,16 @@
     }
 
     /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first
+     * match but may be more expensive than the containsKey method.
+     * @since 2.11.0
+     */
+    @Override
+    protected boolean containsValueInternal(String value) {
+        return config.containsValue(value);
+    }
+
+    /**
      * Returns the wrapped configuration as a {@code FileBased} object. If this cast is not possible, an exception is
      * thrown.
      *
diff --git a/src/main/java/org/apache/commons/configuration2/SubsetConfiguration.java b/src/main/java/org/apache/commons/configuration2/SubsetConfiguration.java
index 9396082..1f176fb 100644
--- a/src/main/java/org/apache/commons/configuration2/SubsetConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/SubsetConfiguration.java
@@ -132,6 +132,16 @@
     }
 
     /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first match
+     * but may be more expensive than the containsKey method.
+     * @since 2.11.0
+     */
+    @Override
+    protected boolean containsValueInternal(final String value) {
+        return parent.containsValue(value);
+    }
+
+    /**
      * Gets the key in the subset configuration associated to the specified key in the parent configuration.
      *
      * @param key The key in the parent configuration.
diff --git a/src/main/java/org/apache/commons/configuration2/web/BaseWebConfiguration.java b/src/main/java/org/apache/commons/configuration2/web/BaseWebConfiguration.java
index 17c12c7..34a0a2c 100644
--- a/src/main/java/org/apache/commons/configuration2/web/BaseWebConfiguration.java
+++ b/src/main/java/org/apache/commons/configuration2/web/BaseWebConfiguration.java
@@ -69,6 +69,16 @@
     }
 
     /**
+     * Tests whether this configuration contains one or more matches to this value. This operation stops at first match
+     * but may be more expensive than the containsKey method
+     * @since 2.11.0
+     */
+    @Override
+    protected boolean containsValueInternal(final String value) {
+        return contains(getKeys(), value);
+    }
+
+    /**
      * Takes care of list delimiters in property values. This method checks if delimiter parsing is enabled and the passed
      * in value contains a delimiter character. If this is the case, a split operation is performed.
      *
diff --git a/src/test/java/org/apache/commons/configuration2/NonCloneableConfiguration.java b/src/test/java/org/apache/commons/configuration2/NonCloneableConfiguration.java
index b8899a6..552c3e5 100644
--- a/src/test/java/org/apache/commons/configuration2/NonCloneableConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/NonCloneableConfiguration.java
@@ -47,6 +47,16 @@
 
     /**
      * Dummy implementation of this method.
+     *
+     * @since 2.11.0
+     */
+    @Override
+    protected boolean containsValueInternal(String value) {
+        return false;
+    }
+
+    /**
+     * Dummy implementation of this method.
      */
     @Override
     protected Iterator<String> getKeysInternal() {
diff --git a/src/test/java/org/apache/commons/configuration2/TestAbstractConfiguration.java b/src/test/java/org/apache/commons/configuration2/TestAbstractConfiguration.java
index b3e3ba2..12f6886 100644
--- a/src/test/java/org/apache/commons/configuration2/TestAbstractConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/TestAbstractConfiguration.java
@@ -79,6 +79,35 @@
     }
 
     @Test
+    public void testContainsValue() {
+        final Configuration config = getConfiguration();
+        assertTrue(config.containsValue("value1"));
+        assertFalse(config.containsValue("value99999"));
+    }
+
+    @Test
+    public void testContains() {
+        final AbstractConfiguration config = getConfiguration();
+        assertTrue(config.contains(config.getKeys(), "value1"));
+        assertFalse(config.contains(config.getKeys(), "value99999"));
+    }
+
+    @Test
+    public void givenNullIteratorTestContains() {
+        final AbstractConfiguration config = getConfiguration();
+
+        assertThrows(NullPointerException.class, () -> config.contains(null, "value1"));
+    }
+
+    @Test
+    public void givenNullValueTestContains() {
+        AbstractConfiguration config = getConfiguration();
+        Iterator<String> keys = config.getKeys();
+
+        assertThrows(NullPointerException.class, () -> config.contains(keys, null));
+    }
+
+    @Test
     public void testClearProperty() {
         final Configuration config = getConfiguration();
         config.clearProperty("key2");
diff --git a/src/test/java/org/apache/commons/configuration2/TestAbstractConfigurationBasicFeatures.java b/src/test/java/org/apache/commons/configuration2/TestAbstractConfigurationBasicFeatures.java
index 2761c4e..d61f67d 100644
--- a/src/test/java/org/apache/commons/configuration2/TestAbstractConfigurationBasicFeatures.java
+++ b/src/test/java/org/apache/commons/configuration2/TestAbstractConfigurationBasicFeatures.java
@@ -94,6 +94,11 @@
         }
 
         @Override
+        protected boolean containsValueInternal(String value) {
+            return config.containsValue(value);
+        }
+
+        @Override
         protected Iterator<String> getKeysInternal() {
             return config.getKeys();
         }
diff --git a/src/test/java/org/apache/commons/configuration2/TestAbstractHierarchicalConfiguration.java b/src/test/java/org/apache/commons/configuration2/TestAbstractHierarchicalConfiguration.java
index 247a74b..2eee1a2 100644
--- a/src/test/java/org/apache/commons/configuration2/TestAbstractHierarchicalConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/TestAbstractHierarchicalConfiguration.java
@@ -941,4 +941,9 @@
     public void testSize() {
         assertEquals(2, config.size());
     }
+
+    @Test
+    public void testContainsValue() {
+        assertThrows(NullPointerException.class, () -> config.containsValue(null));
+    }
 }
diff --git a/src/test/java/org/apache/commons/configuration2/TestBaseConfiguration.java b/src/test/java/org/apache/commons/configuration2/TestBaseConfiguration.java
index b757561..be797a3 100644
--- a/src/test/java/org/apache/commons/configuration2/TestBaseConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/TestBaseConfiguration.java
@@ -718,4 +718,9 @@
     public void testThrowExceptionOnMissing() {
         assertTrue(config.isThrowExceptionOnMissing());
     }
+
+    @Test
+    public void testContainsValue() {
+        assertFalse(config.containsValue(null));
+    }
 }
diff --git a/src/test/java/org/apache/commons/configuration2/TestDataConfiguration.java b/src/test/java/org/apache/commons/configuration2/TestDataConfiguration.java
index dfdec4c..aff406b 100644
--- a/src/test/java/org/apache/commons/configuration2/TestDataConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/TestDataConfiguration.java
@@ -1965,4 +1965,10 @@
     public void testNullConfiguration() {
         assertThrows(NullPointerException.class, () -> new DataConfiguration(null));
     }
+
+    @Test
+    public void testContainsValue() {
+        final Configuration config = conf.getConfiguration();
+        assertFalse(config.containsValue(null));
+    }
 }
diff --git a/src/test/java/org/apache/commons/configuration2/TestDatabaseConfiguration.java b/src/test/java/org/apache/commons/configuration2/TestDatabaseConfiguration.java
index e2a6df2..c4aa9f7 100644
--- a/src/test/java/org/apache/commons/configuration2/TestDatabaseConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/TestDatabaseConfiguration.java
@@ -173,6 +173,24 @@
         assertTrue(config.containsKey("boolean"));
     }
 
+    @Test
+    void testContainsValue() throws ConfigurationException {
+        final DatabaseConfiguration config = helper.setUpConfig();
+        config.addPropertyDirect("test", "test1");
+
+        assertTrue(config.containsValue("test1"));
+        assertFalse(config.containsValue("test9999"));
+    }
+
+    @Test
+    void containsValueInternal() throws ConfigurationException {
+        final DatabaseConfiguration config = helper.setUpConfig();
+        config.addPropertyDirect("test", "test1");
+
+        assertTrue(config.containsValueInternal("test1"));
+        assertFalse(config.containsValue("test9999"));
+    }
+
     /**
      * Tests whether a commit is performed after a property was added.
      */
diff --git a/src/test/java/org/apache/commons/configuration2/TestDefaultImmutableConfiguration.java b/src/test/java/org/apache/commons/configuration2/TestDefaultImmutableConfiguration.java
index a758332..598b6f9 100644
--- a/src/test/java/org/apache/commons/configuration2/TestDefaultImmutableConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/TestDefaultImmutableConfiguration.java
@@ -19,6 +19,7 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
@@ -217,8 +218,7 @@
 
         @Override
         public Iterator<String> getKeys() {
-            // Super is not a default method.
-            return null;
+            return this.map.keySet().iterator();
         }
 
         @Override
@@ -363,6 +363,12 @@
     }
 
     @Test
+    public void testContainsValueDefaultImplementation() {
+        config.map.put("test", "213123");
+        assertTrue(config.containsValue("213123"));
+    }
+
+    @Test
     public void testGetDurationUnknown() {
         assertThrows(NoSuchElementException.class, () -> config.getDuration("numberNotInConfig"));
     }
diff --git a/src/test/java/org/apache/commons/configuration2/TestMapConfiguration.java b/src/test/java/org/apache/commons/configuration2/TestMapConfiguration.java
index d0f22d5..39675c5 100644
--- a/src/test/java/org/apache/commons/configuration2/TestMapConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/TestMapConfiguration.java
@@ -162,4 +162,9 @@
         assertThrows(NullPointerException.class, () -> new MapConfiguration((Map) null));
         assertThrows(NullPointerException.class, () -> new MapConfiguration((Properties) null));
     }
+
+    @Test
+    public void testContainsValue() {
+        assertFalse(getConfiguration().containsValue(null), "should return false");
+    }
 }
diff --git a/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java b/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java
index 6ccc165..56300ef 100644
--- a/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/TestPropertiesConfiguration.java
@@ -73,6 +73,7 @@
 import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl;
 import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
 import org.apache.commons.configuration2.builder.combined.CombinedConfigurationBuilder;
+import org.apache.commons.configuration2.builder.fluent.Configurations;
 import org.apache.commons.configuration2.builder.fluent.Parameters;
 import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
 import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler;
@@ -1214,6 +1215,15 @@
         assertEquals(Arrays.asList("C:\\path1\\", "C:\\path2\\", "C:\\path3\\complex\\test\\"), list);
     }
 
+    @Test
+    void testConfiguration() throws ConfigurationException {
+        Configurations configManager = new Configurations();
+        Configuration config = configManager.properties("src/test/resources/config/test.properties");
+
+        assertTrue(config.containsValue("jndivalue2"));
+        assertFalse(config.containsValue("notFound"));
+    }
+
     /**
      * Tests the propertyLoaded() method for a simple property.
      */
diff --git a/src/test/java/org/apache/commons/configuration2/web/TestAppletConfiguration.java b/src/test/java/org/apache/commons/configuration2/web/TestAppletConfiguration.java
index 17ddb4c..b564bba 100644
--- a/src/test/java/org/apache/commons/configuration2/web/TestAppletConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/web/TestAppletConfiguration.java
@@ -24,6 +24,7 @@
 
 import org.apache.commons.configuration2.AbstractConfiguration;
 import org.apache.commons.configuration2.BaseConfiguration;
+import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.configuration2.MapConfiguration;
 import org.apache.commons.configuration2.TestAbstractConfiguration;
 import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
@@ -113,4 +114,10 @@
             assertThrows(UnsupportedOperationException.class, super::testClearProperty);
         }
     }
+
+    @Test
+    public void testContainsValue() {
+        final Configuration config = getConfiguration();
+        assertThrows(NullPointerException.class, () -> config.containsValue(null));
+    }
 }
diff --git a/src/test/java/org/apache/commons/configuration2/web/TestServletConfiguration.java b/src/test/java/org/apache/commons/configuration2/web/TestServletConfiguration.java
index a741bba..d333eab 100644
--- a/src/test/java/org/apache/commons/configuration2/web/TestServletConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/web/TestServletConfiguration.java
@@ -28,6 +28,7 @@
 import javax.servlet.http.HttpServlet;
 
 import org.apache.commons.configuration2.AbstractConfiguration;
+import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.configuration2.TestAbstractConfiguration;
 import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
 import org.junit.jupiter.api.Test;
@@ -97,4 +98,10 @@
     public void testClearProperty() {
         assertThrows(UnsupportedOperationException.class, super::testClearProperty);
     }
+
+    @Test
+    public void testContainsValue() {
+        final Configuration config = getConfiguration();
+        assertThrows(NullPointerException.class, () -> config.containsValue(null));
+    }
 }
diff --git a/src/test/java/org/apache/commons/configuration2/web/TestServletContextConfiguration.java b/src/test/java/org/apache/commons/configuration2/web/TestServletContextConfiguration.java
index f62803e..8cd6d4f 100644
--- a/src/test/java/org/apache/commons/configuration2/web/TestServletContextConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/web/TestServletContextConfiguration.java
@@ -29,6 +29,7 @@
 import javax.servlet.http.HttpServlet;
 
 import org.apache.commons.configuration2.AbstractConfiguration;
+import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.configuration2.TestAbstractConfiguration;
 import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
 import org.junit.jupiter.api.Test;
@@ -107,4 +108,10 @@
     public void testClearProperty() {
         assertThrows(UnsupportedOperationException.class, super::testClearProperty);
     }
+
+    @Test
+    public void testContainsValue() {
+        final Configuration config = getConfiguration();
+        assertThrows(NullPointerException.class, () -> config.containsValue(null));
+    }
 }
diff --git a/src/test/java/org/apache/commons/configuration2/web/TestServletFilterConfiguration.java b/src/test/java/org/apache/commons/configuration2/web/TestServletFilterConfiguration.java
index 7341e3b..a48a65f 100644
--- a/src/test/java/org/apache/commons/configuration2/web/TestServletFilterConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/web/TestServletFilterConfiguration.java
@@ -26,6 +26,7 @@
 import javax.servlet.ServletContext;
 
 import org.apache.commons.configuration2.AbstractConfiguration;
+import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.configuration2.TestAbstractConfiguration;
 import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
 import org.junit.jupiter.api.Test;
@@ -91,4 +92,10 @@
     public void testClearProperty() {
         assertThrows(UnsupportedOperationException.class, super::testClearProperty);
     }
+
+    @Test
+    public void testContainsValue() {
+        final Configuration config = getConfiguration();
+        assertThrows(NullPointerException.class, () -> config.containsValue(null));
+    }
 }
diff --git a/src/test/java/org/apache/commons/configuration2/web/TestServletRequestConfiguration.java b/src/test/java/org/apache/commons/configuration2/web/TestServletRequestConfiguration.java
index c8492ec..dad94ef 100644
--- a/src/test/java/org/apache/commons/configuration2/web/TestServletRequestConfiguration.java
+++ b/src/test/java/org/apache/commons/configuration2/web/TestServletRequestConfiguration.java
@@ -115,4 +115,10 @@
         }
         assertEquals(expected, v);
     }
+
+    @Test
+    public void testContainsValue() {
+        final Configuration config = getConfiguration();
+        assertThrows(NullPointerException.class, () -> config.containsValue(null));
+    }
 }