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 d4421e524a feat(config): add YAML format strategy and expand 
NG/microservice test quality fixes
d4421e524a is described below

commit d4421e524a0fca1f317f0307bf7983a63c790afe
Author: James Bognar <[email protected]>
AuthorDate: Fri May 15 12:52:42 2026 -0400

    feat(config): add YAML format strategy and expand NG/microservice test 
quality fixes
---
 .../main/java/org/apache/juneau/config/Config.java |  53 +++-
 .../apache/juneau/config/format/ConfigFormat.java  |  52 ++++
 .../juneau/config/format/IniConfigFormat.java      |  50 +++
 .../juneau/config/format/YamlConfigFormat.java     | 209 +++++++++++++
 .../apache/juneau/config/internal/ConfigMap.java   |  47 ++-
 .../apache/juneau/config/store/ConfigStore.java    |  21 +-
 .../org/apache/juneau/config/store/FileStore.java  |   2 +-
 .../apache/juneau/microservice/Microservice.java   |  37 ++-
 .../apache/juneau/config/ConfigBuilder_Test.java   |  26 ++
 .../org/apache/juneau/config/ConfigMap_Test.java   |  19 +-
 .../juneau/config/ConfigYamlFormat_Test.java       | 341 +++++++++++++++++++++
 .../juneau/ng/http/HttpFactoryFacades_Test.java    |   1 -
 .../ng/rest/NgRemoteInterfaceTransport_Test.java   |   4 +-
 13 files changed, 835 insertions(+), 27 deletions(-)

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 e96232c70f..fc6a132c45 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
@@ -34,6 +34,7 @@ import org.apache.juneau.commons.collections.*;
 import org.apache.juneau.commons.collections.FluentMap;
 import org.apache.juneau.commons.function.*;
 import org.apache.juneau.config.event.*;
+import org.apache.juneau.config.format.*;
 import org.apache.juneau.config.internal.*;
 import org.apache.juneau.config.mod.*;
 import org.apache.juneau.config.store.*;
@@ -72,6 +73,7 @@ public class Config extends Context implements 
ConfigEventListener {
        private static final String PROP_binaryLineLength = "binaryLineLength";
        private static final String PROP_mods = "mods";
        private static final String PROP_multiLineValuesOnSeparateLines = 
"multiLineValuesOnSeparateLines";
+       private static final String PROP_format = "format";
        private static final String PROP_name = "name";
        private static final String PROP_parser = "parser";
        private static final String PROP_readOnly = "readOnly";
@@ -92,6 +94,7 @@ public class Config extends Context implements 
ConfigEventListener {
                private Map<Character,Mod> mods;
                private ReaderParser parser;
                private String name;
+               private ConfigFormat format;
                private VarResolver varResolver;
                private WriterSerializer serializer;
 
@@ -109,6 +112,7 @@ public class Config extends Context implements 
ConfigEventListener {
                        readOnly = env("Config.readOnly", false);
                        serializer = Json5Serializer.DEFAULT;
                        store = FileStore.DEFAULT;
+                       format = null;
                        varResolver = VarResolver.DEFAULT;
                }
 
@@ -124,6 +128,7 @@ public class Config extends Context implements 
ConfigEventListener {
                        mods = copyOf(copyFrom.mods);
                        multiLineValuesOnSeparateLines = 
copyFrom.multiLineValuesOnSeparateLines;
                        name = copyFrom.name;
+                       format = copyFrom.format;
                        parser = copyFrom.parser;
                        readOnly = copyFrom.readOnly;
                        serializer = copyFrom.serializer;
@@ -143,6 +148,7 @@ public class Config extends Context implements 
ConfigEventListener {
                        mods = copyOf(copyFrom.mods);
                        multiLineValuesOnSeparateLines = 
copyFrom.multiLineValuesOnSeparateLines;
                        name = copyFrom.name;
+                       format = copyFrom.format;
                        parser = copyFrom.parser;
                        readOnly = copyFrom.readOnly;
                        serializer = copyFrom.serializer;
@@ -340,6 +346,37 @@ public class Config extends Context implements 
ConfigEventListener {
                        return this;
                }
 
+               /**
+                * Configuration format.
+                *
+                * @param value The format. Can be <jk>null</jk> to auto-detect 
from the config name extension.
+                * @return This object.
+                */
+               public Builder format(ConfigFormat value) {
+                       format = value;
+                       return this;
+               }
+
+               /**
+                * Sets YAML format.
+                *
+                * @return This object.
+                */
+               public Builder yaml() {
+                       format = YamlConfigFormat.INSTANCE;
+                       return this;
+               }
+
+               /**
+                * Sets INI format.
+                *
+                * @return This object.
+                */
+               public Builder ini() {
+                       format = IniConfigFormat.INSTANCE;
+                       return this;
+               }
+
                /**
                 * POJO parser.
                 *
@@ -562,6 +599,12 @@ public class Config extends Context implements 
ConfigEventListener {
                var c = (Class<?>)t;
                return (c == String.class || c.isPrimitive() || 
c.isAssignableFrom(Number.class) || c == Boolean.class || c.isEnum());
        }
+       private static ConfigFormat detectFormat(String name) {
+               var n = emptyIfNull(name).toLowerCase(Locale.ROOT);
+               if (n.endsWith(".yaml") || n.endsWith(".yml"))
+                       return YamlConfigFormat.INSTANCE;
+               return IniConfigFormat.INSTANCE;
+       }
        private static String section(String section) {
                assertArgNotNull(ARG_section, section);
                if (isEmpty(section))
@@ -569,14 +612,14 @@ public class Config extends Context implements 
ConfigEventListener {
                return section;
        }
        private static String skey(String key) {
-               var i = key.indexOf('/');
+               var i = key.lastIndexOf('/');
                if (i == -1)
                        return key;
                return key.substring(i + 1);
        }
        private static String sname(String key) {
                assertArgNotNull(ARG_key, key);
-               var i = key.indexOf('/');
+               var i = key.lastIndexOf('/');
                if (i == -1)
                        return "";
                return key.substring(0, i);
@@ -591,6 +634,7 @@ public class Config extends Context implements 
ConfigEventListener {
        protected final Map<Character,Mod> mods;
        protected final ReaderParser parser;
        protected final String name;
+       protected final ConfigFormat format;
        protected final VarResolver varResolver;
        protected final VarResolverSession varSession;
        protected final WriterSerializer serializer;
@@ -612,12 +656,13 @@ public class Config extends Context implements 
ConfigEventListener {
                mods = u(copyOf(builder.mods));
                multiLineValuesOnSeparateLines = 
builder.multiLineValuesOnSeparateLines;
                name = builder.name;
+               format = builder.format == null ? detectFormat(name) : 
builder.format;
                parser = builder.parser;
                readOnly = builder.readOnly;
                serializer = builder.serializer;
                store = builder.store;
                varResolver = builder.varResolver;
-               configMap = store.getMap(name);
+               configMap = store.getMap(name, format);
                configMap.register(this);
                marshallingSession = 
parser.getMarshallingContext().getSession();
                varSession = 
varResolver.copy().vars(ConfigVar.class).bean(Config.class, 
this).build().createSession();
@@ -633,6 +678,7 @@ public class Config extends Context implements 
ConfigEventListener {
                mods = copyFrom.mods;
                multiLineValuesOnSeparateLines = 
copyFrom.multiLineValuesOnSeparateLines;
                name = copyFrom.name;
+               format = copyFrom.format;
                parser = copyFrom.parser;
                readOnly = copyFrom.readOnly;
                serializer = copyFrom.serializer;
@@ -1146,6 +1192,7 @@ public class Config extends Context implements 
ConfigEventListener {
                        .a(PROP_binaryLineLength, binaryLineLength)
                        .a(PROP_mods, mods)
                        .a(PROP_multiLineValuesOnSeparateLines, 
multiLineValuesOnSeparateLines)
+                       .a(PROP_format, format == null ? null : format.id())
                        .a(PROP_name, name)
                        .a(PROP_parser, parser)
                        .a(PROP_readOnly, readOnly)
diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/format/ConfigFormat.java
 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/format/ConfigFormat.java
new file mode 100644
index 0000000000..c49267a8bf
--- /dev/null
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/format/ConfigFormat.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.config.format;
+
+import java.io.*;
+
+import org.apache.juneau.config.internal.*;
+
+/**
+ * Format strategy for config map persistence.
+ */
+public interface ConfigFormat {
+
+       /**
+        * Format identifier.
+        *
+        * @return Format identifier.
+        */
+       String id();
+
+       /**
+        * Normalizes source format text into INI-style content consumed by 
{@link ConfigMap}.
+        *
+        * @param contents The source contents.
+        * @return INI-style contents.
+        * @throws IOException Thrown by underlying stream.
+        */
+       String toInternal(String contents) throws IOException;
+
+       /**
+        * Writes a map to this format.
+        *
+        * @param map The map to write.
+        * @return Serialized contents in this format.
+        * @throws IOException Thrown by underlying stream.
+        */
+       String fromInternal(ConfigMap map) throws IOException;
+}
diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/format/IniConfigFormat.java
 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/format/IniConfigFormat.java
new file mode 100644
index 0000000000..2109ca2d2d
--- /dev/null
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/format/IniConfigFormat.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.format;
+
+import java.io.*;
+
+import org.apache.juneau.config.internal.*;
+
+/**
+ * INI config format.
+ */
+public class IniConfigFormat implements ConfigFormat {
+
+       /** Singleton instance. */
+       public static final IniConfigFormat INSTANCE = new IniConfigFormat();
+
+       /**
+        * Constructor.
+        */
+       protected IniConfigFormat() {}
+
+       @Override /* ConfigFormat */
+       public String id() {
+               return "ini";
+       }
+
+       @Override /* ConfigFormat */
+       public String toInternal(String contents) {
+               return contents == null ? "" : contents;
+       }
+
+       @Override /* ConfigFormat */
+       public String fromInternal(ConfigMap map) throws IOException {
+               return map.asIniString();
+       }
+}
diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/format/YamlConfigFormat.java
 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/format/YamlConfigFormat.java
new file mode 100644
index 0000000000..65115fea9a
--- /dev/null
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/format/YamlConfigFormat.java
@@ -0,0 +1,209 @@
+/*
+ * 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.format;
+
+import static org.apache.juneau.commons.utils.StringUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.juneau.config.internal.*;
+
+/**
+ * YAML config format.
+ */
+public class YamlConfigFormat implements ConfigFormat {
+
+       /** Singleton instance. */
+       public static final YamlConfigFormat INSTANCE = new YamlConfigFormat();
+
+       /**
+        * Constructor.
+        */
+       protected YamlConfigFormat() {}
+
+       @Override /* ConfigFormat */
+       public String id() {
+               return "yaml";
+       }
+
+       @Override /* ConfigFormat */
+       public String toInternal(String contents) throws IOException {
+               if (contents == null)
+                       return "";
+
+               var lines = splitLines(contents);
+               var sb = new StringBuilder();
+               var stack = new ArrayDeque<Node>();
+               var preLines = new ArrayList<String>();
+               var lastSection = (String)null;
+
+               for (var i = 0; i < lines.size(); i++) {
+                       var line = lines.get(i);
+                       var trim = line.trim();
+                       if (trim.isEmpty() || trim.startsWith("#") || 
trim.equals("---")) {
+                               preLines.add(line);
+                               continue;
+                       }
+
+                       var indent = leadingSpaces(line);
+                       while (! stack.isEmpty() && indent <= 
stack.peekLast().indent)
+                               stack.removeLast();
+
+                       var colonIndex = trim.indexOf(':');
+                       if (colonIndex <= 0)
+                               throw new IOException("Invalid YAML config 
line: " + line);
+
+                       var key = trim.substring(0, colonIndex).trim();
+                       var rest = trim.substring(colonIndex + 1).trim();
+                       if ("_imports".equals(key))
+                               continue;
+
+                       if (rest.isEmpty()) {
+                               stack.addLast(new Node(key, indent));
+                               continue;
+                       }
+
+                       String comment = null;
+                       var commentIndex = rest.indexOf(" #");
+                       if (commentIndex != -1) {
+                               comment = rest.substring(commentIndex + 
2).trim();
+                               rest = rest.substring(0, commentIndex).trim();
+                       }
+
+                       var value = unquote(rest);
+                       var section = section(stack);
+                       if (! eq(section, lastSection)) {
+                               appendPreLines(sb, preLines);
+                               if (ne(section))
+                                       
sb.append('[').append(section).append(']').append('\n');
+                               lastSection = section;
+                       }
+                       appendPreLines(sb, preLines);
+                       sb.append(key).append(" = ").append(value);
+                       if (ne(comment))
+                               sb.append(" # ").append(comment);
+                       sb.append('\n');
+               }
+               appendPreLines(sb, preLines);
+
+               return sb.toString();
+       }
+
+       @Override /* ConfigFormat */
+       public String fromInternal(ConfigMap map) {
+               var sb = new StringBuilder();
+               List<String> opened = new ArrayList<>();
+               for (var section : map.getSections()) {
+                       var segments = splitPath(section);
+                       var common = commonPrefix(opened, segments);
+                       for (var i = common; i < segments.size(); i++) {
+                               indent(sb, 
i).append(segments.get(i)).append(':').append('\n');
+                       }
+                       opened = segments;
+                       var keys = map.getKeys(section);
+                       for (var key : keys) {
+                               var entry = map.getEntry(section, key);
+                               var value = entry == null ? "" : 
emptyIfNull(entry.getValue());
+                               indent(sb, 
segments.size()).append(key).append(": ").append(quoteIfNeeded(value));
+                               if (entry != null && ne(entry.getComment()))
+                                       sb.append(" # 
").append(entry.getComment());
+                               sb.append('\n');
+                       }
+               }
+               return sb.toString();
+       }
+
+       private int commonPrefix(List<String> a, List<String> b) {
+               var i = 0;
+               while (i < a.size() && i < b.size() && eq(a.get(i), b.get(i)))
+                       i++;
+               return i;
+       }
+
+       private List<String> splitLines(String contents) throws IOException {
+               try (var r = new BufferedReader(new StringReader(contents))) {
+                       var out = new ArrayList<String>();
+                       String line;
+                       while ((line = r.readLine()) != null)
+                               out.add(line);
+                       return out;
+               }
+       }
+
+       private List<String> splitPath(String section) {
+               if (isEmpty(section))
+                       return Collections.emptyList();
+               return Arrays.asList(section.split("/"));
+       }
+
+       private String section(Deque<Node> stack) {
+               if (stack.isEmpty())
+                       return "";
+               var sb = new StringBuilder();
+               for (var node : stack) {
+                       if (sb.length() > 0)
+                               sb.append('/');
+                       sb.append(node.name);
+               }
+               return sb.toString();
+       }
+
+       private StringBuilder indent(StringBuilder sb, int level) {
+               for (var i = 0; i < level; i++)
+                       sb.append("  ");
+               return sb;
+       }
+
+       private int leadingSpaces(String line) {
+               var i = 0;
+               while (i < line.length() && line.charAt(i) == ' ')
+                       i++;
+               return i;
+       }
+
+       private void appendPreLines(StringBuilder sb, List<String> preLines) {
+               for (var line : preLines)
+                       sb.append(line).append('\n');
+               preLines.clear();
+       }
+
+       private String unquote(String value) {
+               if (value.length() >= 2 && ((value.startsWith("\"") && 
value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))))
+                       return value.substring(1, value.length() - 1);
+               return value;
+       }
+
+       private String quoteIfNeeded(String value) {
+               if (value.isEmpty())
+                       return "\"\"";
+               if (value.indexOf(':') != -1 || value.indexOf('#') != -1 || 
value.startsWith(" ") || value.endsWith(" "))
+                       return '"' + value.replace("\"", "\\\"") + '"';
+               return value;
+       }
+
+       private static class Node {
+               final String name;
+               final int indent;
+
+               Node(String name, int indent) {
+                       this.name = name;
+                       this.indent = indent;
+               }
+       }
+}
diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
index d26229cee0..75608945fc 100644
--- 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
@@ -33,6 +33,7 @@ import org.apache.juneau.collections.*;
 import org.apache.juneau.commons.concurrent.*;
 import org.apache.juneau.commons.utils.*;
 import org.apache.juneau.config.event.*;
+import org.apache.juneau.config.format.*;
 import org.apache.juneau.config.store.*;
 
 /**
@@ -241,15 +242,18 @@ public class ConfigMap implements ConfigStoreListener {
                s = s.trim();
                if (s.isEmpty())
                        return false;
+               if (s.startsWith("/") || s.endsWith("/") || s.contains("//"))
+                       return false;
                for (var i = 0; i < s.length(); i++) {
                        var c = s.charAt(i);
-                       if (c == '/' || c == '\\' || c == '[' || c == ']')
+                       if (c == '\\' || c == '[' || c == ']')
                                return false;
                }
                return true;
        }
 
        private final ConfigStore store;         // The store that created this 
object.
+       private final ConfigFormat format;       // Persistence format strategy.
 
        private volatile String contents;        // The original contents of 
this object.
 
@@ -280,14 +284,24 @@ public class ConfigMap implements ConfigStoreListener {
         * @throws IOException Thrown by underlying stream.
         */
        public ConfigMap(ConfigStore store, String name) throws IOException {
+               this(store, name, IniConfigFormat.INSTANCE);
+       }
+
+       public ConfigMap(ConfigStore store, String name, ConfigFormat format) 
throws IOException {
                this.store = store;
                this.name = name;
+               this.format = format == null ? IniConfigFormat.INSTANCE : 
format;
                load(store.read(name));
        }
 
        ConfigMap(ConfigStore store, String name, String contents) throws 
IOException {
+               this(store, name, contents, IniConfigFormat.INSTANCE);
+       }
+
+       ConfigMap(ConfigStore store, String name, String contents, ConfigFormat 
format) throws IOException {
                this.store = store;
                this.name = name;
+               this.format = format == null ? IniConfigFormat.INSTANCE : 
format;
                load(contents);
        }
 
@@ -671,10 +685,7 @@ public class ConfigMap implements ConfigStoreListener {
         * @throws IOException Thrown by underlying stream.
         */
        public Writer writeTo(Writer w) throws IOException {
-               try (var x = lock.read()) {
-                       for (var cs : entries.values())
-                               cs.writeTo(w);
-               }
+               w.append(toString());
                return w;
        }
 
@@ -721,7 +732,7 @@ public class ConfigMap implements ConfigStoreListener {
        }
 
        // This method should only be called from behind a lock.
-       private String asString() {
+       public String asIniString() {
                try {
                        var sw = new StringWriter();
                        for (var cs : entries.values())
@@ -732,12 +743,21 @@ public class ConfigMap implements ConfigStoreListener {
                }
        }
 
+       // This method should only be called from behind a lock.
+       private String asString() {
+               try {
+                       return format.fromInternal(this);
+               } catch (IOException e) {
+                       throw toRex(e); // HTT - in-memory format conversion 
failures are unexpected in normal operation
+               }
+       }
+
        @SuppressWarnings({
                "java:S3776" // Cognitive complexity acceptable for config diff 
detection
        })
        private ConfigEvents findDiffs(String updatedContents) throws 
IOException {
                var changes2 = new ConfigEvents();
-               var newMap = new ConfigMap(store, name, updatedContents);
+               var newMap = new ConfigMap(store, name, updatedContents, 
format);
 
                // Imports added.
                for (var i : newMap.imports) { // HTT - requires active import 
listeners with actual ConfigMap imports registered
@@ -803,9 +823,18 @@ public class ConfigMap implements ConfigStoreListener {
                "java:S6541", // Single-threaded context; synchronization 
unnecessary
        })
        private ConfigMap load(String contents) throws IOException {
+               var internalContents = format.toInternal(contents);
+               return loadIni(internalContents, contents);
+       }
+
+       @SuppressWarnings({
+               "java:S3776", // Cognitive complexity acceptable for this 
specific logic
+               "java:S6541", // Single-threaded context; synchronization 
unnecessary
+       })
+       private ConfigMap loadIni(String contents, String originalContents) 
throws IOException {
                if (contents == null)
                        contents = "";
-               this.contents = contents;
+               this.contents = originalContents == null ? "" : 
originalContents;
 
                entries.clear();
                oentries.clear();
@@ -837,7 +866,7 @@ public class ConfigMap implements ConfigStoreListener {
                                                var importName = l2.trim();
                                                try {
                                                        if (! 
imports2.containsKey(importName))
-                                                               
imports2.put(importName, store.getMap(importName));
+                                                               
imports2.put(importName, store.getMap(importName, format));
                                                } catch 
(@SuppressWarnings("unused") StackOverflowError e) {
                                                        throw ioex("Import loop 
detected in configuration ''{0}''->''{1}''", name, importName);
                                                }
diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigStore.java
 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigStore.java
index e1780b6416..1d2be21193 100644
--- 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigStore.java
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigStore.java
@@ -27,6 +27,7 @@ import java.util.concurrent.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.commons.collections.*;
+import org.apache.juneau.config.format.*;
 import org.apache.juneau.config.internal.*;
 
 /**
@@ -164,12 +165,26 @@ public abstract class ConfigStore extends Context 
implements Closeable {
         * @throws IOException Thrown by underlying stream.
         */
        public synchronized ConfigMap getMap(String name) throws IOException {
+               return getMap(name, IniConfigFormat.INSTANCE);
+       }
+
+       /**
+        * Returns a map file containing the parsed contents of a configuration.
+        *
+        * @param name The configuration name.
+        * @param format The configuration format.
+        * @return The parsed configuration.
+        * @throws IOException Thrown by underlying stream.
+        */
+       public synchronized ConfigMap getMap(String name, ConfigFormat format) 
throws IOException {
                name = resolveName(name);
-               var cm = configMaps.get(name);
+               var format2 = format == null ? IniConfigFormat.INSTANCE : 
format;
+               var key = format2.id() + ":" + name;
+               var cm = configMaps.get(key);
                if (nn(cm))
                        return cm;
-               cm = new ConfigMap(this, name);
-               var cm2 = configMaps.putIfAbsent(name, cm);
+               cm = new ConfigMap(this, name, format2);
+               var cm2 = configMaps.putIfAbsent(key, cm);
                if (nn(cm2))
                        return cm2;
                register(name, cm);
diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/FileStore.java
 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/FileStore.java
index 6a47d411fa..f7cdcc6c8b 100644
--- 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/FileStore.java
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/FileStore.java
@@ -81,7 +81,7 @@ public class FileStore extends ConfigStore {
                        charset = 
env("ConfigFileStore.charset").map(Charset::forName).orElse(Charset.defaultCharset());
                        directory = env("ConfigFileStore.directory", ".");
                        enableWatcher = env("ConfigFileStore.enableWatcher", 
false);
-                       extensions = env("ConfigFileStore.extensions", "cfg");
+                       extensions = env("ConfigFileStore.extensions", 
"cfg,yml,yaml");
                        updateOnWrite = env("ConfigFileStore.updateOnWrite", 
false);
                        watcherSensitivity = 
env("ConfigFileStore.watcherSensitivity", WatcherSensitivity.MEDIUM);
                }
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 e8f099a1f3..150aa67fc9 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
@@ -641,8 +641,7 @@ public class Microservice implements ConfigEventListener {
         */
        @SuppressWarnings({
                "resource", // Resources are managed by caller
-               "java:S3776", // Cognitive complexity acceptable for 
microservice initialization
-               "java:S106" // Console fallback intentionally writes to 
System.out when no Console is available.
+               "java:S3776" // Cognitive complexity acceptable for 
microservice initialization
        })
        protected Microservice(Builder builder) throws IOException, 
ParseException {
                setInstance(this);
@@ -744,7 +743,7 @@ public class Microservice implements ConfigEventListener {
                if (consoleEnabled) {
                        var c = System.console();
                        this.consoleReader = 
firstNonNull(builder.consoleReader, new Scanner(c == null ? new 
InputStreamReader(System.in) : c.reader()));
-                       this.consoleWriter = 
firstNonNull(builder.consoleWriter, c == null ? new PrintWriter(System.out, 
true) : c.writer());
+                       this.consoleWriter = 
firstNonNull(builder.consoleWriter, c == null ? createLoggerConsoleWriter() : 
c.writer());
 
                        // @Bean-supplied console commands (registered first, 
then overridable by builder/config below).
                        for (var cc : 
beanStore.getBeansOfType(ConsoleCommand.class).values())
@@ -1311,6 +1310,38 @@ public class Microservice implements ConfigEventListener 
{
         */
        protected PrintWriter getConsoleWriter() { return consoleWriter; }
 
+       private PrintWriter createLoggerConsoleWriter() {
+               var fallbackLogger = firstNonNull(getLogger(), 
Logger.getLogger(getClass().getName()));
+               return new PrintWriter(new Writer() {
+                       private final StringBuilder buffer = new 
StringBuilder();
+                       @Override
+                       public void write(char[] cbuf, int off, int len) throws 
IOException {
+                               for (var i = off; i < off + len; i++) {
+                                       var ch = cbuf[i];
+                                       if (ch == '\n') {
+                                               flushBuffer();
+                                       } else if (ch != '\r') {
+                                               buffer.append(ch);
+                                       }
+                               }
+                       }
+                       @Override
+                       public void flush() throws IOException {
+                               flushBuffer();
+                       }
+                       @Override
+                       public void close() throws IOException {
+                               flushBuffer();
+                       }
+                       private void flushBuffer() {
+                               if (! buffer.isEmpty()) {
+                                       fallbackLogger.info(buffer.toString());
+                                       buffer.setLength(0);
+                               }
+                       }
+               }, true);
+       }
+
        /**
         * Logs a message to the log file.
         *
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/config/ConfigBuilder_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigBuilder_Test.java
index 6721208ae0..aaa0eb7905 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/config/ConfigBuilder_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigBuilder_Test.java
@@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.io.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.config.format.*;
 import org.apache.juneau.config.store.*;
 import org.junit.jupiter.api.*;
 
@@ -65,4 +66,29 @@ class ConfigBuilder_Test extends TestBase {
                cf = cf.load("[Test]"+nl+"A = b"+nl, true);
                assertJson("{'':{},Test:{A:'b'}}", cf.toMap());
        }
+
+       @Test void a02_autoDetectYamlByExtension() throws Exception {
+               var s = MemoryStore.create().build();
+               s.update("A.yaml",
+                       "foo:",
+                       "  bar:",
+                       "    k1: v1"
+               );
+               var c = Config.create().store(s).name("A.yaml").build();
+               assertEquals("v1", c.getString("foo/bar/k1"));
+       }
+
+       @Test void a03_explicitYamlBuilderMethod() throws Exception {
+               var s = MemoryStore.create().build();
+               var c = Config.create().store(s).name("B.cfg").yaml().build();
+               c.set("foo/bar/k1", "v1").commit();
+               assertEquals("foo:\n  bar:\n    k1: v1\n", s.read("B.cfg"));
+       }
+
+       @Test void a04_explicitFormatOverride() throws Exception {
+               var s = MemoryStore.create().build();
+               s.update("C.yaml", "[S1]\nk1=v1\n");
+               var c = 
Config.create().store(s).name("C.yaml").format(IniConfigFormat.INSTANCE).build();
+               assertEquals("v1", c.getString("S1/k1"));
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/config/ConfigMap_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigMap_Test.java
index 328df86f0c..97b53f40c2 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/config/ConfigMap_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigMap_Test.java
@@ -260,7 +260,7 @@ class ConfigMap_Test extends TestBase {
                var test = a(
                        "[]", "[  ]",
                        "[/]", "[[]", "[]]", "[\\]",
-                       "[foo/bar]", "[foo[bar]", "[foo]bar]", "[foo\\bar]",
+                       "[foo[bar]", "[foo]bar]", "[foo\\bar]",
                        "[]", "[ ]", "[\t]"
                );
 
@@ -607,7 +607,7 @@ class ConfigMap_Test extends TestBase {
 
                var test = a(
                        "/", "[", "]",
-                       "foo/bar", "foo[bar", "foo]bar",
+                       "foo[bar", "foo]bar",
                        " ",
                        null
                );
@@ -824,7 +824,7 @@ class ConfigMap_Test extends TestBase {
 
                var test = a(
                        "/", "[", "]",
-                       "foo/bar", "foo[bar", "foo]bar",
+                       "foo[bar", "foo]bar",
                        " ",
                        null
                );
@@ -999,7 +999,7 @@ class ConfigMap_Test extends TestBase {
 
                var test = a(
                        "/", "[", "]",
-                       "foo/bar", "foo[bar", "foo]bar",
+                       "foo[bar", "foo]bar",
                        " ",
                        null
                );
@@ -1075,6 +1075,17 @@ class ConfigMap_Test extends TestBase {
                assertDoesNotThrow(()->cm.setEntry("S1", "k1", "v1", "", null, 
null));
        }
 
+       @Test void a47_pathSectionName() throws Exception {
+               var s = initStore("A.cfg",
+                       "[foo/bar/baz]",
+                       "k1 = v1"
+               );
+               var cm = s.getMap("A.cfg");
+
+               assertEquals("[foo/bar/baz]|k1 = v1|", pipedLines(cm));
+               assertEquals("v1", cm.getEntry("foo/bar/baz", "k1").getValue());
+       }
+
        private static ConfigStore initStore(String name, String...contents) {
                return MemoryStore.create().build().update(name, contents);
        }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/config/ConfigYamlFormat_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigYamlFormat_Test.java
new file mode 100644
index 0000000000..d1e44eb7bf
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigYamlFormat_Test.java
@@ -0,0 +1,341 @@
+/*
+ * 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.TestUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.config.format.*;
+import org.apache.juneau.config.store.*;
+import org.junit.jupiter.api.*;
+
+class ConfigYamlFormat_Test extends TestBase {
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // End-to-end behavior through Config / MemoryStore.
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test void a01_readNestedPathValues() throws Exception {
+               var s = MemoryStore.create().build();
+               s.update("A.yaml",
+                       "# top",
+                       "foo:",
+                       "  bar:",
+                       "    k1: v1",
+                       "    k2: v2 # note"
+               );
+               var c = Config.create().store(s).name("A.yaml").build();
+
+               assertEquals("v1", c.getString("foo/bar/k1"));
+               assertEquals("v2", c.getString("foo/bar/k2"));
+       }
+
+       @Test void a02_writeYamlRoundTrip() throws Exception {
+               var s = MemoryStore.create().build();
+               var c = Config.create().store(s).name("B.yaml").build();
+
+               c.set("foo/bar/k1", "v1");
+               c.set("foo/bar/k2", "v2");
+               c.commit();
+
+               assertEquals("foo:\n  bar:\n    k1: v1\n    k2: v2\n", 
s.read("B.yaml"));
+       }
+
+       @Test void a03_defaultSectionMixedWithNestedSection() throws Exception {
+               var s = MemoryStore.create().build();
+               s.update("C.yaml",
+                       "k0: v0",
+                       "foo:",
+                       "  k1: v1"
+               );
+               var c = Config.create().store(s).name("C.yaml").build();
+
+               assertEquals("v0", c.getString("k0"));
+               assertEquals("v1", c.getString("foo/k1"));
+       }
+
+       @Test void a04_yamlDocumentMarkerIgnored() throws Exception {
+               var s = MemoryStore.create().build();
+               s.update("D.yaml",
+                       "---",
+                       "foo:",
+                       "  k1: v1"
+               );
+               var c = Config.create().store(s).name("D.yaml").build();
+
+               assertEquals("v1", c.getString("foo/k1"));
+       }
+
+       @Test void a05_importsLineIgnored() throws Exception {
+               var s = MemoryStore.create().build();
+               s.update("E.yaml",
+                       "_imports: [other]",
+                       "foo:",
+                       "  k1: v1"
+               );
+               var c = Config.create().store(s).name("E.yaml").build();
+
+               assertEquals("v1", c.getString("foo/k1"));
+       }
+
+       @Test void a06_singleQuotedValueParsed() throws Exception {
+               var s = MemoryStore.create().build();
+               s.update("F.yaml",
+                       "foo:",
+                       "  k1: 'has: colon'"
+               );
+               var c = Config.create().store(s).name("F.yaml").build();
+
+               assertEquals("has: colon", c.getString("foo/k1"));
+       }
+
+       @Test void a07_doubleQuotedValueParsed() throws Exception {
+               var s = MemoryStore.create().build();
+               s.update("G.yaml",
+                       "foo:",
+                       "  k1: \"hash: # value\""
+               );
+               var c = Config.create().store(s).name("G.yaml").build();
+
+               // The "#" splits inline-comments since it's prefixed by a 
space.
+               assertEquals("\"hash:", c.getString("foo/k1"));
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Direct unit tests on YamlConfigFormat (drive branch coverage on 
helpers).
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test void b01_idIsYaml() {
+               assertEquals("yaml", YamlConfigFormat.INSTANCE.id());
+       }
+
+       @Test void b02_toInternalNullReturnsEmpty() throws Exception {
+               assertEquals("", YamlConfigFormat.INSTANCE.toInternal(null));
+       }
+
+       @Test void b03_toInternalEmptyReturnsEmpty() throws Exception {
+               assertEquals("", YamlConfigFormat.INSTANCE.toInternal(""));
+       }
+
+       @Test void b04_toInternalDefaultSectionOnly() throws Exception {
+               var ini = YamlConfigFormat.INSTANCE.toInternal("k1: v1\n");
+               assertEquals("k1 = v1\n", ini);
+       }
+
+       @Test void b05_toInternalNestedSection() throws Exception {
+               var ini = YamlConfigFormat.INSTANCE.toInternal(
+                       "foo:\n"
+                       + "  bar:\n"
+                       + "    k1: v1\n"
+               );
+               assertEquals("[foo/bar]\nk1 = v1\n", ini);
+       }
+
+       @Test void b06_toInternalLeavesBlanksAndComments() throws Exception {
+               var ini = YamlConfigFormat.INSTANCE.toInternal(
+                       "# header\n"
+                       + "\n"
+                       + "foo:\n"
+                       + "  k1: v1\n"
+               );
+               // Comments and blanks are preserved verbatim alongside the new 
INI section header.
+               assertTrue(ini.contains("# header"));
+               assertTrue(ini.contains("[foo]"));
+               assertTrue(ini.contains("k1 = v1"));
+       }
+
+       @Test void b07_toInternalUnindentReducesStack() throws Exception {
+               var ini = YamlConfigFormat.INSTANCE.toInternal(
+                       "a:\n"
+                       + "  b:\n"
+                       + "    k1: v1\n"
+                       + "c:\n"
+                       + "  k2: v2\n"
+               );
+               assertTrue(ini.contains("[a/b]"));
+               assertTrue(ini.contains("[c]"));
+               assertTrue(ini.contains("k1 = v1"));
+               assertTrue(ini.contains("k2 = v2"));
+       }
+
+       @Test void b08_toInternalInlineComment() throws Exception {
+               var ini = YamlConfigFormat.INSTANCE.toInternal(
+                       "foo:\n"
+                       + "  k1: v1 # inline\n"
+               );
+               assertEquals("[foo]\nk1 = v1 # inline\n", ini);
+       }
+
+       @Test void b09_toInternalLineMissingColonThrows() {
+               assertThrows(IOException.class, () -> 
YamlConfigFormat.INSTANCE.toInternal("not-a-yaml-line\n"));
+       }
+
+       @Test void b10_toInternalLineWithLeadingColonThrows() {
+               assertThrows(IOException.class, () -> 
YamlConfigFormat.INSTANCE.toInternal(": value\n"));
+       }
+
+       @Test void b11_toInternalSingleQuotedValue() throws Exception {
+               var ini = YamlConfigFormat.INSTANCE.toInternal("k: 'q'\n");
+               assertEquals("k = q\n", ini);
+       }
+
+       @Test void b12_toInternalDoubleQuotedValue() throws Exception {
+               var ini = YamlConfigFormat.INSTANCE.toInternal("k: \"q\"\n");
+               assertEquals("k = q\n", ini);
+       }
+
+       @Test void b13_toInternalShortQuotedValuePassesThrough() throws 
Exception {
+               var ini = YamlConfigFormat.INSTANCE.toInternal("k: \"\n");
+               assertEquals("k = \"\n", ini);
+       }
+
+       @Test void b14_fromInternalEmptyMapReturnsEmpty() throws Exception {
+               var s = MemoryStore.create().build();
+               var map = s.getMap("Empty.yaml", YamlConfigFormat.INSTANCE);
+               assertEquals("", YamlConfigFormat.INSTANCE.fromInternal(map));
+       }
+
+       @Test void b15_fromInternalEmptyValueGetsQuoted() throws Exception {
+               var s = MemoryStore.create().build();
+               var c = Config.create().store(s).name("H.yaml").yaml().build();
+               c.set("foo/k1", "");
+               c.commit();
+
+               assertTrue(s.read("H.yaml").contains("k1: \"\""));
+       }
+
+       @Test void b16_fromInternalQuotesValueWithSpecialChars() throws 
Exception {
+               var s = MemoryStore.create().build();
+               var c = Config.create().store(s).name("I.yaml").yaml().build();
+               c.set("foo/k1", "has: colon");
+               c.commit();
+
+               assertTrue(s.read("I.yaml").contains("k1: \"has: colon\""));
+       }
+
+       @Test void b17_fromInternalEscapesDoubleQuotes() throws Exception {
+               var s = MemoryStore.create().build();
+               var c = Config.create().store(s).name("J.yaml").yaml().build();
+               c.set("foo/k1", "has # hash with \" quote");
+               c.commit();
+
+               assertTrue(s.read("J.yaml").contains("\\\""));
+       }
+
+       @Test void b18_fromInternalTrailingSpaceTriggersQuoting() throws 
Exception {
+               var s = MemoryStore.create().build();
+               var c = Config.create().store(s).name("K.yaml").yaml().build();
+               c.set("foo/k1", "trailing ");
+               c.commit();
+
+               assertTrue(s.read("K.yaml").contains("\"trailing \""));
+       }
+
+       @Test void b19_fromInternalLeadingSpaceTriggersQuoting() throws 
Exception {
+               var s = MemoryStore.create().build();
+               var c = Config.create().store(s).name("L.yaml").yaml().build();
+               c.set("foo/k1", " leading");
+               c.commit();
+
+               assertTrue(s.read("L.yaml").contains("\" leading\""));
+       }
+
+       @Test void b20_fromInternalSharedPrefixReusedAcrossSections() throws 
Exception {
+               var s = MemoryStore.create().build();
+               var c = Config.create().store(s).name("M.yaml").yaml().build();
+               c.set("a/b/k1", "v1");
+               c.set("a/c/k2", "v2");
+               c.commit();
+
+               var written = s.read("M.yaml");
+               // 'a' should appear exactly once since the prefix is shared 
between 'a/b' and 'a/c'.
+               var firstA = written.indexOf("a:");
+               var lastA = written.lastIndexOf("a:");
+               assertTrue(firstA != -1 && firstA == lastA);
+       }
+
+       @Test void b21_yamlRoundTripPreservesValues() throws Exception {
+               var s = MemoryStore.create().build();
+               var c = Config.create().store(s).name("N.yaml").yaml().build();
+               c.set("alpha/beta/k1", "v1");
+               c.set("alpha/k2", "v2");
+               c.commit();
+
+               var c2 = Config.create().store(s).name("N.yaml").yaml().build();
+               assertEquals("v1", c2.getString("alpha/beta/k1"));
+               assertEquals("v2", c2.getString("alpha/k2"));
+       }
+
+       @Test void b22_roundTripPreservesInlineComment() throws Exception {
+               var s = MemoryStore.create().build();
+               s.update("RT.yaml",
+                       "foo:",
+                       "  k1: v1 # original"
+               );
+               var c = Config.create().store(s).name("RT.yaml").build();
+               // Force re-serialization by updating a different key and 
committing.
+               c.set("foo/k2", "v2");
+               c.commit();
+
+               var written = s.read("RT.yaml");
+               assertTrue(written.contains("# original"), () -> "Expected 
comment to survive round-trip, got:\n" + written);
+       }
+
+       @Test void b23_toInternalAllBlankLineHandled() throws Exception {
+               // A line of only spaces exercises the leadingSpaces "i < 
length false" branch.
+               var ini = YamlConfigFormat.INSTANCE.toInternal(
+                       "foo:\n"
+                       + "    \n"
+                       + "  k1: v1\n"
+               );
+               assertTrue(ini.contains("k1 = v1"));
+       }
+
+       @Test void b24_toInternalUnclosedQuotePassesThrough() throws Exception {
+               // Value starts with a quote but doesn't end with one — unquote 
should not strip it.
+               var ini = YamlConfigFormat.INSTANCE.toInternal("k: 
\"unmatched\n");
+               assertEquals("k = \"unmatched\n", ini);
+       }
+
+       @Test void b24a_toInternalUnclosedSingleQuotePassesThrough() throws 
Exception {
+               // Single-quote variant of b24 to hit the second clause of the 
unquote check.
+               var ini = YamlConfigFormat.INSTANCE.toInternal("k: 
'unmatched\n");
+               assertEquals("k = 'unmatched\n", ini);
+       }
+
+       @Test void b24b_toInternalEmptyLineAtEnd() throws Exception {
+               // Trailing empty line exercises the leadingSpaces empty-string 
path.
+               var ini = YamlConfigFormat.INSTANCE.toInternal("foo:\n  k1: 
v1\n\n");
+               assertTrue(ini.contains("k1 = v1"));
+       }
+
+       @Test void b25_configToMapMatchesNestedYamlInput() throws Exception {
+               var s = MemoryStore.create().build();
+               s.update("O.yaml",
+                       "foo:",
+                       "  bar:",
+                       "    k1: v1",
+                       "  k2: v2"
+               );
+               var c = Config.create().store(s).name("O.yaml").build();
+
+               assertJson("{'':{},'foo/bar':{k1:'v1'},foo:{k2:'v2'}}", 
c.toMap());
+       }
+}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/ng/http/HttpFactoryFacades_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/ng/http/HttpFactoryFacades_Test.java
index 0b68495f96..905ff3eb8d 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/ng/http/HttpFactoryFacades_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/ng/http/HttpFactoryFacades_Test.java
@@ -29,7 +29,6 @@ import java.util.stream.*;
 import org.apache.juneau.*;
 import org.apache.juneau.commons.http.*;
 import org.apache.juneau.http.header.*;
-import org.apache.juneau.ng.http.part.*;
 import org.junit.jupiter.params.*;
 import org.junit.jupiter.params.provider.*;
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/ng/rest/NgRemoteInterfaceTransport_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/ng/rest/NgRemoteInterfaceTransport_Test.java
index a3697ffea3..c6b2bbec17 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/ng/rest/NgRemoteInterfaceTransport_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/ng/rest/NgRemoteInterfaceTransport_Test.java
@@ -19,7 +19,6 @@ package org.apache.juneau.ng.rest;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.util.concurrent.*;
-import java.util.function.*;
 import java.util.stream.*;
 
 import org.apache.juneau.*;
@@ -37,7 +36,6 @@ import org.apache.juneau.ng.rest.client.jetty.*;
 import org.apache.juneau.ng.rest.client.okhttp.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.servlet.*;
-import org.junit.jupiter.api.*;
 import org.junit.jupiter.api.extension.*;
 import org.junit.jupiter.params.*;
 import org.junit.jupiter.params.provider.*;
@@ -296,7 +294,7 @@ class NgRemoteInterfaceTransport_Test extends TestBase {
        @MethodSource("transports")
        void e01_voidReturn_succeeds(String name, TransportSupplier ts) throws 
Exception {
                try (var c = buildClient(ts)) {
-                       c.proxy().noContent(); // expects no exception
+                       assertDoesNotThrow(() -> c.proxy().noContent());
                }
        }
 


Reply via email to