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

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new dbd5ec690f Marshall module improvements
dbd5ec690f is described below

commit dbd5ec690fdcc3e5accfd4f402abe6b961474909
Author: James Bognar <[email protected]>
AuthorDate: Thu Dec 11 14:49:51 2025 -0500

    Marshall module improvements
---
 .../juneau/commons/settings/FunctionalSource.java  | 101 ++---
 .../juneau/commons/settings/FunctionalStore.java   | 174 ++++++++
 .../settings/{MapSource.java => MapStore.java}     |  56 +--
 .../juneau/commons/settings/SettingSource.java     |  73 +---
 .../juneau/commons/settings/SettingStore.java      |  78 ++++
 .../apache/juneau/commons/settings/Settings.java   | 359 +++++++++--------
 .../juneau/commons/utils/AssertionUtils.java       |  18 +
 .../juneau/commons/utils/ThrowableUtils.java       |  27 +-
 .../juneau/commons/settings/Settings_Test.java     | 441 ++++++++++++++++-----
 9 files changed, 917 insertions(+), 410 deletions(-)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalSource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalSource.java
index 11bdc62d6b..e927a21b42 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalSource.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalSource.java
@@ -16,20 +16,23 @@
  */
 package org.apache.juneau.commons.settings;
 
-import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.util.*;
 import java.util.function.*;
 
 /**
- * A read-only {@link SettingSource} implementation that delegates to a 
function.
+ * A functional interface for creating read-only {@link SettingSource} 
instances from a function.
  *
  * <p>
- * This class provides a read-only source for settings that delegates property 
lookups to a provided function.
- * It's particularly useful for wrapping existing property sources (e.g., 
{@link System#getProperty(String)},
+ * This functional interface allows you to create setting sources directly 
from lambda expressions or method references,
+ * making it easy to wrap existing property sources (e.g., {@link 
System#getProperty(String)},
  * {@link System#getenv(String)}) as {@link SettingSource} instances.
  *
+ * <p>
+ * Functional sources are read-only and do not implement {@link SettingStore}. 
If you need a writable source,
+ * use {@link MapStore} or {@link FunctionalStore} instead.
+ *
  * <h5 class='section'>Return Value Semantics:</h5>
  * <ul class='spaced-list'>
  *     <li>If the function returns <c>null</c>, this source returns 
<c>null</c> (key doesn't exist).
@@ -38,35 +41,29 @@ import java.util.function.*;
  *
  * <p>
  * Note: This source cannot distinguish between a key that doesn't exist and a 
key that exists with a null value,
- * since the function only returns a <c>String</c>. If you need to distinguish 
these cases, use {@link MapSource} instead.
+ * since the function only returns a <c>String</c>. If you need to distinguish 
these cases, use {@link MapStore} instead.
  *
  * <h5 class='section'>Example:</h5>
  * <p class='bjava'>
- *     <jc>// Create a read-only source from System.getProperty</jc>
- *     FunctionalSource <jv>sysProps</jv> = <jk>new</jk> FunctionalSource(x 
-&gt; System.getProperty(x));
+ *     <jc>// Create a read-only source directly from a lambda (returns 
Optional)</jc>
+ *     Settings.<jsf>get</jsf>().addSource(name -&gt; 
opt(System.getProperty(name)));
+ *
+ *     <jc>// Using the static factory method (takes Function&lt;String, 
String&gt;)</jc>
+ *     
Settings.<jsf>get</jsf>().addSource(FunctionalSource.<jsf>of</jsf>(System::getProperty));
  *
  *     <jc>// Create a read-only source from System.getenv</jc>
- *     FunctionalSource <jv>envVars</jv> = <jk>new</jk> FunctionalSource(x 
-&gt; System.getenv(x));
+ *     
Settings.<jsf>get</jsf>().addSource(FunctionalSource.<jsf>of</jsf>(System::getenv));
  *
- *     <jc>// Add to Settings</jc>
+ *     <jc>// Explicit creation for reuse</jc>
+ *     FunctionalSource <jv>sysProps</jv> = 
FunctionalSource.<jsf>of</jsf>(System::getProperty);
  *     Settings.<jsf>get</jsf>().addSource(<jv>sysProps</jv>);
  * </p>
  */
-public class FunctionalSource implements SettingSource {
-
-       private final Function<String,String> function;
+@FunctionalInterface
+public interface FunctionalSource extends SettingSource {
 
        /**
-        * Constructor.
-        *
-        * @param function The function to delegate property lookups to. Must 
not be <c>null</c>.
-        */
-       public FunctionalSource(Function<String,String> function) {
-               this.function = function;
-       }
-
-       /**
-        * Returns a setting by delegating to the function.
+        * Returns a setting by applying the function.
         *
         * <p>
         * If the function returns <c>null</c>, this method returns <c>null</c> 
(indicating the key doesn't exist).
@@ -76,47 +73,35 @@ public class FunctionalSource implements SettingSource {
         * @return The property value, or <c>null</c> if the function returns 
<c>null</c>.
         */
        @Override
-       public Optional<String> get(String name) {
-               var v = function.apply(name);
-               return v == null ? null : opt(v);
-       }
+       Optional<String> get(String name);
 
        /**
-        * Returns <c>false</c> since this source is read-only.
+        * Creates a functional source from a function that returns a string.
         *
-        * @return <c>false</c>
-        */
-       @Override
-       public boolean isWriteable() {
-               return false;
-       }
-
-       /**
-        * No-op since this source is read-only.
+        * <p>
+        * This is a convenience factory method for creating functional sources 
from functions that return
+        * <c>String</c> values. The function's return value is converted to an 
<c>Optional</c> as follows:
+        * <ul>
+        *      <li>If the function returns <c>null</c>, the source returns 
<c>null</c> (key doesn't exist).
+        *      <li>If the function returns a non-null value, the source 
returns <c>Optional.of(value)</c>.
+        * </ul>
         *
-        * @param name The property name (ignored).
-        * @param value The property value (ignored).
-        */
-       @Override
-       public void set(String name, String value) {
-               throw illegalArg("Attempting to set a value on a read-only 
source.");
-       }
-
-       /**
-        * No-op since this source is read-only.
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Create from a lambda</jc>
+        *      FunctionalSource <jv>source1</jv> = 
FunctionalSource.<jsf>of</jsf>(name -&gt; System.getProperty(name));
         *
-        * @param name The property name (ignored).
-        */
-       @Override
-       public void unset(String name) {
-               throw illegalArg("Attempting to unset a value on a read-only 
source.");
-       }
-
-       /**
-        * No-op since this source is read-only.
+        *      <jc>// Create from a method reference</jc>
+        *      FunctionalSource <jv>source2</jv> = 
FunctionalSource.<jsf>of</jsf>(System::getProperty);
+        * </p>
+        *
+        * @param function The function to delegate property lookups to. Must 
not be <c>null</c>.
+        * @return A new functional source instance.
         */
-       @Override
-       public void clear() {
-               throw illegalArg("Attempting to clear a read-only source.");
+       static FunctionalSource of(Function<String, String> function) {
+               return name -> {
+                       var v = function.apply(name);
+                       return v == null ? null : opt(v);
+               };
        }
 }
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalStore.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalStore.java
new file mode 100644
index 0000000000..045f82e36c
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalStore.java
@@ -0,0 +1,174 @@
+/*
+ * 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.juneau.commons.settings;
+
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.*;
+import java.util.function.*;
+
+import org.apache.juneau.commons.function.Snippet;
+
+/**
+ * A writable {@link SettingStore} implementation created from functional 
interfaces.
+ *
+ * <p>
+ * This class allows you to create writable setting stores from lambda 
expressions or method references,
+ * making it easy to wrap existing property systems (e.g., custom 
configuration systems) as
+ * {@link SettingStore} instances.
+ *
+ * <h5 class='section'>Return Value Semantics:</h5>
+ * <ul class='spaced-list'>
+ *     <li>If the reader function returns <c>null</c>, this store returns 
<c>null</c> (key doesn't exist).
+ *     <li>If the reader function returns a non-null value, this store returns 
<c>Optional.of(value)</c>.
+ * </ul>
+ *
+ * <p>
+ * Note: This store cannot distinguish between a key that doesn't exist and a 
key that exists with a null value,
+ * since the reader function only returns a <c>String</c>. If you need to 
distinguish these cases, use {@link MapStore} instead.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     <jc>// Create a writable functional store</jc>
+ *     FunctionalStore <jv>store</jv> = FunctionalStore.<jsf>of</jsf>(
+ *             System::getProperty,  <jc>// reader</jc>
+ *             (k, v) -&gt; System.setProperty(k, v),  <jc>// writer</jc>
+ *             k -&gt; System.clearProperty(k),  <jc>// unset</jc>
+ *             () -&gt; { <jc>// clear</jc>
+ *                     <jc>// Clear all properties logic</jc>
+ *             }
+ *     );
+ *
+ *     <jc>// Use it</jc>
+ *     <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
+ *     Optional&lt;String&gt; <jv>value</jv> = 
<jv>store</jv>.get(<js>"my.property"</js>);
+ *     <jv>store</jv>.unset(<js>"my.property"</js>);
+ * </p>
+ */
+public class FunctionalStore implements SettingStore {
+
+       private final Function<String, String> reader;
+       private final BiConsumer<String, String> writer;
+       private final Consumer<String> unsetter;
+       private final Snippet clearer;
+
+       /**
+        * Creates a new writable functional store.
+        *
+        * @param reader The function to read property values. Must not be 
<c>null</c>.
+        * @param writer The function to write property values. Must not be 
<c>null</c>.
+        * @param unsetter The function to remove property values. Must not be 
<c>null</c>.
+        * @param clearer The snippet to clear all property values. Must not be 
<c>null</c>.
+        */
+       public FunctionalStore(
+               Function<String, String> reader,
+               BiConsumer<String, String> writer,
+               Consumer<String> unsetter,
+               Snippet clearer
+       ) {
+               assertArgNotNull("reader", reader);
+               assertArgNotNull("writer", writer);
+               assertArgNotNull("unsetter", unsetter);
+               assertArgNotNull("clearer", clearer);
+               this.reader = reader;
+               this.writer = writer;
+               this.unsetter = unsetter;
+               this.clearer = clearer;
+       }
+
+       /**
+        * Returns a setting by applying the reader function.
+        *
+        * <p>
+        * If the reader function returns <c>null</c>, this method returns 
<c>null</c> (indicating the key doesn't exist).
+        * If the reader function returns a non-null value, this method returns 
<c>Optional.of(value)</c>.
+        *
+        * @param name The property name.
+        * @return The property value, or <c>null</c> if the reader function 
returns <c>null</c>.
+        */
+       @Override
+       public Optional<String> get(String name) {
+               var v = reader.apply(name);
+               return v == null ? null : opt(v);
+       }
+
+       /**
+        * Sets a setting by applying the writer function.
+        *
+        * @param name The property name.
+        * @param value The property value, or <c>null</c> to set an empty 
override.
+        */
+       @Override
+       public void set(String name, String value) {
+               writer.accept(name, value);
+       }
+
+       /**
+        * Removes a setting by applying the unsetter function.
+        *
+        * @param name The property name to remove.
+        */
+       @Override
+       public void unset(String name) {
+               unsetter.accept(name);
+       }
+
+       /**
+        * Clears all settings by invoking the clearer snippet.
+        *
+        * <p>
+        * If the clearer snippet throws an exception, it will be wrapped in a 
{@link RuntimeException}.
+        */
+       @Override
+       public void clear() {
+               safe(()->clearer.run());
+       }
+
+       /**
+        * Creates a writable functional store from four functions.
+        *
+        * <p>
+        * This is a convenience factory method for creating writable 
functional stores.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Create from lambdas</jc>
+        *      FunctionalStore <jv>store</jv> = FunctionalStore.<jsf>of</jsf>(
+        *              System::getProperty,
+        *              (k, v) -&gt; System.setProperty(k, v),
+        *              k -&gt; System.clearProperty(k),
+        *              () -&gt; { <jc>// Clear all properties</jc> }
+        *      );
+        * </p>
+        *
+        * @param reader The function to read property values. Must not be 
<c>null</c>.
+        * @param writer The function to write property values. Must not be 
<c>null</c>.
+        * @param unsetter The function to remove property values. Must not be 
<c>null</c>.
+        * @param clearer The snippet to clear all property values. Must not be 
<c>null</c>.
+        * @return A new writable functional store instance.
+        */
+       public static FunctionalStore of(
+               Function<String, String> reader,
+               BiConsumer<String, String> writer,
+               Consumer<String> unsetter,
+               Snippet clearer
+       ) {
+               return new FunctionalStore(reader, writer, unsetter, clearer);
+       }
+}
+
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapSource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapStore.java
similarity index 67%
rename from 
juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapSource.java
rename to 
juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapStore.java
index 986fbf8d1f..2e826da92f 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapSource.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapStore.java
@@ -16,7 +16,6 @@
  */
 package org.apache.juneau.commons.settings;
 
-import static org.apache.juneau.commons.utils.AssertionUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.util.*;
@@ -24,12 +23,12 @@ import java.util.concurrent.*;
 import java.util.concurrent.atomic.*;
 
 /**
- * A writable {@link SettingSource} implementation backed by a thread-safe map.
+ * A writable {@link SettingStore} implementation backed by a thread-safe map.
  *
  * <p>
- * This class provides a mutable source for settings that can be modified at 
runtime. It's particularly useful
- * for creating custom property sources (e.g., Spring properties, 
configuration files) that can be added to
- * {@link Settings}.
+ * This class provides a mutable store for settings that can be modified at 
runtime. It's particularly useful
+ * for creating custom property stores (e.g., Spring properties, configuration 
files) that can be added to
+ * {@link Settings} as sources.
  *
  * <h5 class='section'>Thread Safety:</h5>
  * <p>
@@ -44,29 +43,29 @@ import java.util.concurrent.atomic.*;
  *
  * <h5 class='section'>Example:</h5>
  * <p class='bjava'>
- *     <jc>// Create a source and add properties</jc>
- *     MapSource <jv>source</jv> = <jk>new</jk> MapSource();
- *     <jv>source</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
- *     <jv>source</jv>.set(<js>"another.property"</js>, 
<js>"another-value"</js>);
+ *     <jc>// Create a store and add properties</jc>
+ *     MapStore <jv>store</jv> = <jk>new</jk> MapStore();
+ *     <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
+ *     <jv>store</jv>.set(<js>"another.property"</js>, 
<js>"another-value"</js>);
  *
- *     <jc>// Add to Settings</jc>
- *     Settings.<jsf>get</jsf>().addSource(<jv>source</jv>);
+ *     <jc>// Add to Settings as a source (stores can be used as sources)</jc>
+ *     Settings.<jsf>get</jsf>().addSource(<jv>store</jv>);
  *
  *     <jc>// Override a system property with null</jc>
- *     <jv>source</jv>.set(<js>"system.property"</js>, <jk>null</jk>);
+ *     <jv>store</jv>.set(<js>"system.property"</js>, <jk>null</jk>);
  *     <jc>// get() will now return Optional.empty() for "system.property"</jc>
  *
  *     <jc>// Remove a property entirely</jc>
- *     <jv>source</jv>.unset(<js>"my.property"</js>);
+ *     <jv>store</jv>.unset(<js>"my.property"</js>);
  *     <jc>// get() will now return null for "my.property"</jc>
  * </p>
  */
-public class MapSource implements SettingSource {
+public class MapStore implements SettingStore {
 
        private final AtomicReference<Map<String,Optional<String>>> map = new 
AtomicReference<>();
 
        /**
-        * Returns a setting from this source.
+        * Returns a setting from this store.
         *
         * <p>
         * Returns <c>null</c> if the key doesn't exist in the map, or the 
stored value (which may be
@@ -80,15 +79,12 @@ public class MapSource implements SettingSource {
        public Optional<String> get(String key) {
                var m = map.get();
                if (m == null)
-                       return null; // Key not in source (map doesn't exist)
-               if (! m.containsKey(key))
-                       return null; // Key not in source (key doesn't exist in 
map)
-               // Key exists in map - return the value (which may be 
Optional.empty() if value was set to null)
+                       return null;
                return m.get(key);
        }
 
        /**
-        * Sets a setting in this source.
+        * Sets a setting in this store.
         *
         * <p>
         * The internal map is lazily initialized on the first call to this 
method. Setting a value to <c>null</c>
@@ -100,17 +96,16 @@ public class MapSource implements SettingSource {
         */
        @Override
        public void set(String key, String value) {
-               assertWriteable("Attempting to set a value on a read-only map 
source.");
                var m = map.get();
                if (m == null) {
                        var newMap = new 
ConcurrentHashMap<String,Optional<String>>();
-                       m = map.compareAndSet(null, newMap) ? newMap : 
map.get();
+                       m = map.compareAndSet(null, newMap) ? newMap : 
map.get();  // Not easily testable.
                }
                m.put(key, opt(value));
        }
 
        /**
-        * Clears all entries from this source.
+        * Clears all entries from this store.
         *
         * <p>
         * After calling this method, all keys will be removed from the map, 
and {@link #get(String)} will return
@@ -118,37 +113,26 @@ public class MapSource implements SettingSource {
         */
        @Override
        public void clear() {
-               assertWriteable("Attempting to update a read-only map source.");
                var m = map.get();
                if (m != null)
                        m.clear();
        }
 
        /**
-        * Removes a setting from this source.
+        * Removes a setting from this store.
         *
         * <p>
         * After calling this method, {@link #get(String)} will return 
<c>null</c> for the specified key,
-        * indicating that the key doesn't exist in this source (as opposed to 
returning <c>Optional.empty()</c>,
+        * indicating that the key doesn't exist in this store (as opposed to 
returning <c>Optional.empty()</c>,
         * which would indicate the key exists but has a null value).
         *
         * @param name The property name to remove.
         */
        @Override
        public void unset(String name) {
-               assertWriteable("Attempting to unset a value on a read-only map 
source.");
                var m = map.get();
                if (m != null)
                        m.remove(name);
        }
 
-       @Override
-       public boolean isWriteable() {
-               return true;
-       }
-
-       private void assertWriteable(String msg, Object...args) {
-               assertArg(isWriteable(), msg, args);
-       }
-
 }
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingSource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingSource.java
index a5f4af9502..63e2fdf353 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingSource.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingSource.java
@@ -22,9 +22,12 @@ import java.util.*;
  * Interface for pluggable property sources used by {@link Settings}.
  *
  * <p>
- * A setting source provides a way to retrieve and optionally modify property 
values.
+ * A setting source provides a way to retrieve property values.
  * Sources are checked in reverse order (last added is checked first) when 
looking up properties.
  *
+ * <p>
+ * For writable sources that support modifying property values, see {@link 
SettingStore}.
+ *
  * <h5 class='section'>Return Value Semantics:</h5>
  * <ul class='spaced-list'>
  *     <li><c>null</c> - The setting does not exist in this source. The lookup 
will continue to the next source.
@@ -35,12 +38,16 @@ import java.util.*;
  *
  * <h5 class='section'>Examples:</h5>
  * <p class='bjava'>
- *     <jc>// Create a writable source</jc>
- *     MapSource <jv>source</jv> = <jk>new</jk> MapSource();
- *     <jv>source</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
+ *     <jc>// Create a read-only functional source directly from a lambda</jc>
+ *     FunctionalSource <jv>readOnly</jv> = name -&gt; 
opt(System.getProperty(name));
+ *
+ *     <jc>// Or use the factory method</jc>
+ *     FunctionalSource <jv>readOnly2</jv> = 
FunctionalSource.<jsf>of</jsf>(System::getProperty);
  *
- *     <jc>// Create a read-only source from a function</jc>
- *     FunctionalSource <jv>readOnly</jv> = <jk>new</jk> FunctionalSource(x 
-&gt; System.getProperty(x));
+ *     <jc>// Stores can be used as sources (they extend SettingSource)</jc>
+ *     MapStore <jv>store</jv> = <jk>new</jk> MapStore();
+ *     <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
+ *     Settings.<jsf>get</jsf>().addSource(<jv>store</jv>);  <jc>// Stores can 
be added as sources</jc>
  * </p>
  */
 public interface SettingSource {
@@ -62,58 +69,4 @@ public interface SettingSource {
         *      if the property exists but has a null value.
         */
        Optional<String> get(String name);
-
-       /**
-        * Returns whether this source is writable.
-        *
-        * <p>
-        * If <c>false</c>, all write operations ({@link #set(String, String)}, 
{@link #unset(String)}, {@link #clear()})
-        * should be no-ops.
-        *
-        * @return <c>true</c> if this source is writable, <c>false</c> 
otherwise.
-        */
-       boolean isWriteable();
-
-       /**
-        * Sets a setting in this setting source.
-        *
-        * <p>
-        * Should be a no-op if the source is not writable (i.e., {@link 
#assertWriteable()} returns <c>false</c>).
-        *
-        * <p>
-        * Setting a value to <c>null</c> means that {@link #get(String)} will 
return <c>Optional.empty()</c> for that key,
-        * effectively overriding any values from lower-priority sources. Use 
{@link #unset(String)} if you want
-        * {@link #get(String)} to return <c>null</c> (indicating the key 
doesn't exist in this source).
-        *
-        * @param name The property name.
-        * @param value The property value, or <c>null</c> to set an empty 
override.
-        */
-       void set(String name, String value);
-
-       /**
-        * Removes a setting from this setting source.
-        *
-        * <p>
-        * Should be a no-op if the source is not writable (i.e., {@link 
#assertWriteable()} returns <c>false</c>).
-        *
-        * <p>
-        * After calling this method, {@link #get(String)} will return 
<c>null</c> for the specified key,
-        * indicating that the key doesn't exist in this source (as opposed to 
returning <c>Optional.empty()</c>,
-        * which would indicate the key exists but has a null value).
-        *
-        * @param name The property name to remove.
-        */
-       void unset(String name);
-
-       /**
-        * Clears all settings from this setting source.
-        *
-        * <p>
-        * Should be a no-op if the source is not writable (i.e., {@link 
#assertWriteable()} returns <c>false</c>).
-        *
-        * <p>
-        * After calling this method, all keys will be removed from this 
source, and {@link #get(String)} will
-        * return <c>null</c> for all keys.
-        */
-       void clear();
 }
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingStore.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingStore.java
new file mode 100644
index 0000000000..5159b2be52
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingStore.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.juneau.commons.settings;
+
+/**
+ * A writable extension of {@link SettingSource} that supports modifying 
property values.
+ *
+ * <p>
+ * This interface extends {@link SettingSource} with methods for setting, 
unsetting, and clearing properties.
+ * All stores that implement this interface provide read/write access and can 
be modified at runtime.
+ *
+ * <p>
+ * <b>Sources vs Stores:</b>
+ * <ul>
+ *     <li><b>Sources</b> ({@link SettingSource}) - Provide read-only access 
to property values
+ *     <li><b>Stores</b> ({@link SettingStore}) - Provide read/write access to 
property values
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     <jc>// Create a writable store</jc>
+ *     MapStore <jv>store</jv> = <jk>new</jk> MapStore();
+ *     <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
+ *     <jv>store</jv>.unset(<js>"my.property"</js>);
+ *     <jv>store</jv>.clear();
+ * </p>
+ */
+public interface SettingStore extends SettingSource {
+
+       /**
+        * Sets a setting in this store.
+        *
+        * <p>
+        * Setting a value to <c>null</c> means that {@link #get(String)} will 
return <c>Optional.empty()</c> for that key,
+        * effectively overriding any values from lower-priority sources. Use 
{@link #unset(String)} if you want
+        * {@link #get(String)} to return <c>null</c> (indicating the key 
doesn't exist in this store).
+        *
+        * @param name The property name.
+        * @param value The property value, or <c>null</c> to set an empty 
override.
+        */
+       void set(String name, String value);
+
+       /**
+        * Removes a setting from this store.
+        *
+        * <p>
+        * After calling this method, {@link #get(String)} will return 
<c>null</c> for the specified key,
+        * indicating that the key doesn't exist in this store (as opposed to 
returning <c>Optional.empty()</c>,
+        * which would indicate the key exists but has a null value).
+        *
+        * @param name The property name to remove.
+        */
+       void unset(String name);
+
+       /**
+        * Clears all settings from this store.
+        *
+        * <p>
+        * After calling this method, all keys will be removed from this store, 
and {@link #get(String)} will
+        * return <c>null</c> for all keys.
+        */
+       void clear();
+}
+
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
index a3e3fef582..9565cb550d 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.commons.settings;
 
 import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.io.*;
@@ -24,7 +25,10 @@ import java.net.*;
 import java.nio.charset.*;
 import java.nio.file.*;
 import java.util.*;
-import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.*;
+import java.util.function.*;
+
+import org.apache.juneau.commons.function.*;
 
 /**
  * Encapsulates Java system properties with support for global and per-thread 
overrides for unit testing.
@@ -34,30 +38,41 @@ import java.util.concurrent.CopyOnWriteArrayList;
  * and per-thread level, making it useful for unit tests that need to 
temporarily change system property
  * values without affecting other tests or threads.
  *
+ * <p>
+ * Settings instances are created using the {@link Builder} pattern. Use 
{@link #create()} to create a new builder,
+ * or {@link #get()} to get the singleton instance (which is created using 
default builder settings).
+ *
  * <h5 class='section'>Lookup Order:</h5>
  * <p>
  * When retrieving a property value, the lookup order is:
  * <ol>
- *     <li>Per-thread override (if set via {@link #setLocal(String, String)})
- *     <li>Global override (if set via {@link #setGlobal(String, String)})
- *     <li>Sources in reverse order (last source added via {@link 
#addSource(SettingSource)} is checked first)
+ *     <li>Per-thread store (if set via {@link #setLocal(String, String)})
+ *     <li>Global store (if set via {@link #setGlobal(String, String)})
+ *     <li>Sources in reverse order (last source added via {@link 
Builder#addSource(SettingSource)} is checked first)
  *     <li>System property source (default, always second-to-last)
  *     <li>System environment variable source (default, always last)
  * </ol>
  *
+ * <h5 class='section'>Sources vs Stores:</h5>
+ * <ul class='spaced-list'>
+ *     <li><b>Sources</b> ({@link SettingSource}) - Provide read-only access 
to property values. Examples: {@link FunctionalSource}
+ *     <li><b>Stores</b> ({@link SettingStore}) - Provide read/write access to 
property values. Examples: {@link MapStore}, {@link FunctionalStore}
+ *     <li>Stores can be used as sources (they extend {@link SettingSource}), 
so you can add stores via {@link Builder#addSource(SettingSource)}
+ * </ul>
+ *
  * <h5 class='section'>Features:</h5>
  * <ul class='spaced-list'>
  *     <li>System property access - read Java system properties with type 
conversion
- *     <li>Global overrides - override system properties globally for all 
threads
- *     <li>Per-thread overrides - override system properties for specific 
threads (useful for unit tests)
- *     <li>Custom sources - add arbitrary property sources (e.g., Spring 
properties, environment variables, config files)
+ *     <li>Global overrides - override system properties globally for all 
threads (stored in a {@link SettingStore})
+ *     <li>Per-thread overrides - override system properties for specific 
threads (stored in a per-thread {@link SettingStore})
+ *     <li>Custom sources - add arbitrary property sources (e.g., Spring 
properties, environment variables, config files) via the {@link Builder}
  *     <li>Disable override support - system property to prevent new global 
overrides from being set
  *     <li>Type-safe accessors - convenience methods for common types: 
Integer, Long, Boolean, Double, Float, File, Path, URI, Charset
  * </ul>
  *
  * <h5 class='section'>Usage Examples:</h5>
  * <p class='bjava'>
- *     <jc>// Get a system property as a string</jc>
+ *     <jc>// Get a system property as a string (using singleton instance)</jc>
  *     Optional&lt;String&gt; <jv>value</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.property"</js>);
  *
  *     <jc>// Get with type conversion</jc>
@@ -85,55 +100,43 @@ import java.util.concurrent.CopyOnWriteArrayList;
  *     <jc>// OR</jc>
  *     Settings.<jsf>get</jsf>().clearGlobal();  <jc>// Clear all global 
overrides</jc>
  *
- *     <jc>// Add a custom source (e.g., Spring properties)</jc>
- *     MapSource <jv>springSource</jv> = <jk>new</jk> MapSource();
+ *     <jc>// Create a custom Settings instance with custom sources (e.g., 
Spring properties)</jc>
+ *     MapStore <jv>springSource</jv> = <jk>new</jk> MapStore();
  *     <jv>springSource</jv>.set(<js>"spring.datasource.url"</js>, 
<js>"jdbc:postgresql://localhost/db"</js>);
- *     Settings.<jsf>get</jsf>().addSource(<jv>springSource</jv>);
+ *     Settings <jv>custom</jv> = Settings.<jsf>create</jsf>()
+ *             .addSource(<jv>springSource</jv>)
+ *             .addSource(FunctionalSource.<jsf>of</jsf>(System::getProperty))
+ *             .build();
  * </p>
  *
  * <h5 class='section'>System Properties:</h5>
  * <ul class='spaced-list'>
- *     <li><c>juneau.settings.disableGlobal</c> - If set to <c>true</c>, 
prevents new global overrides
+ *     <li><c>juneau.settings.disableGlobal</c> (system property) or 
<c>JUNEAU_SETTINGS_DISABLEGLOBAL</c> (system env) - If set to <c>true</c>, 
prevents new global overrides
  *             from being set via {@link #setGlobal(String, String)}. Existing 
global overrides will still be
  *             returned by {@link #get(String)} until explicitly removed.
  *             <p class='bnote'>
- *                     Note: This property is read once at class 
initialization time and cannot be changed at runtime.
- *                     Changing the system property after the class has been 
loaded will have no effect.
- *             </p>
- *     <li><c>juneau.settings.disableCustomSources</c> - If set to 
<c>true</c>, prevents custom sources
- *             from being added via {@link #addSource(SettingSource)} or 
{@link #setSources(SettingSource...)}.
- *             <p class='bnote'>
- *                     Note: This property is read once at class 
initialization time and cannot be changed at runtime.
- *                     Changing the system property after the class has been 
loaded will have no effect.
+ *                     Note: This property is read once at class 
initialization time when creating the singleton instance
+ *                     and cannot be changed at runtime. Changing the system 
property after the class has been loaded will
+ *                     have no effect on the singleton instance. However, you 
can create custom Settings instances using
+ *                     {@link #create()} that ignore this property.
  *             </p>
  * </ul>
  */
 public class Settings {
 
-       private static final Settings INSTANCE = new Settings();
-
-       /**
-        * Returns the singleton instance of Settings.
-        *
-        * @return The singleton Settings instance.
-        */
-       public static Settings get() {
-               return INSTANCE;
-       }
-
-       private final MapSource globalOverrides = new MapSource();
-       private final ThreadLocal<MapSource> threadOverrides = new 
ThreadLocal<>();
-       private final List<SettingSource> sources = new 
CopyOnWriteArrayList<>();
-
        /**
         * System property source that delegates to {@link 
System#getProperty(String)}.
         */
-       public static final SettingSource SYSTEM_PROPERTY_SOURCE = new 
FunctionalSource(x -> System.getProperty(x));
+       public static final SettingSource SYSTEM_PROPERTY_SOURCE = 
FunctionalSource.of(System::getProperty);
 
        /**
         * System environment variable source that delegates to {@link 
System#getenv(String)}.
         */
-       public static final SettingSource SYSTEM_ENV_SOURCE = new 
FunctionalSource(x -> System.getenv(x));
+       public static final SettingSource SYSTEM_ENV_SOURCE = 
FunctionalSource.of(System::getenv);
+
+       private static final String DISABLE_GLOBAL_PROP = 
"juneau.settings.disableGlobal";
+       private static final String MSG_globalDisabled = "Global settings not 
enabled";
+       private static final String MSG_localDisabled = "Local settings not 
enabled";
 
        /**
         * Returns properties for this Settings object itself.
@@ -143,24 +146,139 @@ public class Settings {
                var v = SYSTEM_PROPERTY_SOURCE.get(property);
                if (v != null)
                        return v;  // Not testable
-               v = SYSTEM_ENV_SOURCE.get(property);
+               v = SYSTEM_ENV_SOURCE.get(uc(property.replace('.', '_')));
                if (v != null)
                        return v;  // Not testable
                return opte();
        }
 
-       private static final String DISABLE_CUSTOM_SOURCES_PROP = 
"juneau.settings.disableCustomSources";
-       private static final String DISABLE_GLOBAL_PROP = 
"juneau.settings.disableGlobal";
-       private static final boolean DISABLE_CUSTOM_SOURCES = 
initProperty(DISABLE_CUSTOM_SOURCES_PROP).map(Boolean::valueOf).orElse(false);
-       private static final boolean DISABLE_GLOBAL = 
initProperty(DISABLE_GLOBAL_PROP).map(Boolean::valueOf).orElse(false);
+       /**
+        * Creates a new builder for constructing a Settings instance.
+        *
+        * <p>
+        * This method provides a convenient way to create custom Settings 
instances with specific
+        * configuration. The builder allows you to configure stores, sources, 
and other settings
+        * before building the final Settings instance.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Create a custom Settings instance</jc>
+        *      Settings <jv>custom</jv> = Settings.<jsf>create</jsf>()
+        *              .globalStore(() -&gt; <jk>new</jk> MapStore())
+        *              .localStore(() -&gt; <jk>new</jk> MapStore())
+        *              
.addSource(FunctionalSource.<jsf>of</jsf>(System::getProperty))
+        *              .build();
+        * </p>
+        *
+        * @return A new Builder instance.
+        */
+       public static Builder create() {
+               return new Builder();
+       }
+
+       /**
+        * Builder for creating Settings instances.
+        */
+       public static class Builder {
+               private Supplier<SettingStore> globalStoreSupplier = () -> new 
MapStore();
+               private Supplier<SettingStore> localStoreSupplier = () -> new 
MapStore();
+               private final List<SettingSource> sources = new ArrayList<>();
+
+               /**
+                * Sets the supplier for the global store.
+                *
+                * @param supplier The supplier for the global store.  Must not 
be <c>null</c>.  Can supply null to disable global store.
+                * @return This builder for method chaining.
+                */
+               public Builder globalStore(OptionalSupplier<SettingStore> 
supplier) {
+                       this.globalStoreSupplier = assertArgNotNull("supplier", 
supplier);
+                       return this;
+               }
+
+               /**
+                * Sets the supplier for the local (per-thread) store.
+                *
+                * @param supplier The supplier for the local store. Must not 
be <c>null</c>.
+                * @return This builder for method chaining.
+                */
+               public Builder localStore(OptionalSupplier<SettingStore> 
supplier) {
+                       this.localStoreSupplier = assertArgNotNull("supplier", 
supplier);
+                       return this;
+               }
+
+               /**
+                * Sets the sources list, replacing any existing sources.
+                *
+                * @param sources The sources to set. Must not be <c>null</c> 
or contain <c>null</c> elements.
+                * @return This builder for method chaining.
+                */
+               @SafeVarargs
+               public final Builder setSources(SettingSource...sources) {
+                       assertVarargsNotNull("sources", sources);
+                       this.sources.clear();
+                       for (var source : sources) {
+                               this.sources.add(source);
+                       }
+                       return this;
+               }
+
+               /**
+                * Adds a source to the sources list.
+                *
+                * @param source The source to add. Must not be <c>null</c>.
+                * @return This builder for method chaining.
+                */
+               public Builder addSource(SettingSource source) {
+                       assertArgNotNull("source", source);
+                       this.sources.add(source);
+                       return this;
+               }
+
+               /**
+                * Adds a functional source to the sources list.
+                *
+                * @param source The functional source to add. Must not be 
<c>null</c>.
+                * @return This builder for method chaining.
+                */
+               public Builder addSource(FunctionalSource source) {
+                       return addSource((SettingSource)source);
+               }
+
+               /**
+                * Builds a Settings instance from this builder.
+                *
+                * @return A new Settings instance.
+                */
+               public Settings build() {
+                       return new Settings(this);
+               }
+       }
+
+       private static final Settings INSTANCE = new Builder()
+               
.globalStore(initProperty(DISABLE_GLOBAL_PROP).map(Boolean::valueOf).orElse(false)
 ? () -> null : () -> new MapStore())
+               .setSources(SYSTEM_ENV_SOURCE, SYSTEM_PROPERTY_SOURCE)
+               .build();
+
+       /**
+        * Returns the singleton instance of Settings.
+        *
+        * @return The singleton Settings instance.
+        */
+       public static Settings get() {
+               return INSTANCE;
+       }
+
+       private final ResettableSupplier<SettingStore> globalStore;
+       private final ThreadLocal<SettingStore> localStore;
+       private final List<SettingSource> sources;
 
        /**
         * Constructor.
         */
-       private Settings() {
-               // Initialize with system sources as defaults (env first, then 
property, so property has higher precedence)
-               sources.add(SYSTEM_ENV_SOURCE);
-               sources.add(SYSTEM_PROPERTY_SOURCE);
+       private Settings(Builder builder) {
+               this.globalStore = 
memoizeResettable(builder.globalStoreSupplier);
+               this.localStore = 
ThreadLocal.withInitial(builder.localStoreSupplier);
+               this.sources = new CopyOnWriteArrayList<>(builder.sources);
        }
 
        /**
@@ -181,32 +299,24 @@ public class Settings {
         */
        public Optional<String> get(String name) {
                // 1. Check thread-local override
-               var localSource = threadOverrides.get();
-               if (localSource != null) {
-                       var v = localSource.get(name);
-                       if (v != null)
-                               return v; // v is Optional.empty() if key 
exists with null value, or Optional.of(value) if present
-               }
+               var v = localStore.get().get(name);
+               if (v != null)
+                       return v; // v is Optional.empty() if key exists with 
null value, or Optional.of(value) if present
 
                // 2. Check global override
-               var v = globalOverrides.get(name);
+               v = globalStore.get().get(name);
                if (v != null)
                        return v; // v is Optional.empty() if key exists with 
null value, or Optional.of(value) if present
 
                // 3. Check sources in reverse order (last added first)
                for (int i = sources.size() - 1; i >= 0; i--) {
                        var source = sources.get(i);
-                       if (source == null)
-                               continue; // Skip null sources (defensive check)
                        var result = source.get(name);
-                       if (result == null)
-                               continue; // Key not in this source, try next 
source
-                       // If result is Optional.empty(), it means key exists 
with null value, so return it
-                       // If result is present, return it
-                       return result;
+                       if (result != null)
+                               return result;
                }
 
-               return Optional.empty();
+               return opte();
        }
 
        /**
@@ -357,12 +467,14 @@ public class Settings {
         *
         * @param name The property name.
         * @param value The override value, or <c>null</c> to set an empty 
override.
+        * @return This object for method chaining.
         * @see #unsetGlobal(String)
         * @see #clearGlobal()
         */
-       public void setGlobal(String name, String value) {
-               assertArg(! DISABLE_GLOBAL, "Global settings have been disabled 
via ''{0}''", DISABLE_GLOBAL_PROP);
-               globalOverrides.set(name, value);
+       public Settings setGlobal(String name, String value) {
+               assertArgNotNull("name", name);
+               
globalStore.orElseThrow(()->illegalState(MSG_globalDisabled)).set(name, value);
+               return this;
        }
 
        /**
@@ -377,8 +489,8 @@ public class Settings {
         * @see #clearGlobal()
         */
        public void unsetGlobal(String name) {
-               assertArg(! DISABLE_GLOBAL, "Global settings have been disabled 
via ''{0}''", DISABLE_GLOBAL_PROP);
-               globalOverrides.unset(name);
+               assertArgNotNull("name", name);
+               
globalStore.orElseThrow(()->illegalState(MSG_globalDisabled)).unset(name);
        }
 
        /**
@@ -395,16 +507,15 @@ public class Settings {
         *
         * @param name The property name.
         * @param value The override value, or <c>null</c> to set an empty 
override.
+        * @return This object for method chaining.
         * @see #unsetLocal(String)
         * @see #clearLocal()
         */
-       public void setLocal(String name, String value) {
-               var localSource = threadOverrides.get();
-               if (localSource == null) {
-                       localSource = new MapSource();
-                       threadOverrides.set(localSource);
-               }
-               localSource.set(name, value);
+       public Settings setLocal(String name, String value) {
+               assertArgNotNull("name", name);
+               assertState(nn(localStore.get()), MSG_localDisabled);
+               localStore.get().set(name, value);
+               return this;
        }
 
        /**
@@ -419,9 +530,9 @@ public class Settings {
         * @see #clearLocal()
         */
        public void unsetLocal(String name) {
-               var localSource = threadOverrides.get();
-               if (localSource != null)
-                       localSource.unset(name);
+               assertArgNotNull("name", name);
+               assertState(nn(localStore.get()), MSG_localDisabled);
+               localStore.get().unset(name);
        }
 
        /**
@@ -435,11 +546,14 @@ public class Settings {
         * This is typically called in a <c>@AfterEach</c> or <c>@After</c> 
test method to clean up
         * thread-local overrides after a test completes.
         *
+        * @return This object for method chaining.
         * @see #setLocal(String, String)
         * @see #unsetLocal(String)
         */
-       public void clearLocal() {
-               threadOverrides.remove();
+       public Settings clearLocal() {
+               assertState(nn(localStore.get()), MSG_localDisabled);
+               localStore.get().clear();
+               return this;
        }
 
        /**
@@ -449,96 +563,13 @@ public class Settings {
         * After calling this method, all properties will fall back to resolver 
values
         * (or per-thread overrides if they exist).
         *
+        * @return This object for method chaining.
         * @see #setGlobal(String, String)
         * @see #unsetGlobal(String)
         */
-       public void clearGlobal() {
-               globalOverrides.clear();
-       }
-
-       /**
-        * Adds a source to the source list.
-        *
-        * <p>
-        * Sources are checked in reverse order (last added is checked first), 
after global overrides
-        * but before the system property source. This allows you to add custom 
property sources such
-        * as Spring properties, environment variables, or configuration files.
-        *
-        * <h5 class='section'>Example:</h5>
-        * <p class='bjava'>
-        *      <jc>// Add a Spring properties source</jc>
-        *      MapSource <jv>springSource</jv> = <jk>new</jk> MapSource();
-        *      <jv>springSource</jv>.set(<js>"spring.datasource.url"</js>, 
<js>"jdbc:postgresql://localhost/db"</js>);
-        *      Settings.<jsf>get</jsf>().addSource(<jv>springSource</jv>);
-        * </p>
-        *
-        * @param source The source. Must not be <c>null</c>.
-        * @return This object for method chaining.
-        * @see #setSources(SettingSource...)
-        */
-       public Settings addSource(SettingSource source) {
-               assertArg(! DISABLE_CUSTOM_SOURCES, "Global custom sources have 
been disabled via ''{0}''", DISABLE_CUSTOM_SOURCES_PROP);
-               assertArgNotNull("source", source);
-               // Add at the end - since we iterate in reverse order, this 
means it's checked first (before system sources)
-               sources.add(source);
+       public Settings clearGlobal() {
+               
globalStore.orElseThrow(()->illegalState(MSG_globalDisabled)).clear();
                return this;
        }
-
-       public Settings addSource(FunctionalSource source) {
-               return addSource((SettingSource)source);
-       }
-
-       /**
-        * Sets the source list, replacing all existing sources.
-        *
-        * <p>
-        * This method clears all existing sources (except the system property 
and environment variable sources which are always
-        * present) and adds the specified sources in order. The sources will 
be checked in reverse
-        * order (last source in the array is checked first).
-        *
-        * <p>
-        * Note that using this method resets existing sources, so if you want 
to maintain resolving environment variables
-        * or system properties, you'll need to add {@link #SYSTEM_ENV_SOURCE} 
and {@link #SYSTEM_PROPERTIES_SOURCE} to the
-        * list of sources added to this method.
-        *
-        * <h5 class='section'>Example:</h5>
-        * <p class='bjava'>
-        *      <jc>// Set multiple sources</jc>
-        *      MapSource <jv>springSource</jv> = <jk>new</jk> MapSource();
-        *      MapSource <jv>envSource</jv> = <jk>new</jk> MapSource();
-        *      Settings.<jsf>get</jsf>().setSources(<jv>springSource</jv>, 
<jv>envSource</jv>);
-        *      <jc>// Lookup order: local &gt; global &gt; envSource &gt; 
springSource &gt; system properties &gt; system env vars</jc>
-        * </p>
-        *
-        * <p>
-        * Note that the order of precedence of the sources is in reverse 
order.  So sources at the end of the list are
-        * checked before sources at the beginning of the list.
-        *
-        * @param sources The sources to add. Must not be <c>null</c> or 
contain <c>null</c> elements.
-        * @return This object for method chaining.
-        * @see #addSource(SettingSource)
-        */
-       @SafeVarargs
-       public final Settings setSources(SettingSource...sources) {
-               assertArg(! DISABLE_CUSTOM_SOURCES, "Global custom sources have 
been disabled via ''{0}''", DISABLE_CUSTOM_SOURCES_PROP);
-               assertVarargsNotNull("sources", sources);
-               // Remove all sources
-               this.sources.clear();
-               // Add new sources
-               for (var source : sources) {
-                       this.sources.add(source);
-               }
-               return this;
-       }
-
-       /**
-        * Resets the sources list to default (only system sources).
-        * Package-private for testing purposes.
-        */
-       void resetSources() {
-               this.sources.clear();
-               this.sources.add(SYSTEM_ENV_SOURCE);
-               this.sources.add(SYSTEM_PROPERTY_SOURCE);
-       }
 }
 
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/AssertionUtils.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/AssertionUtils.java
index b8e22005c8..30f2cf3612 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/AssertionUtils.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/AssertionUtils.java
@@ -78,6 +78,24 @@ public class AssertionUtils {
                        throw illegalArg(msg, args);
        }
 
+       /**
+        * Asserts that the given expression is <c>true</c>, throwing an {@link 
IllegalStateException} if it's not.
+        *
+        * <p>
+        * This method is similar to {@link #assertArg(boolean, String, 
Object...)} but throws an
+        * {@link IllegalStateException} instead of an {@link 
IllegalArgumentException}, making it suitable
+        * for state validation rather than argument validation.
+        *
+        * @param expression The expression to test.
+        * @param msg The error message format string.
+        * @param args The arguments for the error message format string.
+        * @throws IllegalStateException if the expression is <c>false</c>.
+        */
+       public static final void assertState(boolean expression, String msg, 
Object...args) throws IllegalStateException {
+               if (! expression)
+                       throw illegalState(msg, args);
+       }
+
        /**
         * Throws an {@link IllegalArgumentException} if the specified argument 
is <jk>null</jk>.
         *
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/ThrowableUtils.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/ThrowableUtils.java
index f836e2cae7..cc9168c8e6 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/ThrowableUtils.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/ThrowableUtils.java
@@ -61,7 +61,7 @@ public class ThrowableUtils {
         * Shortcut for creating a {@link BeanRuntimeException} with a message 
and associated class.
         *
         * <p>
-        * Same as {@link #bex(Class, String, Object...) bex(Class, String, 
Object...)} but accepts a {@link ClassInfo} instead of a {@link Class}.
+        * Same as the {@code bex(Class, String, Object...)} method but accepts 
a {@link ClassInfo} instead of a {@link Class}.
         *
         * @param c The class info associated with the exception.
         * @param msg The message.
@@ -110,7 +110,7 @@ public class ThrowableUtils {
         * Shortcut for creating a {@link BeanRuntimeException} with a cause, 
message, and associated class.
         *
         * <p>
-        * Same as {@link #bex(Throwable, Class, String, Object...)} but 
accepts a {@link ClassInfo} instead of a {@link Class}.
+        * Same as the {@code bex(Throwable, Class, String, Object...)} method 
but accepts a {@link ClassInfo} instead of a {@link Class}.
         *
         * @param e The cause of the exception.
         * @param c The class info associated with the exception.
@@ -332,6 +332,29 @@ public class ThrowableUtils {
                return new IllegalArgumentException(f(msg, args));
        }
 
+       /**
+        * Creates an {@link IllegalStateException} with a formatted message.
+        *
+        * <p>
+        * This is a convenience method for creating state exceptions with 
formatted messages.
+        * The message is formatted using {@link #f(String, Object...)}.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jk>throw</jk> <jsm>illegalState</jsm>(<js>"Invalid state: 
{0}"</js>, <jv>state</jv>);
+        * </p>
+        *
+        * @param msg The message format string.
+        * @param args The arguments for the message format string.
+        * @return A new IllegalStateException with the formatted message.
+        * @see #f(String, Object...)
+        * @see #illegalArg(String, Object...)
+        */
+       public static IllegalStateException illegalState(String msg, 
Object...args) {
+               return new IllegalStateException(f(msg, args));
+       }
+
+
        /**
         * Creates an {@link IllegalArgumentException} wrapping the given 
throwable.
         *
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
index bae91ec0b2..2172e49477 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
@@ -16,16 +16,16 @@
  */
 package org.apache.juneau.commons.settings;
 
+import static org.apache.juneau.commons.utils.Utils.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.io.File;
 import java.net.URI;
 import java.nio.charset.Charset;
-import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.Optional;
 
 import org.apache.juneau.*;
+import org.apache.juneau.commons.function.*;
 import org.junit.jupiter.api.*;
 
 class Settings_Test extends TestBase {
@@ -38,7 +38,6 @@ class Settings_Test extends TestBase {
                // Clean up before each test
                Settings.get().clearLocal();
                Settings.get().clearGlobal();
-               Settings.get().resetSources();
                // Remove test system properties if they exist
                System.clearProperty(TEST_PROP);
                System.clearProperty(TEST_PROP_2);
@@ -59,21 +58,21 @@ class Settings_Test extends TestBase {
        @Test
        void a01_get_fromSystemProperty() {
                System.setProperty(TEST_PROP, "system-value");
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("system-value", result.get());
        }
 
        @Test
        void a02_get_notFound() {
-               Optional<String> result = 
Settings.get().get("nonexistent.property");
+               var result = Settings.get().get("nonexistent.property");
                assertFalse(result.isPresent());
        }
 
        @Test
        void a03_get_fromGlobalOverride() {
                Settings.get().setGlobal(TEST_PROP, "global-value");
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("global-value", result.get());
        }
@@ -81,7 +80,7 @@ class Settings_Test extends TestBase {
        @Test
        void a04_get_fromLocalOverride() {
                Settings.get().setLocal(TEST_PROP, "local-value");
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("local-value", result.get());
        }
@@ -91,7 +90,7 @@ class Settings_Test extends TestBase {
                System.setProperty(TEST_PROP, "system-value");
                Settings.get().setGlobal(TEST_PROP, "global-value");
                Settings.get().setLocal(TEST_PROP, "local-value");
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("local-value", result.get());
        }
@@ -100,7 +99,7 @@ class Settings_Test extends TestBase {
        void a06_get_lookupOrder_globalOverridesSystem() {
                System.setProperty(TEST_PROP, "system-value");
                Settings.get().setGlobal(TEST_PROP, "global-value");
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("global-value", result.get());
        }
@@ -108,7 +107,7 @@ class Settings_Test extends TestBase {
        @Test
        void a07_get_nullValue() {
                Settings.get().setLocal(TEST_PROP, null);
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertFalse(result.isPresent());
        }
 
@@ -118,7 +117,7 @@ class Settings_Test extends TestBase {
        @Test
        void b01_getInteger_valid() {
                System.setProperty(TEST_PROP, "123");
-               Optional<Integer> result = Settings.get().getInteger(TEST_PROP);
+               var result = Settings.get().getInteger(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals(123, result.get());
        }
@@ -126,20 +125,20 @@ class Settings_Test extends TestBase {
        @Test
        void b02_getInteger_invalid() {
                System.setProperty(TEST_PROP, "not-a-number");
-               Optional<Integer> result = Settings.get().getInteger(TEST_PROP);
+               var result = Settings.get().getInteger(TEST_PROP);
                assertFalse(result.isPresent());
        }
 
        @Test
        void b03_getInteger_notFound() {
-               Optional<Integer> result = 
Settings.get().getInteger("nonexistent.property");
+               var result = Settings.get().getInteger("nonexistent.property");
                assertFalse(result.isPresent());
        }
 
        @Test
        void b04_getInteger_fromOverride() {
                Settings.get().setLocal(TEST_PROP, "456");
-               Optional<Integer> result = Settings.get().getInteger(TEST_PROP);
+               var result = Settings.get().getInteger(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals(456, result.get());
        }
@@ -150,7 +149,7 @@ class Settings_Test extends TestBase {
        @Test
        void c01_getLong_valid() {
                System.setProperty(TEST_PROP, "123456789");
-               Optional<Long> result = Settings.get().getLong(TEST_PROP);
+               var result = Settings.get().getLong(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals(123456789L, result.get());
        }
@@ -158,14 +157,14 @@ class Settings_Test extends TestBase {
        @Test
        void c02_getLong_invalid() {
                System.setProperty(TEST_PROP, "not-a-number");
-               Optional<Long> result = Settings.get().getLong(TEST_PROP);
+               var result = Settings.get().getLong(TEST_PROP);
                assertFalse(result.isPresent());
        }
 
        @Test
        void c03_getLong_fromOverride() {
                Settings.get().setLocal(TEST_PROP, "987654321");
-               Optional<Long> result = Settings.get().getLong(TEST_PROP);
+               var result = Settings.get().getLong(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals(987654321L, result.get());
        }
@@ -176,7 +175,7 @@ class Settings_Test extends TestBase {
        @Test
        void d01_getBoolean_true() {
                System.setProperty(TEST_PROP, "true");
-               Optional<Boolean> result = Settings.get().getBoolean(TEST_PROP);
+               var result = Settings.get().getBoolean(TEST_PROP);
                assertTrue(result.isPresent());
                assertTrue(result.get());
        }
@@ -184,7 +183,7 @@ class Settings_Test extends TestBase {
        @Test
        void d02_getBoolean_false() {
                System.setProperty(TEST_PROP, "false");
-               Optional<Boolean> result = Settings.get().getBoolean(TEST_PROP);
+               var result = Settings.get().getBoolean(TEST_PROP);
                assertTrue(result.isPresent());
                assertFalse(result.get());
        }
@@ -192,7 +191,7 @@ class Settings_Test extends TestBase {
        @Test
        void d03_getBoolean_caseInsensitive() {
                System.setProperty(TEST_PROP, "TRUE");
-               Optional<Boolean> result = Settings.get().getBoolean(TEST_PROP);
+               var result = Settings.get().getBoolean(TEST_PROP);
                assertTrue(result.isPresent());
                assertTrue(result.get());
        }
@@ -200,14 +199,14 @@ class Settings_Test extends TestBase {
        @Test
        void d04_getBoolean_nonTrueValue() {
                System.setProperty(TEST_PROP, "anything");
-               Optional<Boolean> result = Settings.get().getBoolean(TEST_PROP);
+               var result = Settings.get().getBoolean(TEST_PROP);
                assertTrue(result.isPresent());
                assertFalse(result.get());
        }
 
        @Test
        void d05_getBoolean_notFound() {
-               Optional<Boolean> result = 
Settings.get().getBoolean("nonexistent.property");
+               var result = Settings.get().getBoolean("nonexistent.property");
                assertFalse(result.isPresent());
        }
 
@@ -217,7 +216,7 @@ class Settings_Test extends TestBase {
        @Test
        void e01_getDouble_valid() {
                System.setProperty(TEST_PROP, "123.456");
-               Optional<Double> result = Settings.get().getDouble(TEST_PROP);
+               var result = Settings.get().getDouble(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals(123.456, result.get(), 0.0001);
        }
@@ -225,7 +224,7 @@ class Settings_Test extends TestBase {
        @Test
        void e02_getDouble_invalid() {
                System.setProperty(TEST_PROP, "not-a-number");
-               Optional<Double> result = Settings.get().getDouble(TEST_PROP);
+               var result = Settings.get().getDouble(TEST_PROP);
                assertFalse(result.isPresent());
        }
 
@@ -235,7 +234,7 @@ class Settings_Test extends TestBase {
        @Test
        void f01_getFloat_valid() {
                System.setProperty(TEST_PROP, "123.456");
-               Optional<Float> result = Settings.get().getFloat(TEST_PROP);
+               var result = Settings.get().getFloat(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals(123.456f, result.get(), 0.0001f);
        }
@@ -243,7 +242,7 @@ class Settings_Test extends TestBase {
        @Test
        void f02_getFloat_invalid() {
                System.setProperty(TEST_PROP, "not-a-number");
-               Optional<Float> result = Settings.get().getFloat(TEST_PROP);
+               var result = Settings.get().getFloat(TEST_PROP);
                assertFalse(result.isPresent());
        }
 
@@ -253,14 +252,14 @@ class Settings_Test extends TestBase {
        @Test
        void g01_getFile_valid() {
                System.setProperty(TEST_PROP, "/tmp/test.txt");
-               Optional<File> result = Settings.get().getFile(TEST_PROP);
+               var result = Settings.get().getFile(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals(new File("/tmp/test.txt"), result.get());
        }
 
        @Test
        void g02_getFile_notFound() {
-               Optional<File> result = 
Settings.get().getFile("nonexistent.property");
+               var result = Settings.get().getFile("nonexistent.property");
                assertFalse(result.isPresent());
        }
 
@@ -270,7 +269,7 @@ class Settings_Test extends TestBase {
        @Test
        void h01_getPath_valid() {
                System.setProperty(TEST_PROP, "/tmp/test.txt");
-               Optional<Path> result = Settings.get().getPath(TEST_PROP);
+               var result = Settings.get().getPath(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals(Paths.get("/tmp/test.txt"), result.get());
        }
@@ -280,7 +279,7 @@ class Settings_Test extends TestBase {
                // Paths.get() can throw exceptions for invalid paths on some 
systems
                // This test verifies that invalid paths return empty
                System.setProperty(TEST_PROP, "\0invalid");
-               Optional<Path> result = Settings.get().getPath(TEST_PROP);
+               var result = Settings.get().getPath(TEST_PROP);
                // May or may not be empty depending on OS, but should not throw
                assertNotNull(result);
        }
@@ -291,7 +290,7 @@ class Settings_Test extends TestBase {
        @Test
        void i01_getURI_valid() {
                System.setProperty(TEST_PROP, "http://example.com/test";);
-               Optional<URI> result = Settings.get().getURI(TEST_PROP);
+               var result = Settings.get().getURI(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals(URI.create("http://example.com/test";), 
result.get());
        }
@@ -299,7 +298,7 @@ class Settings_Test extends TestBase {
        @Test
        void i02_getURI_invalid() {
                System.setProperty(TEST_PROP, "not a valid uri");
-               Optional<URI> result = Settings.get().getURI(TEST_PROP);
+               var result = Settings.get().getURI(TEST_PROP);
                assertFalse(result.isPresent());
        }
 
@@ -309,7 +308,7 @@ class Settings_Test extends TestBase {
        @Test
        void j01_getCharset_valid() {
                System.setProperty(TEST_PROP, "UTF-8");
-               Optional<Charset> result = Settings.get().getCharset(TEST_PROP);
+               var result = Settings.get().getCharset(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals(Charset.forName("UTF-8"), result.get());
        }
@@ -317,7 +316,7 @@ class Settings_Test extends TestBase {
        @Test
        void j02_getCharset_invalid() {
                System.setProperty(TEST_PROP, "INVALID-CHARSET");
-               Optional<Charset> result = Settings.get().getCharset(TEST_PROP);
+               var result = Settings.get().getCharset(TEST_PROP);
                assertFalse(result.isPresent());
        }
 
@@ -327,7 +326,7 @@ class Settings_Test extends TestBase {
        @Test
        void k01_setGlobal() {
                Settings.get().setGlobal(TEST_PROP, "global-value");
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("global-value", result.get());
        }
@@ -336,7 +335,7 @@ class Settings_Test extends TestBase {
        void k02_unsetGlobal() {
                Settings.get().setGlobal(TEST_PROP, "global-value");
                Settings.get().unsetGlobal(TEST_PROP);
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertFalse(result.isPresent());
        }
 
@@ -352,7 +351,7 @@ class Settings_Test extends TestBase {
        @Test
        void k04_setGlobal_nullValue() {
                Settings.get().setGlobal(TEST_PROP, null);
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertFalse(result.isPresent());
        }
 
@@ -362,7 +361,7 @@ class Settings_Test extends TestBase {
        @Test
        void l01_setLocal() {
                Settings.get().setLocal(TEST_PROP, "local-value");
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("local-value", result.get());
        }
@@ -371,7 +370,16 @@ class Settings_Test extends TestBase {
        void l02_unsetLocal() {
                Settings.get().setLocal(TEST_PROP, "local-value");
                Settings.get().unsetLocal(TEST_PROP);
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       @Test
+       void l07_unsetLocal_whenNoLocalSource() {
+               // unsetLocal should not throw when there's no local source 
(threadOverrides.get() returns null)
+               Settings.get().unsetLocal(TEST_PROP);
+               // Should not throw an exception
+               var result = Settings.get().get(TEST_PROP);
                assertFalse(result.isPresent());
        }
 
@@ -387,7 +395,7 @@ class Settings_Test extends TestBase {
        @Test
        void l04_setLocal_nullValue() {
                Settings.get().setLocal(TEST_PROP, null);
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertFalse(result.isPresent());
        }
 
@@ -395,7 +403,7 @@ class Settings_Test extends TestBase {
        void l05_setLocal_overridesGlobal() {
                Settings.get().setGlobal(TEST_PROP, "global-value");
                Settings.get().setLocal(TEST_PROP, "local-value");
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("local-value", result.get());
        }
@@ -404,7 +412,7 @@ class Settings_Test extends TestBase {
        void l06_setLocal_overridesSystemProperty() {
                System.setProperty(TEST_PROP, "system-value");
                Settings.get().setLocal(TEST_PROP, "local-value");
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("local-value", result.get());
        }
@@ -416,9 +424,9 @@ class Settings_Test extends TestBase {
        void m01_localOverride_threadIsolation() throws InterruptedException {
                Settings.get().setLocal(TEST_PROP, "thread1-value");
 
-               Thread thread2 = new Thread(() -> {
+               var thread2 = new Thread(() -> {
                        Settings.get().setLocal(TEST_PROP, "thread2-value");
-                       Optional<String> result = Settings.get().get(TEST_PROP);
+                       var result = Settings.get().get(TEST_PROP);
                        assertTrue(result.isPresent());
                        assertEquals("thread2-value", result.get());
                });
@@ -427,7 +435,7 @@ class Settings_Test extends TestBase {
                thread2.join();
 
                // Original thread should still have its value
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("thread1-value", result.get());
        }
@@ -436,8 +444,8 @@ class Settings_Test extends TestBase {
        void m02_globalOverride_sharedAcrossThreads() throws 
InterruptedException {
                Settings.get().setGlobal(TEST_PROP, "global-value");
 
-               Thread thread2 = new Thread(() -> {
-                       Optional<String> result = Settings.get().get(TEST_PROP);
+               var thread2 = new Thread(() -> {
+                       var result = Settings.get().get(TEST_PROP);
                        assertTrue(result.isPresent());
                        assertEquals("global-value", result.get());
                });
@@ -446,7 +454,7 @@ class Settings_Test extends TestBase {
                thread2.join();
 
                // Original thread should also see the global value
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("global-value", result.get());
        }
@@ -456,8 +464,8 @@ class Settings_Test extends TestBase {
        
//====================================================================================================
        @Test
        void n01_get_returnsSameInstance() {
-               Settings instance1 = Settings.get();
-               Settings instance2 = Settings.get();
+               var instance1 = Settings.get();
+               var instance2 = Settings.get();
                assertSame(instance1, instance2);
        }
 
@@ -466,41 +474,61 @@ class Settings_Test extends TestBase {
        
//====================================================================================================
        @Test
        void o01_addSource() {
-               MapSource source = new MapSource();
+               var source = new MapStore();
                source.set(TEST_PROP, "source-value");
-               Settings.get().addSource(source);
+               var settings = Settings.create()
+                       .addSource(source)
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
 
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = settings.get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("source-value", result.get());
        }
 
        @Test
        void o02_addSource_reverseOrder() {
-               MapSource source1 = new MapSource();
+               var source1 = new MapStore();
                source1.set(TEST_PROP, "source1-value");
-               Settings.get().addSource(source1);
 
-               MapSource source2 = new MapSource();
+               var source2 = new MapStore();
                source2.set(TEST_PROP, "source2-value");
-               Settings.get().addSource(source2);
+
+               var settings = Settings.create()
+                       .addSource(source1)
+                       .addSource(source2)
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
 
                // Last added source should be checked first
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = settings.get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("source2-value", result.get());
        }
 
        @Test
        void o03_addSource_afterGlobalOverride() {
-               Settings.get().setGlobal(TEST_PROP, "global-value");
+               var settings = Settings.create()
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
+
+               settings.setGlobal(TEST_PROP, "global-value");
 
-               MapSource source = new MapSource();
+               var source = new MapStore();
                source.set(TEST_PROP, "source-value");
-               Settings.get().addSource(source);
+               // Note: Can't add sources after building, so we create a new 
instance
+               var settingsWithSource = Settings.create()
+                       .addSource(source)
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
+               settingsWithSource.setGlobal(TEST_PROP, "global-value");
 
                // Global override should take precedence
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = settingsWithSource.get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("global-value", result.get());
        }
@@ -509,105 +537,338 @@ class Settings_Test extends TestBase {
        void o04_addSource_beforeSystemProperty() {
                System.setProperty(TEST_PROP, "system-value");
 
-               MapSource source = new MapSource();
+               var source = new MapStore();
                source.set(TEST_PROP, "source-value");
-               Settings.get().addSource(source);
+               // Sources are checked in reverse order, so add system sources 
first, then custom source
+               var settings = Settings.create()
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(source)
+                       .build();
 
-               // Source should take precedence over system property
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               // Source should take precedence over system property (checked 
first)
+               var result = settings.get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("source-value", result.get());
        }
 
        @Test
        void o05_addSource_fallbackToSystemProperty() {
-               MapSource source = new MapSource();
+               var source = new MapStore();
                // Source doesn't have the property
 
                System.setProperty(TEST_PROP, "system-value");
-               Settings.get().addSource(source);
+               var settings = Settings.create()
+                       .addSource(source)
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
 
                // Should fall back to system property
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = settings.get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("system-value", result.get());
        }
 
        @Test
        void o06_setSources() {
-               MapSource source1 = new MapSource();
+               var source1 = new MapStore();
                source1.set(TEST_PROP, "source1-value");
 
-               MapSource source2 = new MapSource();
+               var source2 = new MapStore();
                source2.set(TEST_PROP, "source2-value");
 
-               Settings.get().setSources(source1, source2);
+               var settings = Settings.create()
+                       .setSources(source1, source2, 
Settings.SYSTEM_PROPERTY_SOURCE, Settings.SYSTEM_ENV_SOURCE)
+                       .build();
 
                // Last source in array should be checked first
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = settings.get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("source2-value", result.get());
        }
 
        @Test
        void o07_setSources_clearsExisting() {
-               MapSource source1 = new MapSource();
+               var source1 = new MapStore();
                source1.set(TEST_PROP, "source1-value");
-               Settings.get().addSource(source1);
 
-               MapSource source2 = new MapSource();
+               var source2 = new MapStore();
                source2.set(TEST_PROP, "source2-value");
-               Settings.get().setSources(source2);
+
+               var settings = Settings.create()
+                       .addSource(source1)
+                       .setSources(source2, Settings.SYSTEM_PROPERTY_SOURCE, 
Settings.SYSTEM_ENV_SOURCE)
+                       .build();
 
                // Only source2 should exist now
-               Optional<String> result = Settings.get().get(TEST_PROP);
+               var result = settings.get(TEST_PROP);
                assertTrue(result.isPresent());
                assertEquals("source2-value", result.get());
        }
 
        @Test
        void o08_addSource_nullValue() {
-               MapSource source = new MapSource();
+               var source = new MapStore();
                source.set(TEST_PROP, null);
-               Settings.get().addSource(source);
-
-               // Note that setting a null value on the source overrides the 
system property.
                System.setProperty(TEST_PROP, "system-value");
-               Optional<String> result = Settings.get().get(TEST_PROP);
+
+               // Sources are checked in reverse order, so add system sources 
first, then custom source
+               var settings = Settings.create()
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(source)
+                       .build();
+
+               // Note that setting a null value on the source 
(Optional.empty()) overrides the system property.
+               // When a source returns Optional.empty(), it means the 
property exists but has a null value.
+               var result = settings.get(TEST_PROP);
                assertFalse(result.isPresent());
        }
 
        @Test
        void o09_addSource_nullSource() {
                assertThrows(IllegalArgumentException.class, () -> {
-                       Settings.get().addSource(null);
+                       Settings.create().addSource(null);
                });
        }
 
        @Test
        void o10_setSources_nullSource() {
-               MapSource source1 = new MapSource();
+               var source1 = new MapStore();
                assertThrows(IllegalArgumentException.class, () -> {
-                       Settings.get().setSources(source1, null);
+                       Settings.create().setSources(source1, null);
                });
        }
 
        @Test
        void o11_source_springPropertiesExample() {
                // Simulate Spring properties
-               MapSource springSource = new MapSource();
+               var springSource = new MapStore();
                springSource.set("spring.datasource.url", 
"jdbc:postgresql://localhost/db");
                springSource.set("spring.datasource.username", "admin");
 
-               Settings.get().addSource(springSource);
+               var settings = Settings.create()
+                       .addSource(springSource)
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
 
-               Optional<String> url = 
Settings.get().get("spring.datasource.url");
+               var url = settings.get("spring.datasource.url");
                assertTrue(url.isPresent());
                assertEquals("jdbc:postgresql://localhost/db", url.get());
 
-               Optional<String> username = 
Settings.get().get("spring.datasource.username");
+               var username = settings.get("spring.datasource.username");
                assertTrue(username.isPresent());
                assertEquals("admin", username.get());
        }
+
+       
//====================================================================================================
+       // addSource(FunctionalSource) - Functional interface usage
+       
//====================================================================================================
+       @Test
+       void p01_addSource_functionalSource() {
+               // Test the addSource(FunctionalSource) overload
+               var source = (FunctionalSource) name -> 
opt(System.getProperty(name));
+               System.setProperty(TEST_PROP, "system-value");
+               var settings = Settings.create()
+                       .addSource(source)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
+               var result = settings.get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("system-value", result.get());
+       }
+
+       @Test
+       void p02_addSource_functionalSource_factoryMethod() {
+               // Test addSource with FunctionalSource.of()
+               var source = FunctionalSource.of(System::getProperty);
+               System.setProperty(TEST_PROP, "system-value");
+               var settings = Settings.create()
+                       .addSource(source)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
+               var result = settings.get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("system-value", result.get());
+       }
+
+       
//====================================================================================================
+       // FunctionalStore
+       
//====================================================================================================
+       @Test
+       void q01_writeableFunctionalSource_basic() {
+               // Create a writable functional source using system properties
+               var source = FunctionalStore.of(
+                       System::getProperty,
+                       (k, v) -> System.setProperty(k, v),
+                       k -> System.clearProperty(k),
+                       () -> { /* No-op clear for system properties */ }
+               );
+
+               // Test set and get
+               source.set(TEST_PROP, "test-value");
+               var result = source.get(TEST_PROP);
+               assertNotNull(result);
+               assertTrue(result.isPresent());
+               assertEquals("test-value", result.get());
+
+               // Test unset
+               source.unset(TEST_PROP);
+               result = source.get(TEST_PROP);
+               assertNull(result); // Should return null when property doesn't 
exist
+
+               // Clean up
+               System.clearProperty(TEST_PROP);
+       }
+
+       @Test
+       void q02_writeableFunctionalSource_withSettings() {
+               // Create a writable functional source and add it to Settings
+               var source = FunctionalStore.of(
+                       System::getProperty,
+                       (k, v) -> System.setProperty(k, v),
+                       k -> System.clearProperty(k),
+                       () -> { /* No-op clear for system properties */ }
+               );
+
+               var settings = Settings.create()
+                       .addSource(source)
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
+
+               // Set a value through the source
+               source.set(TEST_PROP, "source-value");
+
+               // Get it through Settings
+               var result = settings.get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("source-value", result.get());
+
+               // Clean up
+               source.unset(TEST_PROP);
+               System.clearProperty(TEST_PROP);
+       }
+
+       @Test
+       void q03_writeableFunctionalSource_clear() {
+               // Test clear() functionality with a custom clearer
+               var map = new java.util.HashMap<String, String>();
+               var source = FunctionalStore.of(
+                       map::get,
+                       map::put,
+                       map::remove,
+                       map::clear
+               );
+
+               source.set(TEST_PROP, "test-value");
+               source.set(TEST_PROP_2, "test-value-2");
+
+               // Verify values are set
+               var result1 = source.get(TEST_PROP);
+               assertNotNull(result1);
+               assertTrue(result1.isPresent());
+               assertEquals("test-value", result1.get());
+
+               var result2 = source.get(TEST_PROP_2);
+               assertNotNull(result2);
+               assertTrue(result2.isPresent());
+               assertEquals("test-value-2", result2.get());
+
+               // Clear all values
+               source.clear();
+
+               // Verify values are cleared
+               result1 = source.get(TEST_PROP);
+               assertNull(result1);
+
+               result2 = source.get(TEST_PROP_2);
+               assertNull(result2);
+       }
+
+       
//====================================================================================================
+       // Builder.localStore() - Coverage for lines 205-206
+       
//====================================================================================================
+       @Test
+       void r01_localStore() {
+               // Test that localStore() method can be called on the builder
+               var settings = Settings.create()
+                       .localStore(OptionalSupplier.of(() -> new MapStore()))
+                       .build();
+               // Verify it works by setting a local value
+               settings.setLocal(TEST_PROP, "test-value");
+               var result = settings.get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("test-value", result.get());
+       }
+
+       
//====================================================================================================
+       // Global store disabled (null supplier) - Coverage for lines 476, 493, 
571
+       
//====================================================================================================
+       @Test
+       void s01_setGlobal_whenGlobalStoreDisabled() {
+               // Create Settings with null global store (disabled)
+               var settings = Settings.create()
+                       .globalStore(OptionalSupplier.empty())
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
+
+               // setGlobal() should throw IllegalStateException
+               assertThrows(IllegalStateException.class, () -> {
+                       settings.setGlobal(TEST_PROP, "value");
+               });
+       }
+
+       @Test
+       void s02_unsetGlobal_whenGlobalStoreDisabled() {
+               // Create Settings with null global store (disabled)
+               var settings = Settings.create()
+                       .globalStore(OptionalSupplier.empty())
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
+
+               // unsetGlobal() should throw IllegalStateException
+               assertThrows(IllegalStateException.class, () -> {
+                       settings.unsetGlobal(TEST_PROP);
+               });
+       }
+
+       @Test
+       void s03_clearGlobal_whenGlobalStoreDisabled() {
+               // Create Settings with null global store (disabled)
+               var settings = Settings.create()
+                       .globalStore(OptionalSupplier.empty())
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
+
+               // clearGlobal() should throw IllegalStateException
+               assertThrows(IllegalStateException.class, () -> {
+                       settings.clearGlobal();
+               });
+       }
+
+       
//====================================================================================================
+       // MapStore.unset() - Coverage for line 134 (when map is null)
+       
//====================================================================================================
+       @Test
+       void t01_mapStore_unset_whenMapNotInitialized() {
+               // Create a fresh MapStore that has never had set() called on it
+               // This means the internal map is still null (lazy 
initialization)
+               var store = new MapStore();
+
+               // Calling unset() when map is null should not throw and should 
do nothing
+               // This covers the branch where m == null in line 134
+               store.unset(TEST_PROP);
+
+               // Verify the map is still null (not initialized)
+               // get() should return null since the map doesn't exist
+               var result = store.get(TEST_PROP);
+               assertNull(result);
+       }
 }
 

Reply via email to