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 56aec1f802 feat: unify settings property source hierarchy and SVL 
lookup
56aec1f802 is described below

commit 56aec1f80253cfd194d0b87267fcecaffe0e4cb8
Author: James Bognar <[email protected]>
AuthorDate: Thu May 14 12:16:50 2026 -0400

    feat: unify settings property source hierarchy and SVL lookup
---
 .../apache/juneau/commons/lang/StringFormat.java   |   2 +-
 .../juneau/commons/reflect/AnnotationProvider.java |   4 +-
 .../juneau/commons/reflect/ParameterInfo.java      |   3 +-
 .../commons/settings/ArgsPropertySource.java       |  77 +++++
 .../settings/ArgsPropertySourceProvider.java       |  33 ++
 .../commons/settings/DotenvPropertySource.java     |  94 ++++++
 .../settings/DotenvPropertySourceProvider.java     |  33 ++
 .../commons/settings/FunctionalPropertySource.java |  44 +++
 .../commons/settings/FunctionalPropertyStore.java  |  86 +++++
 .../juneau/commons/settings/FunctionalSource.java  | 107 ------
 .../juneau/commons/settings/FunctionalStore.java   | 183 -----------
 .../settings/ManifestFilePropertySource.java       |  62 ++++
 .../ManifestFilePropertySourceProvider.java        |  33 ++
 .../apache/juneau/commons/settings/MapStore.java   |  38 +--
 .../commons/settings/PropertyLookupResult.java     |  92 ++++++
 .../juneau/commons/settings/PropertySource.java    |  45 +++
 .../commons/settings/PropertySourceProvider.java   |  42 +++
 .../juneau/commons/settings/PropertyStore.java     |  43 +++
 .../juneau/commons/settings/SettingSource.java     |  72 ----
 .../juneau/commons/settings/SettingStore.java      |  78 -----
 .../apache/juneau/commons/settings/Settings.java   | 106 +++---
 .../commons/settings/SystemEnvPropertySource.java  |  31 ++
 .../settings/SystemEnvPropertySourceProvider.java  |  33 ++
 .../settings/SystemPropertyPropertySource.java     |  31 ++
 .../SystemPropertyPropertySourceProvider.java      |  33 ++
 .../org/apache/juneau/commons/svl/VarList.java     |   2 +
 .../apache/juneau/commons/svl/vars/ArgsVar.java    |  41 +--
 .../juneau/commons/svl/vars/EnvVariablesVar.java   |   7 +-
 .../juneau/commons/svl/vars/ManifestFileVar.java   |  16 +-
 .../juneau/commons/svl/vars/PropertyVar.java       |  44 +++
 .../commons/svl/vars/SystemPropertiesVar.java      |  13 +-
 .../org/apache/juneau/commons/utils/IoUtils.java   |   4 +-
 .../apache/juneau/commons/utils/SystemUtils.java   |   3 +-
 ....juneau.commons.settings.PropertySourceProvider |  20 ++
 .../main/java/org/apache/juneau/config/Config.java |   4 +-
 .../apache/juneau/config/ConfigPropertySource.java |  50 +++
 .../org/apache/juneau/config/mod/XorEncodeMod.java |   3 +-
 .../apache/juneau/microservice/Microservice.java   |   2 +-
 .../microservice/jetty/JettyMicroservice.java      |   4 +-
 .../org/apache/juneau/rest/client/RestClient.java  |   6 +
 .../juneau/commons/reflect/ParameterInfo_Test.java |  20 +-
 .../commons/settings/PropertySources_Test.java     | 364 +++++++++++++++++++++
 .../juneau/commons/settings/Settings_Test.java     |  43 +--
 .../juneau/commons/svl/vars/PropertyVars_Test.java | 145 ++++++++
 .../juneau/config/ConfigPropertySource_Test.java   |  72 ++++
 45 files changed, 1683 insertions(+), 585 deletions(-)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/StringFormat.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/StringFormat.java
index e2d2101a54..ac886bb1f2 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/StringFormat.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/StringFormat.java
@@ -436,7 +436,7 @@ public final class StringFormat {
                abstract void append(StringBuilder sb, Object[] args, Locale 
locale);
        }
 
-       private static final CacheMode CACHE_MODE = 
CacheMode.parse(System.getProperty("juneau.StringFormat.caching", "FULL"));
+       private static final CacheMode CACHE_MODE = 
env("juneau.StringFormat.caching", CacheMode.FULL);
 
        private static final Cache<String,StringFormat> CACHE = 
Cache.of(String.class, 
StringFormat.class).maxSize(1000).cacheMode(CACHE_MODE).build();
        private static final Cache2<Locale,String,MessageFormat> 
MESSAGE_FORMAT_CACHE = Cache2.of(Locale.class, String.class, 
MessageFormat.class).maxSize(100).threadLocal().cacheMode(CACHE_MODE)
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationProvider.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationProvider.java
index bdba29bcb6..ba652841c8 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationProvider.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationProvider.java
@@ -450,7 +450,7 @@ public class AnnotationProvider {
         *      <li><c>FULL</c> - Uses ConcurrentHashMap (best performance)
         * </ul>
         */
-       private static final CacheMode CACHING_MODE = 
CacheMode.parse(System.getProperty("juneau.annotationProvider.caching", 
"FULL"));
+       private static final CacheMode CACHING_MODE = 
env("juneau.annotationProvider.caching", CacheMode.FULL);
 
        /**
         * Enable logging of cache statistics on JVM shutdown.
@@ -460,7 +460,7 @@ public class AnnotationProvider {
         * <br>Valid values: <c>TRUE</c>, <c>FALSE</c> (case-insensitive)
         * <br>Default: <c>FALSE</c>
         */
-       private static final boolean LOG_ON_EXIT = 
bool(System.getProperty("juneau.annotationProvider.caching.logOnExit"));
+       private static final boolean LOG_ON_EXIT = 
env("juneau.annotationProvider.caching.logOnExit", false);
 
        
//-----------------------------------------------------------------------------------------------------------------
        // Builder
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ParameterInfo.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ParameterInfo.java
index 67312ecfa5..284538b82d 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ParameterInfo.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ParameterInfo.java
@@ -31,6 +31,7 @@ import java.util.stream.*;
 
 import org.apache.juneau.commons.function.*;
 import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.commons.settings.Settings;
 import org.apache.juneau.commons.utils.*;
 
 /**
@@ -114,7 +115,7 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
         * <p>
         * The supplier can be reset for testing purposes using {@link 
#resetDisableParamNameDetection()}.
         */
-       static final Memoizer<Boolean> DISABLE_PARAM_NAME_DETECTION = 
memoizer(() -> Boolean.getBoolean("juneau.disableParamNameDetection"));
+       static final Memoizer<Boolean> DISABLE_PARAM_NAME_DETECTION = 
memoizer(() -> 
Settings.get().get("juneau.disableParamNameDetection").asBoolean().orElse(false));
 
        /**
         * Creates a ParameterInfo wrapper for the specified parameter.
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ArgsPropertySource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ArgsPropertySource.java
new file mode 100644
index 0000000000..6416f24708
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ArgsPropertySource.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.function.*;
+
+import org.apache.juneau.commons.runtime.*;
+
+/**
+ * Property source backed by {@link Args}.
+ */
+public class ArgsPropertySource implements PropertySource {
+
+       private final Supplier<Args> argsSupplier;
+
+       /**
+        * Constructor.
+        *
+        * @param argsSupplier Supplier for args.
+        */
+       public ArgsPropertySource(Supplier<Args> argsSupplier) {
+               this.argsSupplier = argsSupplier;
+       }
+
+       /**
+        * Creates a source using the default command-line discovery.
+        *
+        * @return A new source.
+        */
+       public static ArgsPropertySource createDefault() {
+               return new 
ArgsPropertySource(ArgsPropertySource::createDefaultArgs);
+       }
+
+       public static Args createDefaultArgs() {
+               var s = System.getProperty("sun.java.command");
+               if (ne(s)) {
+                       var i = s.indexOf(' ');
+                       return new Args(i == -1 ? "" : s.substring(i + 1));
+               }
+               return new Args(System.getProperty("juneau.args", ""));
+       }
+
+       @Override
+       public PropertyLookupResult get(String name) {
+               var args = argsSupplier.get();
+               if (args == null)
+                       return PropertyLookupResult.missing();
+               try {
+                       var index = Integer.parseInt(name);
+                       var v = args.get(index);
+                       return v.isPresent() ? PropertyLookupResult.present(v) 
: PropertyLookupResult.missing();
+               } catch (@SuppressWarnings("unused") NumberFormatException e) {
+                       // Fall through.
+               }
+               var values = args.getAll(name);
+               if (! values.isEmpty())
+                       return 
PropertyLookupResult.present(opt(String.join(",", values)));
+               var v = args.get(name);
+               return v.isPresent() ? PropertyLookupResult.present(v) : 
PropertyLookupResult.missing();
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ArgsPropertySourceProvider.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ArgsPropertySourceProvider.java
new file mode 100644
index 0000000000..ffbfd0dd80
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ArgsPropertySourceProvider.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * Provider for {@link ArgsPropertySource}.
+ */
+public class ArgsPropertySourceProvider implements PropertySourceProvider {
+
+       @Override
+       public PropertySource create() {
+               return ArgsPropertySource.createDefault();
+       }
+
+       @Override
+       public int order() {
+               return 50;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/DotenvPropertySource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/DotenvPropertySource.java
new file mode 100644
index 0000000000..66578a9dc9
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/DotenvPropertySource.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.io.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+
+/**
+ * Property source backed by a dotenv file.
+ */
+public class DotenvPropertySource implements PropertySource {
+
+       private static final String DEFAULT_PATH = ".env";
+       private static final String DOTENV_PATH_PROP = "juneau.dotenv.path";
+       private static final String DOTENV_PATH_ENV = "JUNEAU_DOTENV_PATH";
+
+       private final AtomicReference<Map<String,String>> map = new 
AtomicReference<>();
+       private final Path path;
+
+       /**
+        * Constructor with default path discovery.
+        */
+       public DotenvPropertySource() {
+               this(resolvePath());
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param path Dotenv path.
+        */
+       public DotenvPropertySource(Path path) {
+               this.path = path;
+       }
+
+       private static Path resolvePath() {
+               var configured = System.getProperty(DOTENV_PATH_PROP);
+               if (configured == null || configured.isEmpty())
+                       configured = System.getenv(DOTENV_PATH_ENV);
+               if (configured == null || configured.isEmpty())
+                       configured = DEFAULT_PATH;
+               return Paths.get(configured);
+       }
+
+       @Override
+       public PropertyLookupResult get(String name) {
+               var values = map.updateAndGet(existing -> existing != null ? 
existing : load(path));
+               var value = values.get(name);
+               return value == null ? PropertyLookupResult.missing() : 
PropertyLookupResult.present(opt(value));
+       }
+
+       private static Map<String,String> load(Path path) {
+               if (path == null || ! Files.exists(path))
+                       return Collections.emptyMap();
+               var m = new LinkedHashMap<String,String>();
+               try (var r = Files.newBufferedReader(path)) {
+                       String line;
+                       while ((line = r.readLine()) != null) {
+                               line = line.trim();
+                               if (! (line.isEmpty() || line.startsWith("#"))) 
{
+                                       var i = line.indexOf('=');
+                                       if (i > 0) {
+                                               var key = line.substring(0, 
i).trim();
+                                               var value = line.substring(i + 
1).trim();
+                                               if ((value.startsWith("\"") && 
value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'")))
+                                                       value = 
value.substring(1, value.length() - 1);
+                                               m.put(key, value);
+                                       }
+                               }
+                       }
+               } catch (@SuppressWarnings("unused") IOException unused) {
+                       return Collections.emptyMap();
+               }
+               return m;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/DotenvPropertySourceProvider.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/DotenvPropertySourceProvider.java
new file mode 100644
index 0000000000..7375bdcbb1
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/DotenvPropertySourceProvider.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * Provider for {@link DotenvPropertySource}.
+ */
+public class DotenvPropertySourceProvider implements PropertySourceProvider {
+
+       @Override
+       public PropertySource create() {
+               return new DotenvPropertySource();
+       }
+
+       @Override
+       public int order() {
+               return 20;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalPropertySource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalPropertySource.java
new file mode 100644
index 0000000000..0c135be478
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalPropertySource.java
@@ -0,0 +1,44 @@
+/*
+ * 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.Utils.*;
+
+import java.util.function.*;
+
+/**
+ * A functional interface for creating read-only {@link PropertySource} 
instances from a function.
+ */
+@FunctionalInterface
+public interface FunctionalPropertySource extends PropertySource {
+
+       @Override
+       PropertyLookupResult get(String name);
+
+       /**
+        * Creates a functional source from a function that returns a string.
+        *
+        * @param function The function to delegate property lookups to. Must 
not be <c>null</c>.
+        * @return A new functional property source instance.
+        */
+       static FunctionalPropertySource of(UnaryOperator<String> function) {
+               return name -> {
+                       var v = function.apply(name);
+                       return v == null ? PropertyLookupResult.missing() : 
PropertyLookupResult.present(opt(v));
+               };
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalPropertyStore.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalPropertyStore.java
new file mode 100644
index 0000000000..054f81b47d
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalPropertyStore.java
@@ -0,0 +1,86 @@
+/*
+ * 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.function.*;
+
+import org.apache.juneau.commons.function.*;
+
+/**
+ * A writable {@link PropertyStore} implementation created from functional 
interfaces.
+ */
+public class FunctionalPropertyStore implements PropertyStore {
+
+       private static final String ARG_reader = "reader";
+       private static final String ARG_writer = "writer";
+       private static final String ARG_unsetter = "unsetter";
+       private static final String ARG_clearer = "clearer";
+
+       private final UnaryOperator<String> reader;
+       private final BiConsumer<String, String> writer;
+       private final Consumer<String> unsetter;
+       private final Snippet clearer;
+
+       public FunctionalPropertyStore(
+               UnaryOperator<String> reader,
+               BiConsumer<String, String> writer,
+               Consumer<String> unsetter,
+               Snippet clearer
+       ) {
+               assertArgNotNull(ARG_reader, reader);
+               assertArgNotNull(ARG_writer, writer);
+               assertArgNotNull(ARG_unsetter, unsetter);
+               assertArgNotNull(ARG_clearer, clearer);
+               this.reader = reader;
+               this.writer = writer;
+               this.unsetter = unsetter;
+               this.clearer = clearer;
+       }
+
+       @Override
+       public PropertyLookupResult get(String name) {
+               var v = reader.apply(name);
+               return v == null ? PropertyLookupResult.missing() : 
PropertyLookupResult.present(opt(v));
+       }
+
+       @Override
+       public void set(String name, String value) {
+               writer.accept(name, value);
+       }
+
+       @Override
+       public void unset(String name) {
+               unsetter.accept(name);
+       }
+
+       @Override
+       public void clear() {
+               safe(clearer::run);
+       }
+
+       public static FunctionalPropertyStore of(
+               UnaryOperator<String> reader,
+               BiConsumer<String, String> writer,
+               Consumer<String> unsetter,
+               Snippet clearer
+       ) {
+               return new FunctionalPropertyStore(reader, writer, unsetter, 
clearer);
+       }
+}
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
deleted file mode 100644
index 90f26d1cd9..0000000000
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalSource.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.juneau.commons.settings;
-
-import static org.apache.juneau.commons.utils.Utils.*;
-
-import java.util.*;
-import java.util.function.*;
-
-/**
- * A functional interface for creating read-only {@link SettingSource} 
instances from a function.
- *
- * <p>
- * 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).
- *     <li>If the function returns a non-null value, this source returns 
<c>Optional.of(value)</c>.
- * </ul>
- *
- * <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 MapStore} instead.
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- *     <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>
- *     
Settings.<jsf>get</jsf>().addSource(FunctionalSource.<jsf>of</jsf>(System::getenv));
- *
- *     <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>
- */
-@FunctionalInterface
-public interface FunctionalSource extends SettingSource {
-
-       /**
-        * 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).
-        * If the 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 function returns 
<c>null</c>.
-        */
-       @Override
-       Optional<String> get(String name);
-
-       /**
-        * Creates a functional source from a function that returns a string.
-        *
-        * <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>
-        *
-        * <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));
-        *
-        *      <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.
-        */
-       static FunctionalSource of(UnaryOperator<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
deleted file mode 100644
index 6ca511a3f9..0000000000
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalStore.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.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>
- */
-@SuppressWarnings({
-       "java:S115" // Constants use UPPER_snakeCase convention
-})
-public class FunctionalStore implements SettingStore {
-
-       // Argument name constants for assertArgNotNull
-       private static final String ARG_reader = "reader";
-       private static final String ARG_writer = "writer";
-       private static final String ARG_unsetter = "unsetter";
-       private static final String ARG_clearer = "clearer";
-
-       private final UnaryOperator<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(
-               UnaryOperator<String> reader,
-               BiConsumer<String, String> writer,
-               Consumer<String> unsetter,
-               Snippet clearer
-       ) {
-               assertArgNotNull(ARG_reader, reader);
-               assertArgNotNull(ARG_writer, writer);
-               assertArgNotNull(ARG_unsetter, unsetter);
-               assertArgNotNull(ARG_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(
-               UnaryOperator<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/ManifestFilePropertySource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ManifestFilePropertySource.java
new file mode 100644
index 0000000000..efcc7f7239
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ManifestFilePropertySource.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import java.util.function.*;
+
+import org.apache.juneau.commons.runtime.*;
+
+/**
+ * Property source backed by {@link ManifestFile}.
+ */
+public class ManifestFilePropertySource implements PropertySource {
+
+       private final Supplier<ManifestFile> manifestSupplier;
+
+       /**
+        * Constructor.
+        *
+        * @param manifestSupplier The supplier for manifest file.
+        */
+       public ManifestFilePropertySource(Supplier<ManifestFile> 
manifestSupplier) {
+               this.manifestSupplier = manifestSupplier;
+       }
+
+       /**
+        * Creates a source with default classloader scanning behavior.
+        *
+        * @return A new source.
+        */
+       public static ManifestFilePropertySource createDefault() {
+               return new ManifestFilePropertySource(() -> {
+                       try {
+                               return new 
ManifestFile(ManifestFilePropertySource.class);
+                       } catch (@SuppressWarnings("unused") Exception unused) {
+                               return null;
+                       }
+               });
+       }
+
+       @Override
+       public PropertyLookupResult get(String name) {
+               var mf = manifestSupplier.get();
+               if (mf == null)
+                       return PropertyLookupResult.missing();
+               var v = mf.get(name);
+               return v.isPresent() ? PropertyLookupResult.present(v) : 
PropertyLookupResult.missing();
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ManifestFilePropertySourceProvider.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ManifestFilePropertySourceProvider.java
new file mode 100644
index 0000000000..ac456f7853
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ManifestFilePropertySourceProvider.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * Provider for {@link ManifestFilePropertySource}.
+ */
+public class ManifestFilePropertySourceProvider implements 
PropertySourceProvider {
+
+       @Override
+       public PropertySource create() {
+               return ManifestFilePropertySource.createDefault();
+       }
+
+       @Override
+       public int order() {
+               return 10;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapStore.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapStore.java
index 2e826da92f..14c5129b77 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapStore.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapStore.java
@@ -23,7 +23,7 @@ import java.util.concurrent.*;
 import java.util.concurrent.atomic.*;
 
 /**
- * A writable {@link SettingStore} implementation backed by a thread-safe map.
+ * A writable {@link PropertyStore} implementation backed by a thread-safe map.
  *
  * <p>
  * This class provides a mutable store for settings that can be modified at 
runtime. It's particularly useful
@@ -38,8 +38,9 @@ import java.util.concurrent.atomic.*;
  * <h5 class='section'>Null Value Handling:</h5>
  * <p>
  * Setting a value to <c>null</c> stores <c>Optional.empty()</c> in the map, 
which means {@link #get(String)}
- * will return <c>Optional.empty()</c> (not <c>null</c>). This allows you to 
explicitly override system properties
- * with null values. Use {@link #unset(String)} if you want to remove a key 
entirely (so {@link #get(String)} returns <c>null</c>).
+ * will return a {@link PropertyLookupResult#present(Optional)} result with an 
empty optional.
+ * This allows you to explicitly override system properties with null values.
+ * Use {@link #unset(String)} if you want to remove a key entirely (so {@link 
#get(String)} returns {@link PropertyLookupResult#missing()}).
  *
  * <h5 class='section'>Example:</h5>
  * <p class='bjava'>
@@ -53,14 +54,14 @@ import java.util.concurrent.atomic.*;
  *
  *     <jc>// Override a system property with null</jc>
  *     <jv>store</jv>.set(<js>"system.property"</js>, <jk>null</jk>);
- *     <jc>// get() will now return Optional.empty() for "system.property"</jc>
+ *     <jc>// get() will now return present(Optional.empty()) for 
"system.property"</jc>
  *
  *     <jc>// Remove a property entirely</jc>
  *     <jv>store</jv>.unset(<js>"my.property"</js>);
- *     <jc>// get() will now return null for "my.property"</jc>
+ *     <jc>// get() will now return missing() for "my.property"</jc>
  * </p>
  */
-public class MapStore implements SettingStore {
+public class MapStore implements PropertyStore {
 
        private final AtomicReference<Map<String,Optional<String>>> map = new 
AtomicReference<>();
 
@@ -68,19 +69,19 @@ public class MapStore implements SettingStore {
         * 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
-        * <c>Optional.empty()</c> if the value was explicitly set to 
<c>null</c>).
+        * Returns {@link PropertyLookupResult#missing()} if the key doesn't 
exist in the map, or a present result
+        * (which may contain {@link Optional#empty()} if the value was 
explicitly set to <c>null</c>).
         *
         * @param key The property name.
-        * @return The property value, <c>null</c> if the key doesn't exist, or 
<c>Optional.empty()</c> if the key
-        *      exists but has a null value.
+        * @return The property lookup result.
         */
        @Override
-       public Optional<String> get(String key) {
+       public PropertyLookupResult get(String key) {
                var m = map.get();
                if (m == null)
-                       return null;
-               return m.get(key);
+                       return PropertyLookupResult.missing();
+               var value = m.get(key);
+               return value == null ? PropertyLookupResult.missing() : 
PropertyLookupResult.present(value);
        }
 
        /**
@@ -88,8 +89,8 @@ public class MapStore implements SettingStore {
         *
         * <p>
         * The internal map is lazily initialized on the first call to this 
method. Setting a value to <c>null</c>
-        * stores <c>Optional.empty()</c> in the map, which means {@link 
#get(String)} will return <c>Optional.empty()</c>
-        * (not <c>null</c>). This allows you to explicitly override system 
properties with null values.
+        * stores <c>Optional.empty()</c> in the map, which means {@link 
#get(String)} will return a present result
+        * with an empty optional. This allows you to explicitly override 
system properties with null values.
         *
         * @param key The property name.
         * @param value The property value, or <c>null</c> to set an empty 
override.
@@ -109,7 +110,7 @@ public class MapStore implements SettingStore {
         *
         * <p>
         * After calling this method, all keys will be removed from the map, 
and {@link #get(String)} will return
-        * <c>null</c> for all keys.
+        * {@link PropertyLookupResult#missing()} for all keys.
         */
        @Override
        public void clear() {
@@ -122,9 +123,8 @@ public class MapStore implements SettingStore {
         * 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).
+        * After calling this method, {@link #get(String)} will return {@link 
PropertyLookupResult#missing()} for
+        * the specified key, indicating that the key doesn't exist in this 
store.
         *
         * @param name The property name to remove.
         */
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertyLookupResult.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertyLookupResult.java
new file mode 100644
index 0000000000..36062e87d6
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertyLookupResult.java
@@ -0,0 +1,92 @@
+/*
+ * 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.Utils.*;
+
+import java.util.*;
+
+/**
+ * Tri-state result of a property lookup.
+ *
+ * <p>
+ * This type disambiguates source lookup semantics:
+ * <ul class='spaced-list'>
+ *     <li>{@link #missing()} - The key does not exist in the source.
+ *     <li>{@link #present(Optional)} with {@link Optional#empty()} - The key 
exists and resolves to a null value.
+ *     <li>{@link #present(Optional)} with a value - The key exists and 
resolves to a non-null value.
+ * </ul>
+ */
+public final class PropertyLookupResult {
+
+       private static final PropertyLookupResult MISSING = new 
PropertyLookupResult(false, opte());
+
+       private final boolean present;
+       private final Optional<String> value;
+
+       private PropertyLookupResult(boolean present, Optional<String> value) {
+               this.present = present;
+               this.value = value;
+       }
+
+       /**
+        * Returns a result indicating the key is absent in this source.
+        *
+        * @return A missing result.
+        */
+       public static PropertyLookupResult missing() {
+               return MISSING;
+       }
+
+       /**
+        * Returns a result indicating the key is present with the specified 
value.
+        *
+        * @param value The value optional. Must not be <jk>null</jk>.
+        * @return A present result.
+        */
+       public static PropertyLookupResult present(Optional<String> value) {
+               return new PropertyLookupResult(true, 
Objects.requireNonNull(value));
+       }
+
+       /**
+        * Returns a result indicating the key is present with the specified 
value.
+        *
+        * @param value The value.
+        * @return A present result.
+        */
+       public static PropertyLookupResult present(String value) {
+               return present(opt(value));
+       }
+
+       /**
+        * Returns <jk>true</jk> if the key is present in this source.
+        *
+        * @return <jk>true</jk> if present.
+        */
+       public boolean isPresent() {
+               return present;
+       }
+
+       /**
+        * Returns the resolved value for present results.
+        *
+        * @return The value, never <jk>null</jk>.
+        */
+       public Optional<String> value() {
+               return value;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySource.java
new file mode 100644
index 0000000000..708468c780
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySource.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.settings;
+
+import java.util.*;
+
+/**
+ * Interface for pluggable property sources used by {@link Settings}.
+ *
+ * <p>
+ * A property 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 
PropertyStore}.
+ */
+public interface PropertySource {
+
+       /**
+        * Returns a property in this property source.
+        *
+        * @param name The property name.
+        * @return The property lookup result:
+        *      <ul class='spaced-list'>
+        *              <li>{@link PropertyLookupResult#missing()} if this 
source does not define the key.
+        *              <li>{@link PropertyLookupResult#present(Optional)} with 
{@link Optional#empty()} if the key exists with a null value.
+        *              <li>{@link PropertyLookupResult#present(Optional)} with 
a value if the key exists with a non-null value.
+        *      </ul>
+        */
+       PropertyLookupResult get(String name);
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySourceProvider.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySourceProvider.java
new file mode 100644
index 0000000000..0b5305aecc
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertySourceProvider.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * Provider SPI for augmenting {@link Settings} with additional property 
sources.
+ */
+public interface PropertySourceProvider {
+
+       /**
+        * Creates a property source.
+        *
+        * @return The property source, or <jk>null</jk> to skip registration.
+        */
+       PropertySource create();
+
+       /**
+        * Sort key used by ServiceLoader wiring.
+        *
+        * <p>
+        * Lower values are added first.
+        *
+        * @return The order value.
+        */
+       default int order() {
+               return 0;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertyStore.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertyStore.java
new file mode 100644
index 0000000000..255ce83a4e
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/PropertyStore.java
@@ -0,0 +1,43 @@
+/*
+ * 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 PropertySource} that supports modifying 
property values.
+ */
+public interface PropertyStore extends PropertySource {
+
+       /**
+        * Sets a property 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 property from this store.
+        *
+        * @param name The property name to remove.
+        */
+       void unset(String name);
+
+       /**
+        * Clears all properties from this store.
+        */
+       void clear();
+}
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
deleted file mode 100644
index 63e2fdf353..0000000000
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingSource.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.juneau.commons.settings;
-
-import java.util.*;
-
-/**
- * Interface for pluggable property sources used by {@link Settings}.
- *
- * <p>
- * 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.
- *     <li><c>Optional.empty()</c> - The setting exists but has an explicitly 
null value. This will be returned
- *             immediately, overriding any values from lower-priority sources.
- *     <li><c>Optional.of(value)</c> - The setting exists and has a non-null 
value. This will be returned immediately.
- * </ul>
- *
- * <h5 class='section'>Examples:</h5>
- * <p class='bjava'>
- *     <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>// 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 {
-
-       /**
-        * Returns a setting in this setting source.
-        *
-        * <p>
-        * Return value semantics:
-        * <ul>
-        *      <li><c>null</c> - The setting does not exist in this source. 
The lookup will continue to the next source.
-        *      <li><c>Optional.empty()</c> - The setting exists but has an 
explicitly null value. This will be returned
-        *              immediately, overriding any values from lower-priority 
sources.
-        *      <li><c>Optional.of(value)</c> - The setting exists and has a 
non-null value. This will be returned immediately.
-        * </ul>
-        *
-        * @param name The property name.
-        * @return The property value, <c>null</c> if the property doesn't 
exist in this source, or <c>Optional.empty()</c>
-        *      if the property exists but has a null value.
-        */
-       Optional<String> get(String name);
-}
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
deleted file mode 100644
index 5159b2be52..0000000000
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingStore.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.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 71cb854a66..651fdc44a7 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
@@ -47,23 +47,23 @@ import org.apache.juneau.commons.reflect.*;
  * <ol>
  *     <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>Sources in reverse order (last source added via {@link 
Builder#addSource(PropertySource)} 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)}
+ *     <li><b>Sources</b> ({@link PropertySource}) - Provide read-only access 
to property values. Examples: {@link FunctionalPropertySource}
+ *     <li><b>Stores</b> ({@link PropertyStore}) - Provide read/write access 
to property values. Examples: {@link MapStore}, {@link FunctionalPropertyStore}
+ *     <li>Stores can be used as sources (they extend {@link PropertySource}), 
so you can add stores via {@link Builder#addSource(PropertySource)}
  * </ul>
  *
  * <h5 class='section'>Features:</h5>
  * <ul class='spaced-list'>
  *     <li>System property access - read Java system properties with type 
conversion via {@link StringSetting}
- *     <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>Global overrides - override system properties globally for all 
threads (stored in a {@link PropertyStore})
+ *     <li>Per-thread overrides - override system properties for specific 
threads (stored in a per-thread {@link PropertyStore})
  *     <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 - type conversion methods on {@link 
StringSetting} for common types: Integer, Long, Boolean, Double, Float, File, 
Path, URI, Charset
@@ -112,7 +112,7 @@ import org.apache.juneau.commons.reflect.*;
  *     <jv>springSource</jv>.set(<js>"spring.datasource.url"</js>, 
<js>"jdbc:postgresql://localhost/db"</js>);
  *     Settings <jv>custom</jv> = Settings.<jsf>create</jsf>()
  *             .addSource(<jv>springSource</jv>)
- *             .addSource(FunctionalSource.<jsf>of</jsf>(System::getProperty))
+ *             
.addSource(FunctionalPropertySource.<jsf>of</jsf>(System::getProperty))
  *             .build();
  * </p>
  *
@@ -150,14 +150,14 @@ public class Settings {
        /**
         * System property source that delegates to {@link 
System#getProperty(String)}.
         */
-       public static final SettingSource SYSTEM_PROPERTY_SOURCE = 
FunctionalSource.of(System::getProperty);
+       public static final PropertySource SYSTEM_PROPERTY_SOURCE = 
FunctionalPropertySource.of(System::getProperty);
 
        private static final Set<String> FROM_STRING_METHOD_NAMES = new 
LinkedHashSet<>(Arrays.asList("fromString", "parse", "forName", "valueOf"));
 
        /**
         * System environment variable source that delegates to {@link 
System#getenv(String)}.
         */
-       public static final SettingSource SYSTEM_ENV_SOURCE = 
FunctionalSource.of(System::getenv);
+       public static final PropertySource SYSTEM_ENV_SOURCE = 
FunctionalPropertySource.of(System::getenv);
 
        private static final String DISABLE_GLOBAL_PROP = 
"juneau.settings.disableGlobal";
        private static final String MSG_globalDisabled = "Global settings not 
enabled";
@@ -167,16 +167,13 @@ public class Settings {
         * Returns properties for this Settings object itself.
         * Note that these are initialized at startup and not changeable 
through System.setProperty().
         */
-       @SuppressWarnings({
-               "java:S2789" // null check on Optional is intentional - 
SettingSource.get() returns null if key doesn't exist, Optional.empty() if key 
exists with null value
-       })
        private static final Optional<String> initProperty(String property) {
                var v = SYSTEM_PROPERTY_SOURCE.get(property);
-               if (v != null)
-                       return v;  // Not testable
+               if (v.isPresent())
+                       return v.value();  // Not testable
                v = SYSTEM_ENV_SOURCE.get(property.replace('.', 
'_').toUpperCase());
-               if (v != null)
-                       return v;  // Not testable
+               if (v.isPresent())
+                       return v.value();  // Not testable
                return opte();
        }
 
@@ -194,7 +191,7 @@ public class Settings {
         *      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))
+        *              
.addSource(FunctionalPropertySource.<jsf>of</jsf>(System::getProperty))
         *              .build();
         * </p>
         *
@@ -208,9 +205,9 @@ public class Settings {
         * Builder for creating Settings instances.
         */
        public static class Builder {
-               private Supplier<SettingStore> globalStoreSupplier = 
MapStore::new;
-               private Supplier<SettingStore> localStoreSupplier = 
MapStore::new;
-               private final List<SettingSource> sources = new ArrayList<>();
+               private Supplier<PropertyStore> globalStoreSupplier = 
MapStore::new;
+               private Supplier<PropertyStore> localStoreSupplier = 
MapStore::new;
+               private final List<PropertySource> sources = new ArrayList<>();
                private final Map<Class<?>,Function<String,?>> 
customTypeFunctions = new IdentityHashMap<>();
 
                /**
@@ -219,7 +216,7 @@ public class Settings {
                 * @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(NullableSupplier<SettingStore> 
supplier) {
+               public Builder globalStore(NullableSupplier<PropertyStore> 
supplier) {
                        this.globalStoreSupplier = 
assertArgNotNull(ARG_supplier, supplier);
                        return this;
                }
@@ -230,7 +227,7 @@ public class Settings {
                 * @param supplier The supplier for the local store. Must not 
be <c>null</c>.
                 * @return This builder for method chaining.
                 */
-               public Builder localStore(NullableSupplier<SettingStore> 
supplier) {
+               public Builder localStore(NullableSupplier<PropertyStore> 
supplier) {
                        this.localStoreSupplier = 
assertArgNotNull(ARG_supplier, supplier);
                        return this;
                }
@@ -242,7 +239,7 @@ public class Settings {
                 * @return This builder for method chaining.
                 */
                @SafeVarargs
-               public final Builder setSources(SettingSource...sources) {
+               public final Builder setSources(PropertySource...sources) {
                        assertArgNoNulls(ARG_sources, sources);
                        this.sources.clear();
                        for (var source : sources) {
@@ -257,7 +254,7 @@ public class Settings {
                 * @param source The source to add. Must not be <c>null</c>.
                 * @return This builder for method chaining.
                 */
-               public Builder addSource(SettingSource source) {
+               public Builder addSource(PropertySource source) {
                        assertArgNotNull(ARG_source, source);
                        this.sources.add(source);
                        return this;
@@ -269,8 +266,27 @@ public class Settings {
                 * @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);
+               public Builder addSource(FunctionalPropertySource source) {
+                       return addSource((PropertySource)source);
+               }
+
+               /**
+                * Discovers and registers {@link PropertySourceProvider} 
instances via {@link ServiceLoader}.
+                *
+                * <p>
+                * Providers are sorted by {@link 
PropertySourceProvider#order()} ascending.
+                *
+                * @return This builder for method chaining.
+                */
+               public Builder useServiceLoader() {
+                       ServiceLoader.load(PropertySourceProvider.class)
+                               .stream()
+                               .map(ServiceLoader.Provider::get)
+                               
.sorted(Comparator.comparingInt(PropertySourceProvider::order))
+                               .map(PropertySourceProvider::create)
+                               .filter(Objects::nonNull)
+                               .forEach(this::addSource);
+                       return this;
                }
 
                /**
@@ -319,6 +335,7 @@ public class Settings {
        private static final Settings INSTANCE = new Builder()
                
.globalStore(initProperty(DISABLE_GLOBAL_PROP).map(Boolean::valueOf).orElse(false)
 ? () -> null : MapStore::new)
                .setSources(SYSTEM_ENV_SOURCE, SYSTEM_PROPERTY_SOURCE)
+               .useServiceLoader()
                .build();
 
        /**
@@ -330,12 +347,12 @@ public class Settings {
                return INSTANCE;
        }
 
-       private final Memoizer<SettingStore> globalStore;
+       private final Memoizer<PropertyStore> globalStore;
        @SuppressWarnings({
                "java:S5164" // Cleanup method provided: cleanup()
        })
-       private final ThreadLocal<SettingStore> localStore;
-       private final List<SettingSource> sources;
+       private final ThreadLocal<PropertyStore> localStore;
+       private final List<PropertySource> sources;
        private final Map<Class<?>,Function<String,?>> toTypeFunctions;
 
        /**
@@ -348,6 +365,18 @@ public class Settings {
                this.toTypeFunctions = new 
ConcurrentHashMap<>(builder.customTypeFunctions);
        }
 
+       /**
+        * Adds a property source after this settings instance has been built.
+        *
+        * @param source The property source to add. Must not be <jk>null</jk>.
+        * @return This object for method chaining.
+        */
+       public Settings addSource(PropertySource source) {
+               assertArgNotNull(ARG_source, source);
+               sources.add(source);
+               return this;
+       }
+
        /**
         * Returns a {@link StringSetting} for the specified system property.
         *
@@ -361,36 +390,33 @@ public class Settings {
         * <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 
Builder#addSource(SettingSource)} is checked first)
+        *      <li>Sources in reverse order (last source added via {@link 
Builder#addSource(PropertySource)} is checked first)
         *      <li>System property source (default, always second-to-last)
         *      <li>System environment variable source (default, always last)
         * </ol>
         *
         * @param name The property name. Must not be <jk>null</jk>.
-        * @return A {@link StringSetting} that provides the property value, or 
<jk>null</jk> if not found.
+        * @return A {@link StringSetting} that provides the resolved property 
value.
         */
-       @SuppressWarnings({
-               "java:S2789" // null check on Optional is intentional - 
SettingStore.get()/SettingSource.get() return null if key doesn't exist, 
Optional.empty() if key exists with null value
-       })
        public StringSetting get(String name) {
                assertArgNotNull(ARG_name, name);
                return new StringSetting(this, () -> {
                        // 1. Check thread-local override
                        var v = localStore.get().get(name);
-                       if (v != null)
-                               return v.orElse(null); // v is Optional.empty() 
if key exists with null value, or Optional.of(value) if present
+                       if (v.isPresent())
+                               return v.value().orElse(null); // Present 
result: Optional.empty() means explicit null override.
 
                        // 2. Check global override
                        v = globalStore.get().get(name);
-                       if (v != null)
-                               return v.orElse(null); // v is Optional.empty() 
if key exists with null value, or Optional.of(value) if present
+                       if (v.isPresent())
+                               return v.value().orElse(null); // Present 
result: Optional.empty() means explicit null override.
 
                        // 3. Check sources in reverse order (last added first)
                        for (int i = sources.size() - 1; i >= 0; i--) {
                                var source = sources.get(i);
                                var result = source.get(name);
-                               if (result != null)
-                                       return result.orElse(null);
+                               if (result.isPresent())
+                                       return result.value().orElse(null);
                        }
 
                        return null;
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemEnvPropertySource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemEnvPropertySource.java
new file mode 100644
index 0000000000..bcc1aa59dc
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemEnvPropertySource.java
@@ -0,0 +1,31 @@
+/*
+ * 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.Utils.*;
+
+/**
+ * Property source backed by {@link System#getenv(String)}.
+ */
+public class SystemEnvPropertySource implements PropertySource {
+
+       @Override
+       public PropertyLookupResult get(String name) {
+               var v = System.getenv(name);
+               return v == null ? PropertyLookupResult.missing() : 
PropertyLookupResult.present(opt(v));
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemEnvPropertySourceProvider.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemEnvPropertySourceProvider.java
new file mode 100644
index 0000000000..bb14ded7d7
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemEnvPropertySourceProvider.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * Provider for {@link SystemEnvPropertySource}.
+ */
+public class SystemEnvPropertySourceProvider implements PropertySourceProvider 
{
+
+       @Override
+       public PropertySource create() {
+               return new SystemEnvPropertySource();
+       }
+
+       @Override
+       public int order() {
+               return 30;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemPropertyPropertySource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemPropertyPropertySource.java
new file mode 100644
index 0000000000..28f0bff73b
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemPropertyPropertySource.java
@@ -0,0 +1,31 @@
+/*
+ * 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.Utils.*;
+
+/**
+ * Property source backed by {@link System#getProperty(String)}.
+ */
+public class SystemPropertyPropertySource implements PropertySource {
+
+       @Override
+       public PropertyLookupResult get(String name) {
+               var v = System.getProperty(name);
+               return v == null ? PropertyLookupResult.missing() : 
PropertyLookupResult.present(opt(v));
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemPropertyPropertySourceProvider.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemPropertyPropertySourceProvider.java
new file mode 100644
index 0000000000..80caf735d9
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SystemPropertyPropertySourceProvider.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * Provider for {@link SystemPropertyPropertySource}.
+ */
+public class SystemPropertyPropertySourceProvider implements 
PropertySourceProvider {
+
+       @Override
+       public PropertySource create() {
+               return new SystemPropertyPropertySource();
+       }
+
+       @Override
+       public int order() {
+               return 40;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarList.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarList.java
index 0ed70da91a..78d2935c63 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarList.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/VarList.java
@@ -85,6 +85,7 @@ public class VarList extends ArrayList<Object> {
         * <p>
         * The default variables are:
         * <ul>
+        *      <li>{@link PropertyVar}
         *      <li>{@link SystemPropertiesVar}
         *      <li>{@link EnvVariablesVar}
         *      <li>{@link ArgsVar}
@@ -107,6 +108,7 @@ public class VarList extends ArrayList<Object> {
        public VarList addDefault() {
                // @formatter:off
                return append(
+                       PropertyVar.class,
                        SystemPropertiesVar.class,
                        EnvVariablesVar.class,
                        ManifestFileVar.class,
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ArgsVar.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ArgsVar.java
index 45887033a0..1d8dbf38a2 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ArgsVar.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ArgsVar.java
@@ -16,12 +16,11 @@
  */
 package org.apache.juneau.commons.svl.vars;
 
-import static org.apache.juneau.commons.utils.Utils.*;
-
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.*;
 import java.util.function.*;
 
 import org.apache.juneau.commons.runtime.*;
+import org.apache.juneau.commons.settings.*;
 import org.apache.juneau.commons.svl.*;
 
 /**
@@ -69,7 +68,7 @@ public class ArgsVar extends DefaultingVar {
        /** The name of this variable. */
        public static final String NAME = "A";
 
-       private static final AtomicReference<Args> staticArgs = new 
AtomicReference<>();
+       private static final AtomicReference<Supplier<Args>> 
STATIC_ARGS_SUPPLIER = new 
AtomicReference<>(ArgsPropertySource::createDefaultArgs);
 
        /**
         * Initialize the args for this variable.
@@ -81,7 +80,7 @@ public class ArgsVar extends DefaultingVar {
         * @param args The parsed command-line arguments.
         */
        public static void init(Args args) {
-               staticArgs.set(args);
+               STATIC_ARGS_SUPPLIER.set(() -> args);
        }
 
        /**
@@ -99,46 +98,24 @@ public class ArgsVar extends DefaultingVar {
                return new ArgsVar(supplier);
        }
 
-       private final Supplier<Args> argsSupplier;
+       private final ArgsPropertySource source;
 
        /**
         * Constructor.
         */
        public ArgsVar() {
                super(NAME);
-               var captured = computeDefault();
-               this.argsSupplier = () -> captured;
+               this.source = new ArgsPropertySource(() -> 
STATIC_ARGS_SUPPLIER.get().get());
        }
 
        private ArgsVar(Supplier<Args> supplier) {
                super(NAME);
-               this.argsSupplier = supplier;
-       }
-
-       private static Args computeDefault() {
-               var sa = staticArgs.get();
-               if (nn(sa))
-                       return sa;
-               var s = System.getProperty("sun.java.command");
-               if (ne(s)) {
-                       var i = s.indexOf(' ');
-                       return new Args(i == -1 ? "" : s.substring(i + 1));
-               }
-               return new Args(System.getProperty("juneau.args", ""));
+               this.source = new ArgsPropertySource(supplier);
        }
 
        @Override /* Overridden from Var */
        public String resolve(VarResolverSession session, String key) {
-               var args = argsSupplier.get();
-               if (args == null)
-                       return null;
-               try {
-                       var idx = Integer.parseInt(key);
-                       return args.get(idx).orElse(null);
-               } catch (@SuppressWarnings("unused") NumberFormatException e) {
-                       // not a positional index; fall through to named-option 
lookup
-               }
-               var values = args.getAll(key);
-               return values.isEmpty() ? null : String.join(",", values);
+               var result = source.get(key);
+               return result.isPresent() ? result.value().orElse(null) : null;
        }
 }
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/EnvVariablesVar.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/EnvVariablesVar.java
index 74e3895ae1..05b75f8eb1 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/EnvVariablesVar.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/EnvVariablesVar.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.commons.svl.vars;
 
 import org.apache.juneau.commons.svl.*;
+import org.apache.juneau.commons.settings.*;
 
 /**
  * Environment variable variable resolver.
@@ -42,6 +43,8 @@ import org.apache.juneau.commons.svl.*;
  * </ul>
  */
 public class EnvVariablesVar extends DefaultingVar {
+       private final SystemEnvPropertySource source = new 
SystemEnvPropertySource();
+
 
        /** The name of this variable. */
        public static final String NAME = "E";
@@ -55,7 +58,7 @@ public class EnvVariablesVar extends DefaultingVar {
 
        @Override /* Overridden from Var */
        public String resolve(VarResolverSession session, String varVal) {
-               // Note that lookup is case-insensitive on windows.
-               return System.getenv(varVal);
+               var v = source.get(varVal);
+               return v.isPresent() ? v.value().orElse(null) : null;
        }
 }
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ManifestFileVar.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ManifestFileVar.java
index 8f70c9e83a..bc6a3fdd90 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ManifestFileVar.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/ManifestFileVar.java
@@ -16,10 +16,10 @@
  */
 package org.apache.juneau.commons.svl.vars;
 
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.*;
 
 import org.apache.juneau.commons.runtime.*;
+import org.apache.juneau.commons.settings.*;
 import org.apache.juneau.commons.svl.*;
 
 /**
@@ -62,7 +62,7 @@ public class ManifestFileVar extends DefaultingVar {
        /** The name of this variable. */
        public static final String NAME = "MF";
 
-       private static final AtomicReference<ManifestFile> manifestFile = new 
AtomicReference<>();
+       private static volatile Supplier<ManifestFile> manifestSupplier = () -> 
null;
 
        /**
         * Initialize the manifest file for this variable.
@@ -74,7 +74,7 @@ public class ManifestFileVar extends DefaultingVar {
         * @param manifestFile The parsed manifest file.
         */
        public static void init(ManifestFile manifestFile) {
-               ManifestFileVar.manifestFile.set(manifestFile);
+               ManifestFileVar.manifestSupplier = () -> manifestFile;
        }
 
        /**
@@ -91,24 +91,24 @@ public class ManifestFileVar extends DefaultingVar {
                return new ManifestFileVar(supplier);
        }
 
-       private final Supplier<ManifestFile> manifestSupplier;
+       private final ManifestFilePropertySource source;
 
        /**
         * Constructor.
         */
        public ManifestFileVar() {
                super(NAME);
-               this.manifestSupplier = manifestFile::get;
+               this.source = new ManifestFilePropertySource(() -> 
manifestSupplier.get());
        }
 
        private ManifestFileVar(Supplier<ManifestFile> supplier) {
                super(NAME);
-               this.manifestSupplier = supplier;
+               this.source = new ManifestFilePropertySource(supplier);
        }
 
        @Override /* Overridden from Var */
        public String resolve(VarResolverSession session, String key) {
-               var mf = manifestSupplier.get();
-               return mf == null ? "" : mf.get(key).orElse(null);
+               var v = source.get(key);
+               return v.isPresent() ? v.value().orElse(null) : "";
        }
 }
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/PropertyVar.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/PropertyVar.java
new file mode 100644
index 0000000000..9aeba335b3
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/PropertyVar.java
@@ -0,0 +1,44 @@
+/*
+ * 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.svl.vars;
+
+import org.apache.juneau.commons.settings.*;
+import org.apache.juneau.commons.svl.*;
+
+/**
+ * Unified property variable resolver.
+ *
+ * <p>
+ * The format for this var is <js>"$P{propertyName[,defaultValue]}"</js>.
+ */
+public class PropertyVar extends DefaultingVar {
+
+       /** The name of this variable. */
+       public static final String NAME = "P";
+
+       /**
+        * Constructor.
+        */
+       public PropertyVar() {
+               super(NAME);
+       }
+
+       @Override /* Overridden from Var */
+       public String resolve(VarResolverSession session, String key) {
+               return Settings.get().get(key).orElse(null);
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/SystemPropertiesVar.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/SystemPropertiesVar.java
index 8e436c4709..0a315ccbf1 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/SystemPropertiesVar.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/svl/vars/SystemPropertiesVar.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.commons.svl.vars;
 
 import org.apache.juneau.commons.svl.*;
+import org.apache.juneau.commons.settings.*;
 
 /**
  * System property variable resolver.
@@ -41,7 +42,9 @@ import org.apache.juneau.commons.svl.*;
  *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/SimpleVariableLanguageBasics";>Simple
 Variable Language Basics</a>
  * </ul>
  */
-public class SystemPropertiesVar extends MapVar {
+public class SystemPropertiesVar extends DefaultingVar {
+       private final SystemPropertyPropertySource source = new 
SystemPropertyPropertySource();
+
 
        /** The name of this variable. */
        public static final String NAME = "S";
@@ -50,6 +53,12 @@ public class SystemPropertiesVar extends MapVar {
         * Constructor.
         */
        public SystemPropertiesVar() {
-               super(NAME, System.getProperties());
+               super(NAME);
+       }
+
+       @Override /* Overridden from Var */
+       public String resolve(VarResolverSession session, String key) {
+               var v = source.get(key);
+               return v.isPresent() ? v.value().orElse(null) : null;
        }
 }
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
index cbb45173dc..ed2b148b14 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
@@ -51,11 +51,11 @@ public class IoUtils {
        @SuppressWarnings({
                "java:S5164" // Cleanup method provided: cleanupThreadLocals()
        })
-       private static final ThreadLocal<byte[]> BYTE_BUFFER_CACHE = 
(Boolean.getBoolean("juneau.disableIoBufferReuse") ? null : new 
ThreadLocal<>());
+       private static final ThreadLocal<byte[]> BYTE_BUFFER_CACHE = 
(env("juneau.disableIoBufferReuse", false) ? null : new ThreadLocal<>());
        @SuppressWarnings({
                "java:S5164" // Cleanup method provided: cleanupThreadLocals()
        })
-       private static final ThreadLocal<char[]> CHAR_BUFFER_CACHE = 
(Boolean.getBoolean("juneau.disableIoBufferReuse") ? null : new 
ThreadLocal<>());
+       private static final ThreadLocal<char[]> CHAR_BUFFER_CACHE = 
(env("juneau.disableIoBufferReuse", false) ? null : new ThreadLocal<>());
        static final AtomicInteger BYTE_BUFFER_CACHE_HITS = new AtomicInteger();
 
        static final AtomicInteger BYTE_BUFFER_CACHE_MISSES = new 
AtomicInteger();
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/SystemUtils.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/SystemUtils.java
index d2923fa2a9..e3f37e60a5 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/SystemUtils.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/SystemUtils.java
@@ -21,6 +21,7 @@ import java.util.concurrent.*;
 import java.util.function.*;
 
 import org.apache.juneau.commons.logging.Logger;
+import org.apache.juneau.commons.settings.*;
 
 /**
  * System utilities.
@@ -40,7 +41,7 @@ public class SystemUtils {
                Runtime.getRuntime().addShutdownHook(new Thread() {
                        @Override
                        public void run() {
-                               if (! 
Boolean.getBoolean("juneau.shutdown.quiet")) // HTT - shutdown hook; true 
branch tested, false requires JVM shutdown with system property set
+                               if (! 
Settings.get().get("juneau.shutdown.quiet").asBoolean().orElse(false)) // HTT - 
shutdown hook; true branch tested, false requires JVM shutdown with system 
property set
                                        SHUTDOWN_MESSAGES.forEach(x -> 
LOG.info(x.get()));
                        }
                });
diff --git 
a/juneau-core/juneau-commons/src/main/resources/META-INF/services/org.apache.juneau.commons.settings.PropertySourceProvider
 
b/juneau-core/juneau-commons/src/main/resources/META-INF/services/org.apache.juneau.commons.settings.PropertySourceProvider
new file mode 100644
index 0000000000..f503f5f57e
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/resources/META-INF/services/org.apache.juneau.commons.settings.PropertySourceProvider
@@ -0,0 +1,20 @@
+# 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.
+
+org.apache.juneau.commons.settings.ManifestFilePropertySourceProvider
+org.apache.juneau.commons.settings.DotenvPropertySourceProvider
+org.apache.juneau.commons.settings.SystemEnvPropertySourceProvider
+org.apache.juneau.commons.settings.SystemPropertyPropertySourceProvider
+org.apache.juneau.commons.settings.ArgsPropertySourceProvider
diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
index 0ae0394a20..e96232c70f 100644
--- 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
@@ -437,7 +437,7 @@ public class Config extends Context implements 
ConfigEventListener {
        }
 
        // Use set(T)/reset() for testing.
-       static final Memoizer<Boolean> DISABLE_AUTO_SYSTEM_PROPS = memoizer(() 
-> Boolean.getBoolean("juneau.disableAutoSystemProps"));
+       static final Memoizer<Boolean> DISABLE_AUTO_SYSTEM_PROPS = memoizer(() 
-> env("juneau.disableAutoSystemProps", false));
 
        // Use set(T)/reset() for testing.
        static final Memoizer<Config> SYSTEM_DEFAULT = 
memoizer(Config::findSystemDefault);
@@ -487,7 +487,7 @@ public class Config extends Context implements 
ConfigEventListener {
        public static synchronized List<String> 
getCandidateSystemDefaultConfigNames() {
                var l = listOf(String.class);
 
-               var s = System.getProperty("juneau.configFile");
+               var s = env("juneau.configFile").orElse(null);
                if (nn(s)) {
                        l.add(s);
                        return l;
diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigPropertySource.java
 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigPropertySource.java
new file mode 100644
index 0000000000..cbc3a032c6
--- /dev/null
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigPropertySource.java
@@ -0,0 +1,50 @@
+/*
+ * 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.config;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import org.apache.juneau.commons.settings.*;
+
+/**
+ * {@link PropertySource} adapter for {@link Config}.
+ */
+public class ConfigPropertySource implements PropertySource {
+
+       private final Config config;
+
+       /**
+        * Constructor.
+        *
+        * @param config The wrapped config.
+        */
+       public ConfigPropertySource(Config config) {
+               this.config = config;
+       }
+
+       @Override
+       public PropertyLookupResult get(String name) {
+               if (config == null)
+                       return PropertyLookupResult.missing();
+               try {
+                       var value = config.getString(name);
+                       return value == null ? PropertyLookupResult.missing() : 
PropertyLookupResult.present(opt(value));
+               } catch (@SuppressWarnings("unused") Exception unused) {
+                       return PropertyLookupResult.missing();
+               }
+       }
+}
diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/mod/XorEncodeMod.java
 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/mod/XorEncodeMod.java
index c66c15751f..e877445035 100644
--- 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/mod/XorEncodeMod.java
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/mod/XorEncodeMod.java
@@ -18,6 +18,7 @@ package org.apache.juneau.config.mod;
 
 import static org.apache.juneau.commons.utils.IoUtils.*;
 import static org.apache.juneau.commons.utils.StringUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
 
 /**
  * Simply XOR+Base64 encoder for obscuring passwords and other sensitive data 
in INI config files.
@@ -34,7 +35,7 @@ public class XorEncodeMod extends Mod {
        /** Reusable XOR-ConfigEncoder instance. */
        public static final XorEncodeMod INSTANCE = new XorEncodeMod();
 
-       private static final String KEY = 
System.getProperty("org.apache.juneau.config.XorEncoder.key", 
"nuy7og796Vh6G9O6bG230SHK0cc8QYkH");    // The super-duper-secret key
+       private static final String KEY = 
env("org.apache.juneau.config.XorEncoder.key", 
"nuy7og796Vh6G9O6bG230SHK0cc8QYkH");   // The super-duper-secret key
 
        /**
         * Constructor.
diff --git 
a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
 
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
index af6c1db1f8..787af6bbe3 100755
--- 
a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
+++ 
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
@@ -123,7 +123,7 @@ public class Microservice implements ConfigEventListener {
                Scanner consoleReader;
                PrintWriter consoleWriter;
                MicroserviceListener listener;
-               File workingDir = System.getProperty("juneau.workingDir") == 
null ? null : new File(System.getProperty("juneau.workingDir"));
+               File workingDir = 
env("juneau.workingDir").map(File::new).orElse(null);
 
                /**
                 * Constructor.
diff --git 
a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroservice.java
 
b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroservice.java
index d4d5d005ce..92675f4862 100644
--- 
a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroservice.java
+++ 
b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroservice.java
@@ -566,7 +566,7 @@ public class JettyMicroservice extends Microservice {
                var ports = firstNonNull(builder.ports, 
cf.get("Jetty/port").as(int[].class).orElseGet(() -> 
mf.get("Jetty-Port").map(JettyMicroservice::parseIntArray).orElseGet(() -> 
ints(8000))));
                var availablePort = findOpenPort(ports);
 
-               if (System.getProperty("availablePort") == null)
+               if (env("availablePort").isEmpty())
                        System.setProperty("availablePort", 
String.valueOf(availablePort));
 
                var jettyXml = builder.jettyXml;
@@ -624,7 +624,7 @@ public class JettyMicroservice extends Microservice {
 
                builder.servletAttributes.forEach(this::addServletAttribute);
 
-               if (System.getProperty("juneau.serverPort") == null)
+               if (env("juneau.serverPort").isEmpty())
                        System.setProperty("juneau.serverPort", 
String.valueOf(availablePort));
 
                return server.get();
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index ee0c09b920..8519ff21e1 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -7044,6 +7044,9 @@ public class RestClient extends MarshallingContextable 
implements HttpClient, Cl
         * @deprecated Use {@link HttpClientBuilder}.
         */
        @Deprecated(since = "10.0", forRemoval = true)
+       @SuppressWarnings({
+               "java:S1133" // Deprecated override required by HttpClient 
interface
+       })
        @Override /* Overridden from HttpClient */
        public ClientConnectionManager getConnectionManager() { return 
httpClient.getConnectionManager(); }
 
@@ -7063,6 +7066,9 @@ public class RestClient extends MarshallingContextable 
implements HttpClient, Cl
         * @deprecated Use {@link RequestConfig}.
         */
        @Deprecated(since = "10.0", forRemoval = true)
+       @SuppressWarnings({
+               "java:S1133" // Deprecated override required by HttpClient 
interface
+       })
        @Override /* Overridden from HttpClient */
        public HttpParams getParams() { return httpClient.getParams(); }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfo_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfo_Test.java
index bd273f7365..1f3ea9c055 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfo_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfo_Test.java
@@ -30,6 +30,7 @@ import java.util.stream.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.commons.bean.Name;
+import org.apache.juneau.commons.settings.*;
 import org.junit.jupiter.api.*;
 
 @SuppressWarnings({
@@ -46,11 +47,11 @@ class ParameterInfo_Test extends TestBase {
 
        @BeforeAll
        static void beforeAll() {
-               // Save original system property value
-               originalDisableParamNameDetection = 
System.getProperty("juneau.disableParamNameDetection");
+               // Save original effective value.
+               originalDisableParamNameDetection = 
Settings.get().get("juneau.disableParamNameDetection").orElse(null);
 
                // Set to true to ensure consistent behavior regardless of JVM 
compiler settings
-               System.setProperty("juneau.disableParamNameDetection", "true");
+               Settings.get().setLocal("juneau.disableParamNameDetection", 
"true");
                ParameterInfo.reset();
        }
 
@@ -58,10 +59,11 @@ class ParameterInfo_Test extends TestBase {
        static void afterAll() {
                // Restore original system property value
                if (originalDisableParamNameDetection == null)
-                       
System.clearProperty("juneau.disableParamNameDetection");
+                       
Settings.get().unsetLocal("juneau.disableParamNameDetection");
                else
-                       System.setProperty("juneau.disableParamNameDetection", 
originalDisableParamNameDetection);
+                       
Settings.get().setLocal("juneau.disableParamNameDetection", 
originalDisableParamNameDetection);
                ParameterInfo.reset();
+               Settings.get().clearLocal();
        }
 
        @Documented
@@ -649,9 +651,9 @@ class ParameterInfo_Test extends TestBase {
 
                // Test line 632: bytecode parameter name fallback
                // Temporarily disable the flag to test the bytecode name 
fallback
-               String originalValue = 
System.getProperty("juneau.disableParamNameDetection");
+               String originalValue = 
Settings.get().get("juneau.disableParamNameDetection").orElse(null);
                try {
-                       System.setProperty("juneau.disableParamNameDetection", 
"false");
+                       
Settings.get().setLocal("juneau.disableParamNameDetection", "false");
                        ParameterInfo.reset();
 
                        // Get a fresh ParameterInfo instance after resetting 
(don't use cached static field)
@@ -680,9 +682,9 @@ class ParameterInfo_Test extends TestBase {
                } finally {
                        // Restore original value
                        if (originalValue == null)
-                               
System.clearProperty("juneau.disableParamNameDetection");
+                               
Settings.get().unsetLocal("juneau.disableParamNameDetection");
                        else
-                               
System.setProperty("juneau.disableParamNameDetection", originalValue);
+                               
Settings.get().setLocal("juneau.disableParamNameDetection", originalValue);
                        ParameterInfo.reset();
                }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/settings/PropertySources_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/PropertySources_Test.java
new file mode 100644
index 0000000000..c6a1d9553d
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/PropertySources_Test.java
@@ -0,0 +1,364 @@
+/*
+ * 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.junit.jupiter.api.Assertions.*;
+
+import java.io.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.jar.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.commons.runtime.*;
+import org.junit.jupiter.api.*;
+
+class PropertySources_Test extends TestBase {
+
+       
//====================================================================================================
+       // PropertyLookupResult
+       
//====================================================================================================
+
+       @Test
+       void a01_result_missing_isNotPresent() {
+               assertFalse(PropertyLookupResult.missing().isPresent());
+       }
+
+       @Test
+       void a02_result_missing_returnsSingleton() {
+               assertSame(PropertyLookupResult.missing(), 
PropertyLookupResult.missing());
+       }
+
+       @Test
+       void a03_result_present_withOptionalValue() {
+               var r = PropertyLookupResult.present(Optional.of("hello"));
+               assertTrue(r.isPresent());
+               assertEquals("hello", r.value().orElse(null));
+       }
+
+       @Test
+       void a04_result_present_withOptionalEmpty() {
+               var r = PropertyLookupResult.present(Optional.empty());
+               assertTrue(r.isPresent());
+               assertFalse(r.value().isPresent());
+       }
+
+       @Test
+       void a05_result_present_withStringValue() {
+               // Exercises the present(String) overload directly (not via 
present(Optional))
+               var r = PropertyLookupResult.present("world");
+               assertTrue(r.isPresent());
+               assertEquals("world", r.value().orElse(null));
+       }
+
+       @Test
+       void a06_result_present_withNullString() {
+               var r = PropertyLookupResult.present((String) null);
+               assertTrue(r.isPresent());
+               assertFalse(r.value().isPresent());
+       }
+
+       @Test
+       void a07_result_present_nullOptional_throws() {
+               assertThrows(NullPointerException.class, () -> 
PropertyLookupResult.present((Optional<String>) null));
+       }
+
+       
//====================================================================================================
+       // PropertySourceProvider — default order()
+       
//====================================================================================================
+
+       @Test
+       void b01_provider_defaultOrder() {
+               PropertySourceProvider provider = () -> null;
+               assertEquals(0, provider.order());
+       }
+
+       
//====================================================================================================
+       // ArgsPropertySource
+       
//====================================================================================================
+
+       @Test
+       void c01_args_nullSupplier_missing() {
+               var src = new ArgsPropertySource(() -> null);
+               assertFalse(src.get("anything").isPresent());
+       }
+
+       @Test
+       void c02_args_positionalIndex_found() {
+               var src = new ArgsPropertySource(() -> new Args("pos0 pos1"));
+               var r = src.get("0");
+               assertTrue(r.isPresent());
+               assertEquals("pos0", r.value().orElse(null));
+       }
+
+       @Test
+       void c03_args_positionalIndex_outOfBounds_missing() {
+               var src = new ArgsPropertySource(() -> new Args("pos0"));
+               assertFalse(src.get("5").isPresent());
+       }
+
+       @Test
+       void c04_args_namedOption_found() {
+               var src = new ArgsPropertySource(() -> new Args("-port 8080"));
+               var r = src.get("port");
+               assertTrue(r.isPresent());
+               assertEquals("8080", r.value().orElse(null));
+       }
+
+       @Test
+       void c05_args_namedOption_missing() {
+               var src = new ArgsPropertySource(() -> new Args("-port 8080"));
+               assertFalse(src.get("host").isPresent());
+       }
+
+       @Test
+       void c06_args_multiValueOption_joinedWithComma() {
+               var src = new ArgsPropertySource(() -> new Args("-tag a -tag b 
-tag c"));
+               var r = src.get("tag");
+               assertTrue(r.isPresent());
+               assertEquals("a,b,c", r.value().orElse(null));
+       }
+
+       @Test
+       void c07_createDefaultArgs_sunJavaCommand_withArgs() {
+               System.setProperty("sun.java.command", "com.example.Main --port 
9090");
+               try {
+                       var args = ArgsPropertySource.createDefaultArgs();
+                       assertNotNull(args);
+                       assertTrue(args.get("port").isPresent());
+               } finally {
+                       System.clearProperty("sun.java.command");
+               }
+       }
+
+       @Test
+       void c08_createDefaultArgs_sunJavaCommand_noArgs() {
+               // sun.java.command with no space → empty args string
+               System.setProperty("sun.java.command", "com.example.Main");
+               try {
+                       var args = ArgsPropertySource.createDefaultArgs();
+                       assertNotNull(args);
+               } finally {
+                       System.clearProperty("sun.java.command");
+               }
+       }
+
+       @Test
+       void c09_createDefaultArgs_juneauArgsFallback() {
+               System.clearProperty("sun.java.command");
+               System.setProperty("juneau.args", "--env prod");
+               try {
+                       var args = ArgsPropertySource.createDefaultArgs();
+                       assertNotNull(args);
+                       assertTrue(args.get("env").isPresent());
+               } finally {
+                       System.clearProperty("juneau.args");
+               }
+       }
+
+       @Test
+       void c10_createDefaultArgs_bothAbsent() {
+               System.clearProperty("sun.java.command");
+               System.clearProperty("juneau.args");
+               assertNotNull(ArgsPropertySource.createDefaultArgs());
+       }
+
+       
//====================================================================================================
+       // DotenvPropertySource
+       
//====================================================================================================
+
+       @Test
+       void d01_dotenv_missingFile_returnsEmpty() {
+               var src = new 
DotenvPropertySource(Paths.get("nonexistent.env.file.xyz"));
+               assertFalse(src.get("ANY_KEY").isPresent());
+       }
+
+       @Test
+       void d02_dotenv_presentKey_found() throws IOException {
+               var tmp = writeTempDotenv("MY_KEY=hello\n");
+               try {
+                       var src = new DotenvPropertySource(tmp);
+                       var r = src.get("MY_KEY");
+                       assertTrue(r.isPresent());
+                       assertEquals("hello", r.value().orElse(null));
+               } finally {
+                       Files.deleteIfExists(tmp);
+               }
+       }
+
+       @Test
+       void d03_dotenv_missingKey_missing() throws IOException {
+               var tmp = writeTempDotenv("OTHER_KEY=value\n");
+               try {
+                       var src = new DotenvPropertySource(tmp);
+                       assertFalse(src.get("MY_KEY").isPresent());
+               } finally {
+                       Files.deleteIfExists(tmp);
+               }
+       }
+
+       @Test
+       void d04_dotenv_doubleQuotedValue() throws IOException {
+               var tmp = writeTempDotenv("KEY=\"quoted value\"\n");
+               try {
+                       var src = new DotenvPropertySource(tmp);
+                       assertEquals("quoted value", 
src.get("KEY").value().orElse(null));
+               } finally {
+                       Files.deleteIfExists(tmp);
+               }
+       }
+
+       @Test
+       void d05_dotenv_singleQuotedValue() throws IOException {
+               var tmp = writeTempDotenv("KEY='single'\n");
+               try {
+                       var src = new DotenvPropertySource(tmp);
+                       assertEquals("single", 
src.get("KEY").value().orElse(null));
+               } finally {
+                       Files.deleteIfExists(tmp);
+               }
+       }
+
+       @Test
+       void d06_dotenv_commentsAndBlankLinesIgnored() throws IOException {
+               var tmp = writeTempDotenv("# comment\n\nKEY=value\n");
+               try {
+                       var src = new DotenvPropertySource(tmp);
+                       assertEquals("value", 
src.get("KEY").value().orElse(null));
+                       assertFalse(src.get("# comment").isPresent());
+               } finally {
+                       Files.deleteIfExists(tmp);
+               }
+       }
+
+       @Test
+       void d07_dotenv_lineWithNoEquals_ignored() throws IOException {
+               var tmp = writeTempDotenv("INVALID_LINE\nGOOD=ok\n");
+               try {
+                       var src = new DotenvPropertySource(tmp);
+                       assertFalse(src.get("INVALID_LINE").isPresent());
+                       assertEquals("ok", 
src.get("GOOD").value().orElse(null));
+               } finally {
+                       Files.deleteIfExists(tmp);
+               }
+       }
+
+       @Test
+       void d08_dotenv_resolvePath_fromSystemProperty() throws IOException {
+               var tmp = writeTempDotenv("DOTENV_SYS=found\n");
+               System.setProperty("juneau.dotenv.path", tmp.toString());
+               try {
+                       var src = new DotenvPropertySource();
+                       assertEquals("found", 
src.get("DOTENV_SYS").value().orElse(null));
+               } finally {
+                       System.clearProperty("juneau.dotenv.path");
+                       Files.deleteIfExists(tmp);
+               }
+       }
+
+       @Test
+       void d09_dotenv_defaultConstructor_doesNotThrow() {
+               // Default path ".env" may not exist; just verify no exception.
+               System.clearProperty("juneau.dotenv.path");
+               var src = new DotenvPropertySource();
+               assertNotNull(src.get("ANYTHING"));
+       }
+
+       @Test
+       void d10_dotenv_emptySystemProperty_fallsToDefault() {
+               // Empty string value → same as absent → falls through to 
default ".env" path.
+               System.setProperty("juneau.dotenv.path", "");
+               try {
+                       var src = new DotenvPropertySource();
+                       assertNotNull(src.get("ANY_KEY")); // no exception, 
missing is fine
+               } finally {
+                       System.clearProperty("juneau.dotenv.path");
+               }
+       }
+
+       @Test
+       void d11_dotenv_unmatchedQuotes_preservedAsIs() throws IOException {
+               // Value starts with `"` but doesn't end with it → kept as-is 
(no strip).
+               var tmp = writeTempDotenv("KEY=\"unmatched\n");
+               try {
+                       var src = new DotenvPropertySource(tmp);
+                       var r = src.get("KEY");
+                       assertTrue(r.isPresent());
+                       // The value ends with a newline-stripped but unmatched 
quote is kept verbatim.
+                       assertTrue(r.value().orElse("").startsWith("\""));
+               } finally {
+                       Files.deleteIfExists(tmp);
+               }
+       }
+
+       @Test
+       void d12_dotenv_singleCharValue_quotedBothEnds() throws IOException {
+               // Edge: value is exactly one char between single quotes, e.g. 
KEY='' → empty string.
+               var tmp = writeTempDotenv("KEY=''\n");
+               try {
+                       var src = new DotenvPropertySource(tmp);
+                       assertEquals("", src.get("KEY").value().orElse("x"));
+               } finally {
+                       Files.deleteIfExists(tmp);
+               }
+       }
+
+       private static Path writeTempDotenv(String content) throws IOException {
+               var tmp = Files.createTempFile("juneau-test-", ".env");
+               Files.writeString(tmp, content);
+               return tmp;
+       }
+
+       
//====================================================================================================
+       // ManifestFilePropertySource
+       
//====================================================================================================
+
+       @Test
+       void e01_manifest_nullSupplier_missing() {
+               var src = new ManifestFilePropertySource(() -> null);
+               assertFalse(src.get("Main-Class").isPresent());
+       }
+
+       @Test
+       void e02_manifest_presentKey_found() {
+               var mf = buildManifest("Main-Class", "com.example.Main");
+               var src = new ManifestFilePropertySource(() -> mf);
+               var r = src.get("Main-Class");
+               assertTrue(r.isPresent());
+               assertEquals("com.example.Main", r.value().orElse(null));
+       }
+
+       @Test
+       void e03_manifest_missingKey_missing() {
+               var mf = buildManifest("Main-Class", "com.example.Main");
+               var src = new ManifestFilePropertySource(() -> mf);
+               assertFalse(src.get("Implementation-Version").isPresent());
+       }
+
+       @Test
+       void e04_manifest_createDefault_doesNotThrow() {
+               var src = ManifestFilePropertySource.createDefault();
+               assertNotNull(src);
+               assertNotNull(src.get("Main-Class"));
+       }
+
+       private static ManifestFile buildManifest(String key, String value) {
+               var manifest = new Manifest();
+               manifest.getMainAttributes().putValue(key, value);
+               return new ManifestFile(manifest);
+       }
+}
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 8c11e49f62..a96e7089b6 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
@@ -667,12 +667,15 @@ class Settings_Test extends TestBase {
        }
 
        
//====================================================================================================
-       // addSource(FunctionalSource) - Functional interface usage
+       // addSource(FunctionalPropertySource) - Functional interface usage
        
//====================================================================================================
        @Test
        void p01_addSource_functionalSource() {
-               // Test the addSource(FunctionalSource) overload
-               var source = (FunctionalSource) name -> 
opt(System.getProperty(name));
+               // Test the addSource(FunctionalPropertySource) overload
+               var source = (FunctionalPropertySource) name -> {
+                       var value = System.getProperty(name);
+                       return value == null ? PropertyLookupResult.missing() : 
PropertyLookupResult.present(opt(value));
+               };
                System.setProperty(TEST_PROP, "system-value");
                var settings = Settings.create()
                        .addSource(source)
@@ -685,8 +688,8 @@ class Settings_Test extends TestBase {
 
        @Test
        void p02_addSource_functionalSource_factoryMethod() {
-               // Test addSource with FunctionalSource.of()
-               var source = FunctionalSource.of(System::getProperty);
+               // Test addSource with FunctionalPropertySource.of()
+               var source = FunctionalPropertySource.of(System::getProperty);
                System.setProperty(TEST_PROP, "system-value");
                var settings = Settings.create()
                        .addSource(source)
@@ -698,12 +701,12 @@ class Settings_Test extends TestBase {
        }
 
        
//====================================================================================================
-       // FunctionalStore
+       // FunctionalPropertyStore
        
//====================================================================================================
        @Test
-       void q01_writeableFunctionalSource_basic() {
+       void q01_writeableFunctionalPropertySource_basic() {
                // Create a writable functional source using system properties
-               var source = FunctionalStore.of(
+               var source = FunctionalPropertyStore.of(
                        System::getProperty,
                        System::setProperty,
                        System::clearProperty,
@@ -715,21 +718,21 @@ class Settings_Test extends TestBase {
                var result = source.get(TEST_PROP);
                assertNotNull(result);
                assertTrue(result.isPresent());
-               assertEquals("test-value", result.get());
+               assertEquals("test-value", result.value().orElse(null));
 
                // Test unset
                source.unset(TEST_PROP);
                result = source.get(TEST_PROP);
-               assertNull(result); // Should return null when property doesn't 
exist
+               assertFalse(result.isPresent()); // Should report missing when 
property doesn't exist
 
                // Clean up
                System.clearProperty(TEST_PROP);
        }
 
        @Test
-       void q02_writeableFunctionalSource_withSettings() {
+       void q02_writeableFunctionalPropertySource_withSettings() {
                // Create a writable functional source and add it to Settings
-               var source = FunctionalStore.of(
+               var source = FunctionalPropertyStore.of(
                        System::getProperty,
                        System::setProperty,
                        System::clearProperty,
@@ -756,10 +759,10 @@ class Settings_Test extends TestBase {
        }
 
        @Test
-       void q03_writeableFunctionalSource_clear() {
+       void q03_writeableFunctionalPropertySource_clear() {
                // Test clear() functionality with a custom clearer
                var map = new java.util.HashMap<String, String>();
-               var source = FunctionalStore.of(
+               var source = FunctionalPropertyStore.of(
                        map::get,
                        map::put,
                        map::remove,
@@ -773,22 +776,22 @@ class Settings_Test extends TestBase {
                var result1 = source.get(TEST_PROP);
                assertNotNull(result1);
                assertTrue(result1.isPresent());
-               assertEquals("test-value", result1.get());
+               assertEquals("test-value", result1.value().orElse(null));
 
                var result2 = source.get(TEST_PROP_2);
                assertNotNull(result2);
                assertTrue(result2.isPresent());
-               assertEquals("test-value-2", result2.get());
+               assertEquals("test-value-2", result2.value().orElse(null));
 
                // Clear all values
                source.clear();
 
                // Verify values are cleared
                result1 = source.get(TEST_PROP);
-               assertNull(result1);
+               assertFalse(result1.isPresent());
 
                result2 = source.get(TEST_PROP_2);
-               assertNull(result2);
+               assertFalse(result2.isPresent());
        }
 
        
//====================================================================================================
@@ -867,9 +870,9 @@ class Settings_Test extends TestBase {
                store.unset(TEST_PROP);
 
                // Verify the map is still null (not initialized)
-               // get() should return null since the map doesn't exist
+               // get() should report missing since the map doesn't exist
                var result = store.get(TEST_PROP);
-               assertNull(result);
+               assertFalse(result.isPresent());
        }
 
        
//====================================================================================================
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/svl/vars/PropertyVars_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/svl/vars/PropertyVars_Test.java
new file mode 100644
index 0000000000..9bee2df31a
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/svl/vars/PropertyVars_Test.java
@@ -0,0 +1,145 @@
+/*
+ * 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.svl.vars;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.jar.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.commons.runtime.*;
+import org.apache.juneau.commons.settings.*;
+import org.apache.juneau.commons.svl.*;
+import org.junit.jupiter.api.*;
+
+class PropertyVars_Test extends TestBase {
+
+       
//====================================================================================================
+       // ArgsVar
+       
//====================================================================================================
+
+       @Test
+       void a01_argsVar_init_resolvesPositional() {
+               ArgsVar.init(new Args("hello world"));
+               var vr = VarResolver.create().vars(ArgsVar.class).build();
+               assertEquals("hello", vr.resolve("$A{0}"));
+               assertEquals("world", vr.resolve("$A{1}"));
+       }
+
+       @Test
+       void a02_argsVar_init_resolvesNamed() {
+               ArgsVar.init(new Args("-port 9999"));
+               var vr = VarResolver.create().vars(ArgsVar.class).build();
+               assertEquals("9999", vr.resolve("$A{port}"));
+       }
+
+       @Test
+       void a03_argsVar_init_missingKey_returnsDefault() {
+               ArgsVar.init(new Args("-port 9999"));
+               var vr = VarResolver.create().vars(ArgsVar.class).build();
+               assertEquals("defaultVal", vr.resolve("$A{host,defaultVal}"));
+       }
+
+       @Test
+       void a04_argsVar_create_withSupplier() {
+               var src = ArgsVar.create(() -> new Args("-env staging"));
+               var vr = VarResolver.create().vars(src).build();
+               assertEquals("staging", vr.resolve("$A{env}"));
+       }
+
+       @Test
+       void a05_argsVar_create_missingKey_returnsDefault() {
+               var src = ArgsVar.create(() -> new Args("-env staging"));
+               var vr = VarResolver.create().vars(src).build();
+               assertEquals("prod", vr.resolve("$A{region,prod}"));
+       }
+
+       
//====================================================================================================
+       // ManifestFileVar
+       
//====================================================================================================
+
+       @Test
+       void b01_manifestFileVar_init_resolvesKey() {
+               var manifest = new Manifest();
+               manifest.getMainAttributes().putValue("My-Attr", 
"from-manifest");
+               ManifestFileVar.init(new ManifestFile(manifest));
+               var vr = 
VarResolver.create().vars(ManifestFileVar.class).build();
+               assertEquals("from-manifest", vr.resolve("$MF{My-Attr}"));
+       }
+
+       @Test
+       void b02_manifestFileVar_init_missingKey_returnsEmptyString() {
+               // ManifestFileVar.resolve() returns "" (not null) for missing 
keys,
+               // so DefaultingVar does not apply the default — empty string 
is returned.
+               var manifest = new Manifest();
+               manifest.getMainAttributes().putValue("My-Attr", "x");
+               ManifestFileVar.init(new ManifestFile(manifest));
+               var vr = 
VarResolver.create().vars(ManifestFileVar.class).build();
+               assertEquals("", vr.resolve("$MF{Missing-Attr}"));
+               assertEquals("", 
vr.resolve("$MF{Missing-Attr,ignored-default}"));
+       }
+
+       @Test
+       void b03_manifestFileVar_create_withSupplier() {
+               var manifest = new Manifest();
+               manifest.getMainAttributes().putValue("Version", "1.2.3");
+               var mf = new ManifestFile(manifest);
+               var src = ManifestFileVar.create(() -> mf);
+               var vr = VarResolver.create().vars(src).build();
+               assertEquals("1.2.3", vr.resolve("$MF{Version}"));
+       }
+
+       @Test
+       void b04_manifestFileVar_nullManifest_returnsEmptyString() {
+               var src = ManifestFileVar.create(() -> null);
+               var vr = VarResolver.create().vars(src).build();
+               assertEquals("", vr.resolve("$MF{Main-Class}"));
+       }
+
+       
//====================================================================================================
+       // PropertyVar
+       
//====================================================================================================
+
+       @Test
+       void c01_propertyVar_resolvesFromSystemProperty() {
+               System.setProperty("PropertyVars_Test.c01", "sysval");
+               try {
+                       var vr = 
VarResolver.create().vars(PropertyVar.class).build();
+                       assertEquals("sysval", 
vr.resolve("$P{PropertyVars_Test.c01}"));
+               } finally {
+                       System.clearProperty("PropertyVars_Test.c01");
+               }
+       }
+
+       @Test
+       void c02_propertyVar_missingKey_returnsDefault() {
+               System.clearProperty("PropertyVars_Test.c02.missing");
+               var vr = VarResolver.create().vars(PropertyVar.class).build();
+               assertEquals("defval", 
vr.resolve("$P{PropertyVars_Test.c02.missing,defval}"));
+       }
+
+       @Test
+       void c03_propertyVar_fromSettings() {
+               Settings.get().setGlobal("PropertyVars_Test.c03", 
"overrideVal");
+               try {
+                       var vr = 
VarResolver.create().vars(PropertyVar.class).build();
+                       assertEquals("overrideVal", 
vr.resolve("$P{PropertyVars_Test.c03}"));
+               } finally {
+                       Settings.get().unsetGlobal("PropertyVars_Test.c03");
+               }
+       }
+}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/config/ConfigPropertySource_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigPropertySource_Test.java
new file mode 100644
index 0000000000..0c2a968fde
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigPropertySource_Test.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.config;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.config.store.*;
+import org.junit.jupiter.api.*;
+
+class ConfigPropertySource_Test extends TestBase {
+
+       private Config build(String...lines) {
+               MemoryStore.DEFAULT.update("Test.cfg", lines);
+               return 
Config.create().store(MemoryStore.DEFAULT).name("Test.cfg").build();
+       }
+
+       @Test
+       void a01_nullConfig_missing() {
+               var src = new ConfigPropertySource(null);
+               assertFalse(src.get("any.key").isPresent());
+       }
+
+       @Test
+       void a02_presentKey_found() {
+               var cfg = build("key = value");
+               var src = new ConfigPropertySource(cfg);
+               var r = src.get("key");
+               assertTrue(r.isPresent());
+               assertEquals("value", r.value().orElse(null));
+       }
+
+       @Test
+       void a03_missingKey_missing() {
+               var cfg = build("other = value");
+               var src = new ConfigPropertySource(cfg);
+               assertFalse(src.get("key").isPresent());
+       }
+
+       @Test
+       void a04_keyInSection_found() {
+               var cfg = build("[section]", "key = hello");
+               var src = new ConfigPropertySource(cfg);
+               var r = src.get("section/key");
+               assertTrue(r.isPresent());
+               assertEquals("hello", r.value().orElse(null));
+       }
+
+       @Test
+       void a05_exception_treatedAsMissing() {
+               // Passing a null config exercises the null guard, not the 
catch block.
+               // We exercise the catch by using a key path that 
Config.getString may reject.
+               var cfg = build();
+               var src = new ConfigPropertySource(cfg);
+               // An empty string key is unusual; whatever happens, no 
exception should propagate.
+               assertNotNull(src.get(""));
+       }
+}

Reply via email to