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 d63455807a StringFormatter class
d63455807a is described below

commit d63455807acab26ea88bdf27153730009ac2681d
Author: James Bognar <[email protected]>
AuthorDate: Sat Nov 29 09:47:17 2025 -0500

    StringFormatter class
---
 .../java/org/apache/juneau/bean/LinkString.java    |   2 +-
 .../org/apache/juneau/assertions/Assertion.java    |   2 +-
 .../juneau/common/reflect/ExecutableInfo.java      |   4 +-
 .../apache/juneau/common/utils/FormatString.java   | 724 ---------------------
 .../apache/juneau/common/utils/StringFormat.java   |   8 +-
 .../apache/juneau/common/utils/ThrowableUtils.java |  16 +-
 .../java/org/apache/juneau/BasicException.java     |   3 +-
 .../org/apache/juneau/BasicRuntimeException.java   |   7 +-
 .../main/java/org/apache/juneau/BeanSession.java   |   2 +-
 .../java/org/apache/juneau/ContextSession.java     |   2 +-
 .../main/java/org/apache/juneau/cp/Messages.java   |   2 +-
 .../org/apache/juneau/parser/ParseException.java   |   2 +-
 .../org/apache/juneau/parser/ParserListener.java   |   5 +-
 .../juneau/serializer/SerializeException.java      |   2 +-
 .../juneau/serializer/SerializerListener.java      |   3 +-
 .../juneau/serializer/SerializerSession.java       |   2 +-
 .../apache/juneau/microservice/Microservice.java   |   2 +-
 .../juneau/microservice/jetty/JettyLogger.java     |  18 +-
 .../org/apache/juneau/rest/client/RestClient.java  |  12 +-
 .../juneau/rest/mock/MockServletRequest.java       |   2 +-
 .../org/apache/juneau/rest/arg/ArgException.java   |   3 +-
 .../org/apache/juneau/rest/servlet/RestObject.java |   4 +-
 .../apache/juneau/rest/servlet/RestServlet.java    |   4 +-
 .../juneau/rest/swagger/SwaggerException.java      |   3 +-
 .../test/java/org/apache/juneau/ComboInput.java    |   2 +-
 .../a/rttests/RoundTripLargeObjects_Test.java      |   3 +-
 .../juneau/common/utils/FormatString_Test.java     | 416 ------------
 .../org/apache/juneau/common/utils/Utils_Test.java |   4 +-
 .../apache/juneau/rest/annotation/Path_Test.java   |  15 +-
 .../client/RestClient_Config_RestClient_Test.java  |   2 +-
 30 files changed, 59 insertions(+), 1217 deletions(-)

diff --git 
a/juneau-bean/juneau-bean-common/src/main/java/org/apache/juneau/bean/LinkString.java
 
b/juneau-bean/juneau-bean-common/src/main/java/org/apache/juneau/bean/LinkString.java
index 2cc51dec96..fb23f0218e 100644
--- 
a/juneau-bean/juneau-bean-common/src/main/java/org/apache/juneau/bean/LinkString.java
+++ 
b/juneau-bean/juneau-bean-common/src/main/java/org/apache/juneau/bean/LinkString.java
@@ -152,7 +152,7 @@ public class LinkString implements Comparable<LinkString> {
                        } catch (SchemaValidationException | SerializeException 
e) {
                                throw toRex(e);
                        }
-               this.uri = java.net.URI.create(mformat(value, args));
+               this.uri = java.net.URI.create(f(value, args));
                return this;
        }
 
diff --git 
a/juneau-core/juneau-assertions/src/main/java/org/apache/juneau/assertions/Assertion.java
 
b/juneau-core/juneau-assertions/src/main/java/org/apache/juneau/assertions/Assertion.java
index e884dda1fc..17b10eacf7 100644
--- 
a/juneau-core/juneau-assertions/src/main/java/org/apache/juneau/assertions/Assertion.java
+++ 
b/juneau-core/juneau-assertions/src/main/java/org/apache/juneau/assertions/Assertion.java
@@ -215,7 +215,7 @@ public class Assertion {
        protected BasicAssertionError error(Throwable cause, String msg, 
Object...args) {
                msg = mformat(msg, args);
                if (nn(this.msg))
-                       msg = mformat(this.msg, 
this.msgArgs).replace("<<<MSG>>>", msg);
+                       msg = f(this.msg, this.msgArgs).replace("<<<MSG>>>", 
msg);
                if (nn(out))
                        out.println(msg);
                if (nn(throwable)) {
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
index 967d3541e0..515af99dc0 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
@@ -753,9 +753,9 @@ public abstract class ExecutableInfo extends AccessibleInfo 
{
        private void checkIndex(int index) {
                int pc = getParameterCount();
                if (pc == 0)
-                       throw new IndexOutOfBoundsException(mformat("Invalid 
index ''{0}''.  No parameters.", index));
+                       throw new IndexOutOfBoundsException(f("Invalid index 
''{0}''.  No parameters.", index));
                if (index < 0 || index >= pc)
-                       throw new IndexOutOfBoundsException(mformat("Invalid 
index ''{0}''.  Parameter count: {1}", index, pc));
+                       throw new IndexOutOfBoundsException(f("Invalid index 
''{0}''.  Parameter count: {1}", index, pc));
        }
 
        private List<ParameterInfo> findParameters() {
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/FormatString.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/FormatString.java
deleted file mode 100644
index d3a2e9dc69..0000000000
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/FormatString.java
+++ /dev/null
@@ -1,724 +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.common.utils;
-
-import static org.apache.juneau.common.utils.AssertionUtils.*;
-import static org.apache.juneau.common.utils.CollectionUtils.*;
-import static org.apache.juneau.common.utils.StateEnum.*;
-import static org.apache.juneau.common.utils.Utils.*;
-
-import java.text.*;
-import java.util.*;
-import java.util.stream.*;
-import java.util.MissingFormatArgumentException;
-
-import org.apache.juneau.common.collections.*;
-
-/**
- * Unified string formatter supporting both MessageFormat-style and 
printf-style formatting in the same pattern.
- *
- * <p>
- * This class provides a thread-safe, cacheable formatter that can handle 
mixed format styles in a single pattern.
- * It supports both MessageFormat syntax (<js>"{0}"</js>, 
<js>"{1,number}"</js>) and printf syntax (<js>"%s"</js>, <js>"%d"</js>)
- * within the same string.
- *
- * <h5 class='section'>Features:</h5>
- * <ul>
- *   <li><b>Dual Format Support:</b> Mix MessageFormat and printf-style 
placeholders in the same pattern</li>
- *   <li><b>Thread-Safe:</b> Immutable class, safe for concurrent use</li>
- *   <li><b>Cacheable:</b> Use {@link #of(String)} for cached instances</li>
- *   <li><b>Argument Sharing:</b> Both format styles share the same argument 
array</li>
- * </ul>
- *
- * <h5 class='section'>Format Style Detection:</h5>
- * <p>
- * The formatter automatically detects which style to use for each placeholder:
- * <ul>
- *   <li><b>MessageFormat style:</b> <js>"{0}"</js>, <js>"{1,number}"</js>, 
<js>"{2,date}"</js>, etc.</li>
- *   <li><b>Printf style:</b> <js>"%s"</js>, <js>"%d"</js>, <js>"%.2f"</js>, 
<js>"%1$s"</js>, etc.</li>
- * </ul>
- *
- * <h5 class='section'>Argument Mapping:</h5>
- * <p>
- * Arguments are processed in order of appearance:
- * <ul>
- *   <li><b>MessageFormat placeholders:</b> Use explicit indices (e.g., 
<js>"{0}"</js> uses <c>args[0]</c>)</li>
- *   <li><b>Printf placeholders:</b> Use sequential indices starting after the 
highest MessageFormat index</li>
- * </ul>
- *
- * <h5 class='section'>Examples:</h5>
- * <p class='bjava'>
- *     <jc>// Mixed format styles</jc>
- *     FormatString <jv>fmt</jv> = FormatString.<jsm>of</jsm>(<js>"Hello {0}, 
you have %d items"</js>);
- *     String <jv>result</jv> = 
<jv>fmt</jv>.<jsm>format</jsm>(<js>"John"</js>, 5);
- *     <jc>// Returns: "Hello John, you have 5 items"</jc>
- *
- *     <jc>// MessageFormat with explicit indices, printf with sequential</jc>
- *     FormatString <jv>fmt2</jv> = FormatString.<jsm>of</jsm>(<js>"User {0} 
has %s and {1} items"</js>);
- *     String <jv>result2</jv> = 
<jv>fmt2</jv>.<jsm>format</jsm>(<js>"Alice"</js>, 10, <js>"admin"</js>);
- *     <jc>// Returns: "User Alice has admin and 10 items"</jc>
- *     <jc>// {0} -> "Alice", {1} -> 10, %s -> "admin"</jc>
- *
- *     <jc>// Printf with explicit indices</jc>
- *     FormatString <jv>fmt3</jv> = FormatString.<jsm>of</jsm>(<js>"%1$s loves 
%2$s, and {0} also loves %3$s"</js>);
- *     String <jv>result3</jv> = 
<jv>fmt3</jv>.<jsm>format</jsm>(<js>"Alice"</js>, <js>"Bob"</js>, 
<js>"Charlie"</js>);
- *     <jc>// Returns: "Alice loves Bob, and Alice also loves Charlie"</jc>
- * </p>
- *
- * <h5 class='section'>Caching:</h5>
- * <p>
- * Use {@link #of(String)} to get cached instances. The cache is thread-safe 
and limited to 1000 entries.
- * For uncached instances, use the constructor directly.
- * </p>
- *
- * <h5 class='section'>Thread Safety:</h5>
- * <p>
- * This class is immutable and thread-safe. Multiple threads can safely use 
the same instance concurrently.
- * </p>
- *
- * @see StringUtils#format(String, Object...)
- * @see StringUtils#mformat(String, Object...)
- */
-public final class FormatString {
-
-       private static final Cache<String,FormatString> CACHE = 
Cache.of(String.class, FormatString.class).maxSize(1000).weak().build();
-
-       private static final Cache2<Locale,String,MessageFormat> 
MESSAGE_FORMAT_CACHE = Cache2.of(Locale.class, String.class, 
MessageFormat.class).maxSize(100).threadLocal().weak()
-               .supplier((locale, content) -> new MessageFormat(content, 
locale)).build();
-
-       private static final Cache<Locale,NumberFormat> NUMBER_FORMAT_CACHE = 
Cache.of(Locale.class, 
NumberFormat.class).maxSize(50).threadLocal().weak().supplier(NumberFormat::getInstance).build();
-
-       private static final Cache<Locale,DateFormat> DATE_FORMAT_CACHE = 
Cache.of(Locale.class, DateFormat.class).maxSize(50).threadLocal().weak()
-               .supplier(locale -> 
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, 
locale)).build();
-
-       private static final AsciiSet PRINTF_CONVERSION_CHARS = 
AsciiSet.of("bBhHsScCdoxXeEfgGaAtTn%");
-       private static final AsciiSet PRINTF_FORMAT_CHARS = AsciiSet.of("-+ 
0(#.*$");
-       private static final AsciiSet FORMAT_CHARS = AsciiSet.of("%{'");
-
-       /**
-        * Formats a pattern string with the given arguments using the default 
locale.
-        *
-        * <p>
-        * This is a convenience method that creates a FormatString instance 
and formats it.
-        * If the pattern contains no format specifiers (no '%', '{', or '''), 
the pattern is returned as-is.
-        *
-        * @param pattern The format pattern.
-        * @param args The arguments to format.
-        * @return The formatted string.
-        * @throws IllegalArgumentException If the pattern is <jk>null</jk> or 
format specifiers are invalid.
-        */
-       public static String format(String pattern, Object...args) {
-               if (!hasArgs(pattern))
-                       return pattern;
-               return of(pattern).format(args);
-       }
-
-       /**
-        * Formats a pattern string with the given arguments using the 
specified locale.
-        *
-        * <p>
-        * This is a convenience method that creates a FormatString instance 
and formats it.
-        * If the pattern contains no format specifiers (no '%', '{', or '''), 
the pattern is returned as-is.
-        *
-        * @param pattern The format pattern.
-        * @param locale The locale to use for formatting. If <jk>null</jk>, 
uses the default locale.
-        * @param args The arguments to format.
-        * @return The formatted string.
-        * @throws IllegalArgumentException If the pattern is <jk>null</jk> or 
format specifiers are invalid.
-        */
-       public static String format(String pattern, Locale locale, 
Object...args) {
-               if (!hasArgs(pattern))
-                       return pattern;
-               return of(pattern).format(locale, args);
-       }
-
-       /**
-        * Checks if a pattern contains any format specifiers.
-        *
-        * <p>
-        * Returns <jk>true</jk> if the pattern contains any of the following 
characters:
-        * <ul>
-        *   <li><js>'%'</js> - Printf-style format specifier</li>
-        *   <li><js>'{'</js> - MessageFormat-style placeholder</li>
-        *   <li><js>'''</js> - MessageFormat quote</li>
-        * </ul>
-        *
-        * <p>
-        * Uses {@link AsciiSet} for efficient single-pass scanning of the 
string.
-        *
-        * @param pattern The pattern to check.
-        * @return <jk>true</jk> if the pattern contains format specifiers, 
<jk>false</jk> otherwise.
-        */
-       private static boolean hasArgs(String pattern) {
-               if (pattern == null || pattern.isEmpty())
-                       return false;
-               for (var i = 0; i < pattern.length(); i++) {
-                       if (FORMAT_CHARS.contains(pattern.charAt(i)))
-                               return true;
-               }
-               return false;
-       }
-
-       /**
-        * Base class for format tokens.
-        */
-       private abstract static class Token {
-               /**
-                * Appends the formatted content to the StringBuilder.
-                *
-                * @param sb The StringBuilder to append to.
-                * @param args The arguments array.
-                * @param locale The locale for formatting (can be null for 
default).
-                */
-               abstract void append(StringBuilder sb, Object[] args, Locale 
locale);
-       }
-
-       /**
-        * Literal text token.
-        */
-       private static final class LiteralToken extends Token {
-               private final String text;
-
-               LiteralToken(String text) {
-                       this.text = text;
-               }
-
-               @Override
-               void append(StringBuilder sb, Object[] args, Locale locale) {
-                       sb.append(text);
-               }
-
-               @Override
-               public String toString() {
-                       return "[L:" + text + "]";
-               }
-       }
-
-       /**
-        * MessageFormat-style token (e.g., {0}, {1,number}).
-        */
-       private static final class MessageFormatToken extends Token {
-               private final char format;
-               private final String content; // null for simple tokens, 
normalized pattern like "{0,number}" for complex tokens
-               private final int index; // 0-based index
-               private final String placeholder; // Original placeholder text 
like "{0}" or "{0,number}"
-
-               /**
-                * @param content - The variable content such as "{}" or "{0}", 
or "{0,number}"
-                *      The content should have the curly-braces already 
removed so that we're only looking at the inner parts.
-                * @param index - The zero-based index of the variable in the 
message (used for sequential {} placeholders).
-                */
-               MessageFormatToken(String content, int index) {
-                       if (content.isBlank()) {
-                               this.content = null;
-                               this.index = index;
-                               this.format = 's';
-                               this.placeholder = "{" + index + "}";
-                       } else if (content.indexOf(',') == -1) {
-                               this.content = null;
-                               this.index = parseIndexMF(content);
-                               this.format = 's';
-                               this.placeholder = "{" + this.index + "}";
-                       } else {
-                               var tokens = content.split(",", 2);
-                               this.index = parseIndexMF(tokens[0]);
-                               this.content = "{0," + tokens[1] + "}";
-                               this.format = 'o';
-                               this.placeholder = "{" + this.index + "," + 
tokens[1] + "}";
-                       }
-               }
-
-               @Override
-               void append(StringBuilder sb, Object[] args, Locale locale) {
-                       // MessageFormat inserts the placeholder text if 
argument is missing
-                       if (args == null || index >= args.length || index < 0) {
-                               sb.append(placeholder);
-                               return;
-                       }
-                       var o = args[index];
-                       var l = locale == null ? Locale.getDefault() : locale;
-                       switch (format) {
-                               case 's':
-                                       if (o == null) {
-                                               sb.append("null");
-                                       } else if (o instanceof Number o2) {
-                                               
sb.append(NUMBER_FORMAT_CACHE.get(l).format(o2));
-                                       } else if (o instanceof Date o2) {
-                                               
sb.append(DATE_FORMAT_CACHE.get(l).format(o2));
-                                       } else {
-                                               sb.append(o.toString());
-                                       }
-                                       break;
-                               default:
-                                       // Use Cache2 with Locale and content 
as separate keys to avoid string concatenation
-                                       var mf = MESSAGE_FORMAT_CACHE.get(l, 
content);
-                                       sb.append(mf.format(a(o)));
-                                       break;
-                       }
-               }
-
-               @Override
-               public String toString() {
-                       return "[M:" + format + index + (content == null ? "" : 
(':' + content)) + "]";
-               }
-       }
-
-       /**
-        * Printf-style token (e.g., %s, %d, %.2f).
-        */
-       private static final class StringFormatToken extends Token {
-               private final char format; // 's' = simple (handle directly), 
'o' = other (use String.format)
-               private final String content; // The format string to pass to 
String.format (null for simple formats)
-               private final int index; // 0-based index
-
-               StringFormatToken(String content, int index) {
-                       // content is everything after '%' (e.g., "s", "1$s", 
"d", ".2f", "1$.2f")
-                       var $ = content.indexOf('$');
-                       if ($ >= 0) {
-                               index = parseIndexSF(content.substring(0, $)) - 
1;
-                               content = content.substring($ + 1);
-                       }
-                       this.format = content.length() == 1 ? 
content.charAt(content.length() - 1) : 'z';
-                       this.index = index;
-                       this.content = "%" + content;
-               }
-
-               @Override
-               void append(StringBuilder sb, Object[] args, Locale locale) {
-                       // String.format() throws 
MissingFormatArgumentException when argument is missing
-                       if (args == null || index >= args.length || index < 0) {
-                               throw new 
MissingFormatArgumentException(content);
-                       }
-                       var o = args[index];
-                       var l = locale == null ? Locale.getDefault() : locale;
-                       var dl = locale == null || 
locale.equals(Locale.getDefault());
-                       switch (format) {
-                               case 'b':
-                                       // String.format() with %b converts:
-                                       // - null -> "false"
-                                       // - Boolean -> toString()
-                                       // - Any other non-null value -> "true"
-                                       if (o == null) {
-                                               sb.append("false");
-                                       } else if (o instanceof Boolean) {
-                                               sb.append(o.toString());
-                                       } else {
-                                               sb.append("true");
-                                       }
-                                       return;
-                               case 'B':
-                                       // String.format() with %B converts:
-                                       // - null -> "FALSE"
-                                       // - Boolean -> toString().toUpperCase()
-                                       // - Any other non-null value -> "TRUE"
-                                       if (o == null) {
-                                               sb.append("FALSE");
-                                       } else if (o instanceof Boolean) {
-                                               
sb.append(o.toString().toUpperCase());
-                                       } else {
-                                               sb.append("TRUE");
-                                       }
-                                       return;
-                               case 's':
-                                       if (o == null) {
-                                               sb.append("null");
-                                               return;
-                                       }
-                                       sb.append(o.toString());
-                                       return;
-                               case 'S':
-                                       if (o == null) {
-                                               sb.append("NULL");
-                                               return;
-                                       }
-                                       sb.append(o.toString().toUpperCase());
-                                       return;
-                               case 'd':
-                                       if (o == null) {
-                                               sb.append("null");
-                                               return;
-                                       }
-                                       if (o instanceof Number o2) {
-                                               if (dl) {
-                                                       if (o instanceof 
Integer || o instanceof Long || o instanceof Byte || o instanceof Short) {
-                                                               sb.append(o);
-                                                       } else {
-                                                               // For other 
Number types (BigDecimal, BigInteger, etc.), convert to long
-                                                               
sb.append(o2.longValue());
-                                                       }
-                                                       return;
-                                               }
-                                               // For non-default locales, use 
String.format to ensure printf-style consistency
-                                               sb.append(sf(l, "%d", o));
-                                               return;
-                                       }
-                                       break;
-                               case 'x':
-                                       if (o == null) {
-                                               sb.append("null");
-                                               return;
-                                       }
-                                       if (o instanceof Integer o2) {
-                                               
sb.append(Integer.toHexString(o2));
-                                               return;
-                                       } else if (o instanceof Long o2) {
-                                               sb.append(Long.toHexString(o2));
-                                               return;
-                                       }
-                                       break;
-                               case 'X':
-                                       if (o == null) {
-                                               sb.append("NULL");
-                                               return;
-                                       }
-                                       if (o instanceof Integer o2) {
-                                               
sb.append(Integer.toHexString(o2).toUpperCase());
-                                               return;
-                                       } else if (o instanceof Long o2) {
-                                               
sb.append(Long.toHexString(o2).toUpperCase());
-                                               return;
-                                       }
-                                       break;
-                               case 'o':
-                                       if (o == null) {
-                                               sb.append("null");
-                                               return;
-                                       }
-                                       if (o instanceof Integer o2) {
-                                               
sb.append(Integer.toOctalString(o2));
-                                               return;
-                                       } else if (o instanceof Long o2) {
-                                               
sb.append(Long.toOctalString(o2));
-                                               return;
-                                       }
-                                       break;
-                               case 'c':
-                                       if (o == null) {
-                                               sb.append("null");
-                                               return;
-                                       }
-                                       if (o instanceof Character) {
-                                               sb.append(o);
-                                               return;
-                                       } else if (o instanceof Integer o2) {
-                                               sb.append((char)o2.intValue());
-                                               return;
-                                       }
-                                       break;
-                               case 'C':
-                                       if (o == null) {
-                                               sb.append("NULL");
-                                               return;
-                                       }
-                                       if (o instanceof Character o2) {
-                                               
sb.append(Character.toUpperCase(o2));
-                                               return;
-                                       } else if (o instanceof Integer o2) {
-                                               
sb.append(Character.toUpperCase((char)o2.intValue()));
-                                               return;
-                                       }
-                                       break;
-                               case 'f':
-                                       if (o == null) {
-                                               sb.append("null");
-                                               return;
-                                       }
-                                       // Always use String.format() to match 
exact behavior (precision, etc.)
-                                       if (o instanceof Number) {
-                                               sb.append(sf(l, "%f", o));
-                                               return;
-                                       }
-                                       break;
-                               default:
-                                       break;
-                       }
-
-                       // Fallback to String.format for any other simple format
-                       sb.append(sf(l, content, o));
-               }
-
-               @Override
-               public String toString() {
-                       return "[S:" + format + index + ":" + content + "]";
-               }
-       }
-
-       private static String sf(Locale l, String s, Object o) {
-               return String.format(l, s, a(o));
-       }
-
-       private final String pattern;
-       private final Token[] tokens;
-
-       /**
-        * Creates a new FormatString instance.
-        *
-        * @param pattern The format pattern. Can contain both MessageFormat 
and printf-style placeholders.
-        * @throws IllegalArgumentException If the pattern is <jk>null</jk>.
-        */
-       public FormatString(String pattern) {
-               this.pattern = assertArgNotNull("pattern", pattern);
-               this.tokens = parseTokens(pattern).toArray(Token[]::new);
-       }
-
-       /**
-        * Returns a cached FormatString instance for the given pattern.
-        *
-        * <p>
-        * This method uses a thread-safe cache to avoid recreating 
FormatString instances for the same pattern.
-        * The cache is limited to 1000 entries.
-        *
-        * @param pattern The format pattern.
-        * @return A cached or new FormatString instance.
-        * @throws IllegalArgumentException If the pattern is <jk>null</jk>.
-        */
-       public static FormatString of(String pattern) {
-               assertArgNotNull("pattern", pattern);
-               return CACHE.get(pattern, () -> new FormatString(pattern));
-       }
-
-       /**
-        * Formats the pattern with the given arguments using the default 
locale.
-        *
-        * @param args The arguments to format.
-        * @return The formatted string.
-        * @throws IllegalArgumentException If format specifiers are invalid or 
arguments don't match.
-        */
-       public String format(Object...args) {
-               return format(Locale.getDefault(), args);
-       }
-
-       /**
-        * Formats the pattern with the given arguments using the specified 
locale.
-        *
-        * <p>
-        * The locale affects both MessageFormat and printf-style formatting:
-        * <ul>
-        *   <li><b>MessageFormat:</b> Locale-specific number, date, and time 
formatting</li>
-        *   <li><b>Printf:</b> Locale-specific number formatting (decimal 
separators, etc.)</li>
-        * </ul>
-        *
-        * @param locale The locale to use for formatting. If <jk>null</jk>, 
uses the default locale.
-        * @param args The arguments to format.
-        * @return The formatted string.
-        * @throws IllegalArgumentException If format specifiers are invalid or 
arguments don't match.
-        */
-       public String format(Locale locale, Object...args) {
-               var sb = new StringBuilder(pattern.length() + 64);
-               for (var token : tokens) {
-                       token.append(sb, args, locale);
-               }
-               return sb.toString();
-       }
-
-       /**
-        * Parses the pattern into a list of tokens.
-        */
-       private static List<Token> parseTokens(String pattern) {
-               var tokens = new ArrayList<Token>();
-               var length = pattern.length();
-               var i = 0;
-               var sequentialIndex = 0; // 0-based index for sequential 
placeholders
-
-               // Possible String.format variable formats:
-               // %[argument_index$][flags][width][.precision]conversion
-               // %[argument_index$][flags][width]conversion
-               // %[flags][width]conversion
-
-               // Possible MessageFormat variable formats:
-               // {}
-               // {#,formatType}
-               // {#,formatType,formatStyle}
-
-               // S1 - In literal, looking for %, {, or '
-               // S2 - Found %, looking for conversion char or t or T
-               // S3 - Found {, looking for }
-               // S4 - Found ', in quoted section (MessageFormat single quotes 
escape special chars), looking for '
-               var state = S1;
-
-               var nestedBracketDepth = 0;
-
-               var mark = 0;
-               while (i < length) {
-                       var ch = pattern.charAt(i++);
-
-                       if (state == S1) {
-                               if (ch == '%') {
-                                       lit(tokens, pattern, mark, i - 1);
-                                       state = S2;
-                                       mark = i;
-                               } else if (ch == '{') {
-                                       lit(tokens, pattern, mark, i - 1);
-                                       state = S3;
-                                       mark = i - 1;
-                               } else if (ch == '\'') {
-                                       lit(tokens, pattern, mark, i - 1);
-                                       state = S4;
-                                       mark = i;
-                               }
-                       } else if (state == S2) {
-                               if (ch == '%') {
-                                       tokens.add(new LiteralToken("%"));
-                                       state = S1;
-                                       mark = i;
-                               } else if (ch == 't' || ch == 'T') {
-                                       // Do nothing.  Part of 2-character 
time conversion.
-                               } else if 
(PRINTF_CONVERSION_CHARS.contains(ch)) {
-                                       sf(tokens, pattern, mark, i, 
sequentialIndex++);
-                                       state = S1;
-                                       mark = i;
-                               } else if (PRINTF_FORMAT_CHARS.contains(ch) || 
Character.isDigit(ch)) {
-                                       // Do nothing.
-                               } else {
-                                       // Unknown character - could be invalid 
conversion or end of format
-                                       // Create StringFormatToken and let 
String.format() validate it
-                                       // This allows String.format() to throw 
IllegalFormatException for invalid conversions like %F
-                                       // printfStart is position after '%', 
so substring from printfStart-1 (the '%') to i (after the char)
-                                       sf(tokens, pattern, mark, i, 
sequentialIndex++);
-                                       state = S1;
-                                       mark = i;
-                               }
-                       } else if (state == S3) {
-                               if (ch == '{') {
-                                       nestedBracketDepth++;
-                               } else if (ch == '}') {
-                                       if (nestedBracketDepth > 0) {
-                                               nestedBracketDepth--;
-                                       } else {
-                                               mf(tokens, pattern, mark + 1, i 
- 1, sequentialIndex++);
-                                               state = S1;
-                                               mark = i;
-                                       }
-                               }
-                       } else /* if (state == S4) */ {
-                               if (ch == '\'') {
-                                       if (mark == i - 1) {
-                                               lit(tokens, pattern, mark, i);  
// '' becomes '
-                                               state = S1;
-                                               mark = i;
-                                       } else {
-                                               lit(tokens, pattern, mark, i - 
1);
-                                               state = S1;
-                                               mark = i;
-                                       }
-                               }
-                       }
-               }
-
-               // Process remaining content based on final state
-               if (state == S1) {
-                       lit(tokens, pattern, mark);
-               } else if (state == S2) {
-                       // Dangling '%' without conversion - throw exception to 
match String.format() behavior
-                       // UnknownFormatConversionException constructor takes 
just the conversion character
-                       throw new 
java.util.UnknownFormatConversionException("%");
-               } else if (state == S3) {
-                       // Unmatched '{' - throw exception to match 
MessageFormat behavior
-                       throw new IllegalArgumentException("Unmatched braces in 
the pattern.");
-               } else /* if (state == S4) */ {
-                       // Unmatched quote - MessageFormat treats it as ending 
the quoted section
-                       // Add the quoted content as literal (from mark to end 
of pattern)
-                       lit(tokens, pattern, mark);
-               }
-
-               return tokens;
-       }
-
-       private static void lit(List<Token> tokens, String pattern, int start, 
int end) {
-               if (start == end)
-                       return;
-               tokens.add(new LiteralToken(pattern.substring(start, end)));
-       }
-
-       private static void lit(List<Token> tokens, String pattern, int start) {
-               if (start == pattern.length())
-                       return;
-               tokens.add(new LiteralToken(pattern.substring(start)));
-       }
-
-       private static void sf(List<Token> tokens, String pattern, int start, 
int end, int index) {
-               tokens.add(new StringFormatToken(pattern.substring(start, end), 
index));
-       }
-
-       private static void mf(List<Token> tokens, String pattern, int start, 
int end, int index) {
-               tokens.add(new MessageFormatToken(pattern.substring(start, 
end), index));
-       }
-
-       private static int parseIndexMF(String s) {
-               try {
-                       return Integer.parseInt(s.trim());
-               } catch (@SuppressWarnings("unused") NumberFormatException e) {
-                       throw new IllegalArgumentException("can't parse 
argument number: " + s);
-               }
-       }
-
-       private static int parseIndexSF(String s) {
-               return Integer.parseInt(s.trim());
-       }
-
-       @Override
-       public String toString() {
-               return pattern;
-       }
-
-       /**
-        * Returns a debug representation of the parsed pattern showing the 
token structure.
-        *
-        * <p>
-        * This method is useful for debugging and understanding how a pattern 
was parsed.
-        * It returns a string showing each token in the format:
-        * <ul>
-        *   <li><b>Literal tokens:</b> <js>"[L:text]"</js> - Literal text</li>
-        *   <li><b>MessageFormat tokens (simple):</b> <js>"[M:s0]"</js> - 
Simple MessageFormat placeholder (format='s', index=0)</li>
-        *   <li><b>MessageFormat tokens (complex):</b> 
<js>"[M:o0:{0,number,currency}]"</js> - Complex MessageFormat placeholder 
(format='o', index=0, content)</li>
-        *   <li><b>StringFormat tokens (simple):</b> <js>"[S:s0:%s]"</js> - 
Simple printf placeholder (format='s', index=0, content)</li>
-        *   <li><b>StringFormat tokens (complex):</b> <js>"[S:z0:%.2f]"</js> - 
Complex printf placeholder (format='z', index=0, content)</li>
-        * </ul>
-        *
-        * <h5 class='section'>Token Format:</h5>
-        * <ul>
-        *   <li><b>L</b> = Literal token</li>
-        *   <li><b>M</b> = MessageFormat token</li>
-        *   <li><b>S</b> = StringFormat (printf) token</li>
-        *   <li><b>Format character:</b> 's' = simple, 'o' = other/complex, 
'z' = complex printf</li>
-        *   <li><b>Index:</b> 0-based argument index</li>
-        *   <li><b>Content:</b> The format string content (for complex 
tokens)</li>
-        * </ul>
-        *
-        * <h5 class='section'>Examples:</h5>
-        * <p class='bjava'>
-        *      FormatString <jv>fmt</jv> = 
FormatString.<jsm>of</jsm>(<js>"Hello {0}, you have %d items"</js>);
-        *      <jv>fmt</jv>.<jsm>toPattern</jsm>();
-        *      <jc>// Returns: "[L:Hello ][M:s0][L:, you have ][S:d1:%d][L: 
items]"</jc>
-        * </p>
-        *
-        * @return A debug string showing the parsed token structure.
-        */
-       public String toPattern() {
-               return 
Arrays.stream(tokens).map(Object::toString).collect(Collectors.joining());
-       }
-
-       @Override
-       public boolean equals(Object o) {
-               return o instanceof FormatString o2 && eq(this, o2, (x, y) -> 
eq(x.pattern, y.pattern));
-       }
-
-       @Override
-       public int hashCode() {
-               return pattern.hashCode();
-       }
-}
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringFormat.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringFormat.java
index 145d8e6a58..9cefec0cd9 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringFormat.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringFormat.java
@@ -114,7 +114,7 @@ public final class StringFormat {
         *
         * <p>
         * This is a convenience method that creates a StringFormat instance 
and formats it.
-        * If the pattern contains no format specifiers (no '%', '{', or '''), 
the pattern is returned as-is.
+        * If no arguments are passed in, the pattern is simply returned as-is.
         *
         * @param pattern The format pattern.
         * @param args The arguments to format.
@@ -122,7 +122,7 @@ public final class StringFormat {
         * @throws IllegalArgumentException If the pattern is <jk>null</jk> or 
format specifiers are invalid.
         */
        public static String format(String pattern, Object...args) {
-               if (!hasArgs(pattern))
+               if (args.length == 0)
                        return pattern;
                return of(pattern).format(args);
        }
@@ -132,7 +132,7 @@ public final class StringFormat {
         *
         * <p>
         * This is a convenience method that creates a StringFormat instance 
and formats it.
-        * If the pattern contains no format specifiers (no '%', '{', or '''), 
the pattern is returned as-is.
+        * If no arguments are passed in, the pattern is returned as-is.
         *
         * @param pattern The format pattern.
         * @param locale The locale to use for formatting. If <jk>null</jk>, 
uses the default locale.
@@ -141,7 +141,7 @@ public final class StringFormat {
         * @throws IllegalArgumentException If the pattern is <jk>null</jk> or 
format specifiers are invalid.
         */
        public static String format(String pattern, Locale locale, 
Object...args) {
-               if (!hasArgs(pattern))
+               if (args.length == 0)
                        return pattern;
                return of(pattern).format(locale, args);
        }
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/ThrowableUtils.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/ThrowableUtils.java
index 482949cb9f..6b6a492f00 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/ThrowableUtils.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/ThrowableUtils.java
@@ -138,7 +138,7 @@ public class ThrowableUtils {
         * @return A new IllegalArgumentException with the formatted message.
         */
        public static IllegalArgumentException illegalArg(String msg, 
Object...args) {
-               return new IllegalArgumentException(args.length == 0 ? msg : 
f(msg, args));
+               return new IllegalArgumentException(f(msg, args));
        }
 
        /**
@@ -160,7 +160,7 @@ public class ThrowableUtils {
         * @return A new IllegalArgumentException with the formatted message 
and cause.
         */
        public static IllegalArgumentException illegalArg(Throwable cause, 
String msg, Object...args) {
-               return new IllegalArgumentException(args.length == 0 ? msg : 
f(msg, args), cause);
+               return new IllegalArgumentException(f(msg, args), cause);
        }
 
        /**
@@ -171,7 +171,7 @@ public class ThrowableUtils {
         * @return A new IOException with the formatted message.
         */
        public static java.io.IOException ioex(String msg, Object...args) {
-               return new java.io.IOException(args.length == 0 ? msg : f(msg, 
args));
+               return new java.io.IOException(f(msg, args));
        }
 
        /**
@@ -193,7 +193,7 @@ public class ThrowableUtils {
         * @return A new IOException with the formatted message and cause.
         */
        public static java.io.IOException ioex(Throwable cause, String msg, 
Object...args) {
-               return new java.io.IOException(args.length == 0 ? msg : f(msg, 
args), cause);
+               return new java.io.IOException(f(msg, args), cause);
        }
 
        /**
@@ -214,7 +214,7 @@ public class ThrowableUtils {
         * @return A new RuntimeException with the formatted message.
         */
        public static RuntimeException rex(String msg, Object...args) {
-               return new RuntimeException(args.length == 0 ? msg : f(msg, 
args));
+               return new RuntimeException(f(msg, args));
        }
 
        /**
@@ -294,7 +294,7 @@ public class ThrowableUtils {
         * @return A new RuntimeException with the formatted message and cause.
         */
        public static BeanRuntimeException bex(Throwable cause, String msg, 
Object...args) {
-               return new BeanRuntimeException(args.length == 0 ? msg : f(msg, 
args), cause);
+               return new BeanRuntimeException(f(msg, args), cause);
        }
 
        /**
@@ -324,7 +324,7 @@ public class ThrowableUtils {
         * @return A new UnsupportedOperationException with the formatted 
message.
         */
        public static UnsupportedOperationException unsupportedOp(String msg, 
Object...args) {
-               return new UnsupportedOperationException(args.length == 0 ? msg 
: f(msg, args));
+               return new UnsupportedOperationException(f(msg, args));
        }
 
        /**
@@ -346,7 +346,7 @@ public class ThrowableUtils {
         * @return A new UnsupportedOperationException with the formatted 
message and cause.
         */
        public static UnsupportedOperationException unsupportedOp(Throwable 
cause, String msg, Object...args) {
-               return new UnsupportedOperationException(args.length == 0 ? msg 
: f(msg, args), cause);
+               return new UnsupportedOperationException(f(msg, args), cause);
        }
 
        /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicException.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicException.java
index ee7c55e3c8..662f8b7763 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicException.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicException.java
@@ -18,6 +18,7 @@ package org.apache.juneau;
 
 import static org.apache.juneau.common.utils.StringUtils.*;
 import static org.apache.juneau.common.utils.ThrowableUtils.*;
+import static org.apache.juneau.common.utils.Utils.*;
 
 import java.text.*;
 
@@ -38,7 +39,7 @@ public abstract class BasicException extends Exception {
         * @param args Optional {@link MessageFormat}-style arguments.
         */
        public BasicException(String message, Object...args) {
-               super(mformat(message, args));
+               super(f(message, args));
        }
 
        /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicRuntimeException.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicRuntimeException.java
index 72e10564f4..4ed62867f4 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicRuntimeException.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicRuntimeException.java
@@ -16,7 +16,6 @@
  */
 package org.apache.juneau;
 
-import static org.apache.juneau.common.utils.StringUtils.*;
 import static org.apache.juneau.common.utils.ThrowableUtils.*;
 import static org.apache.juneau.common.utils.Utils.*;
 
@@ -40,7 +39,7 @@ public class BasicRuntimeException extends RuntimeException {
         * @param args Optional {@link MessageFormat}-style arguments.
         */
        public BasicRuntimeException(String message, Object...args) {
-               super(mformat(message, args));
+               super(f(message, args));
        }
 
        /**
@@ -60,7 +59,7 @@ public class BasicRuntimeException extends RuntimeException {
         * @param args Optional {@link MessageFormat}-style arguments.
         */
        public BasicRuntimeException(Throwable cause, String message, 
Object...args) {
-               super(mformat(message, args), cause);
+               super(f(message, args), cause);
        }
 
        @Override /* Overridden from Throwable */
@@ -112,7 +111,7 @@ public class BasicRuntimeException extends RuntimeException 
{
         */
        public BasicRuntimeException setMessage(String message, Object...args) {
                assertModifiable();
-               this.message = mformat(message, args);
+               this.message = f(message, args);
                return this;
        }
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
index 1218f6ee22..e35e0ee03a 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
@@ -309,7 +309,7 @@ public class BeanSession extends ContextSession {
        @Override
        public void addWarning(String msg, Object...args) {
                if (isDebug())
-                       LOG.log(Level.WARNING, () -> args.length == 0 ? msg : 
MessageFormat.format(msg, args));
+                       LOG.log(Level.WARNING, () -> args.length == 0 ? msg : 
f(msg, args));
                super.addWarning(msg, args);
        }
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextSession.java
index d24cca367d..fd4a9aabfc 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextSession.java
@@ -189,7 +189,7 @@ public abstract class ContextSession {
                        return;
                if (warnings == null)
                        warnings = new LinkedList<>();
-               warnings.add((warnings.size() + 1) + ": " + mformat(msg, args));
+               warnings.add((warnings.size() + 1) + ": " + f(msg, args));
        }
 
        /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/Messages.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/Messages.java
index 1bb254649e..cf18850fd4 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/Messages.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/Messages.java
@@ -441,7 +441,7 @@ public class Messages extends ResourceBundle {
                var s = getString(key);
                if (s.startsWith("{!"))
                        return s;
-               return mformat(s, args);
+               return f(s, args);
        }
 
        @Override /* Overridden from ResourceBundle */
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParseException.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParseException.java
index 9f9ffc280f..4fe799fdaf 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParseException.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParseException.java
@@ -60,7 +60,7 @@ public class ParseException extends BasicRuntimeException {
 
        private static String getMessage(ParserSession session, String msg, 
Object...args) {
                if (args.length != 0)
-                       msg = mformat(msg, args);
+                       msg = f(msg, args);
 
                if (nn(session)) {
                        Position p = session.getPosition();
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserListener.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserListener.java
index b77f267701..fd32671ace 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserListener.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserListener.java
@@ -18,6 +18,7 @@ package org.apache.juneau.parser;
 
 import static org.apache.juneau.common.utils.StringUtils.*;
 import static org.apache.juneau.common.utils.ThrowableUtils.*;
+import static org.apache.juneau.common.utils.Utils.*;
 
 import org.apache.juneau.*;
 
@@ -44,7 +45,7 @@ public class ParserListener {
         * @param p The bean property we had an issue on.
         */
        public void onBeanSetterException(ParserSession session, Throwable t, 
BeanPropertyMeta p) {
-               onError(session, t, mformat("Could not call setValue() on 
property ''{0}'' of class ''{1}'', exception = {2}", p.getName(), 
p.getBeanMeta().getClassMeta(), lm(t)));
+               onError(session, t, f("Could not call setValue() on property 
''{0}'' of class ''{1}'', exception = {2}", p.getName(), 
p.getBeanMeta().getClassMeta(), lm(t)));
        }
 
        /**
@@ -72,6 +73,6 @@ public class ParserListener {
         * @param bean The bean.
         */
        public <T> void onUnknownBeanProperty(ParserSession session, String 
propertyName, Class<T> beanClass, T bean) {
-               onError(session, null, mformat("Unknown property ''{0}'' 
encountered while trying to parse into class ''{1}'' at location {2}", 
propertyName, beanClass, session.getPosition()));
+               onError(session, null, f("Unknown property ''{0}'' encountered 
while trying to parse into class ''{1}'' at location {2}", propertyName, 
beanClass, session.getPosition()));
        }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializeException.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializeException.java
index c62da17c53..fe5f51a2bc 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializeException.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializeException.java
@@ -60,7 +60,7 @@ public class SerializeException extends BasicRuntimeException 
{
        }
 
        private static String getMessage(SerializerSession session, String msg, 
Object...args) {
-               msg = mformat(msg, args);
+               msg = f(msg, args);
                if (nn(session)) {
                        Map<String,Object> m = session.getLastLocation();
                        if (nn(m) && ! m.isEmpty())
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerListener.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerListener.java
index 5cd8588cfd..f20825027c 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerListener.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerListener.java
@@ -18,6 +18,7 @@ package org.apache.juneau.serializer;
 
 import static org.apache.juneau.common.utils.StringUtils.*;
 import static org.apache.juneau.common.utils.ThrowableUtils.*;
+import static org.apache.juneau.common.utils.Utils.*;
 
 import org.apache.juneau.*;
 
@@ -44,7 +45,7 @@ public class SerializerListener {
         * @param p The bean property we had an issue on.
         */
        public void onBeanGetterException(SerializerSession session, Throwable 
t, BeanPropertyMeta p) {
-               onError(session, t, mformat("Could not call getValue() on 
property ''{0}'' of class ''{1}'', exception = {2}", p.getName(), 
p.getBeanMeta().getClassMeta(), lm(t)));
+               onError(session, t, f("Could not call getValue() on property 
''{0}'' of class ''{1}'', exception = {2}", p.getName(), 
p.getBeanMeta().getClassMeta(), lm(t)));
        }
 
        /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSession.java
index cf4f5667d3..4d5231ea30 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSession.java
@@ -980,7 +980,7 @@ public class SerializerSession extends BeanTraverseSession {
        @Override
        protected void onError(Throwable t, String msg, Object...args) {
                if (nn(listener))
-                       listener.onError(this, t, mformat(msg, args));
+                       listener.onError(this, t, f(msg, args));
                super.onError(t, msg, args);
        }
 
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 b261575b56..2117ced2e8 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
@@ -1155,7 +1155,7 @@ public class Microservice implements ConfigEventListener {
         * @param args Optional {@link MessageFormat}-style arguments.
         */
        protected void log(Level level, String message, Object...args) {
-               var msg = args.length == 0 ? message : 
MessageFormat.format(message, args);
+               var msg = args.length == 0 ? message : f(message, args);
                getLogger().log(level, msg);
        }
 
diff --git 
a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyLogger.java
 
b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyLogger.java
index 948e934c68..7e9b190725 100644
--- 
a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyLogger.java
+++ 
b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyLogger.java
@@ -57,23 +57,7 @@ public class JettyLogger implements LocationAwareLogger {
         * @return The formatted message string.
         */
        private static String format(String msg, Object...args) {
-               msg = String.valueOf(msg);
-               if (args.length == 0)
-                       return msg;
-               var sb = new StringBuilder();
-               int start = 0;
-               for (var arg : args) {
-                       int bi = msg.indexOf("{}", start);
-                       if (bi < 0) {
-                               sb.append(msg.substring(start)).append(" 
").append(arg);
-                               start = msg.length();
-                       } else {
-                               sb.append(msg.substring(start, 
bi)).append(String.valueOf(arg));
-                               start = bi + 2;
-                       }
-               }
-               sb.append(msg.substring(start));
-               return sb.toString();
+               return f(msg, args);
        }
 
        /**
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 68de91f3f9..21d24257ab 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
@@ -7537,10 +7537,6 @@ public class RestClient extends BeanContextable 
implements HttpClient, Closeable
                return request(op(method, uri, body));
        }
 
-       private static Supplier<String> msg(String msg, Object...args) {
-               return () -> args.length == 0 ? msg : MessageFormat.format(msg, 
args);
-       }
-
        private static RestOperation op(String method, Object url, Object body) 
{
                return RestOperation.of(method, url, body);
        }
@@ -7691,9 +7687,9 @@ public class RestClient extends BeanContextable 
implements HttpClient, Closeable
         * @param args The arguments.
         */
        protected void log(Level level, String msg, Object...args) {
-               logger.log(level, msg(msg, args));
+               logger.log(level, f(msg, args));
                if (logToConsole)
-                       console.println(msg(msg, args).get());
+                       console.println(f(msg, args));
        }
 
        /**
@@ -7705,9 +7701,9 @@ public class RestClient extends BeanContextable 
implements HttpClient, Closeable
         * @param args Optional message arguments.
         */
        protected void log(Level level, Throwable t, String msg, Object...args) 
{
-               logger.log(level, t, msg(msg, args));
+               logger.log(level, t, fs(msg, args));
                if (logToConsole) {
-                       console.println(msg(msg, args).get());
+                       console.println(f(msg, args));
                        if (nn(t))
                                t.printStackTrace(console);
                }
diff --git 
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
 
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
index 57af897cf8..2b4ba16015 100644
--- 
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
+++ 
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
@@ -76,7 +76,7 @@ public class MockServletRequest implements HttpServletRequest 
{
         * @return A new request.
         */
        public static MockServletRequest create(String method, String uri, 
Object...pathArgs) {
-               return create().method(method).uri(mformat(uri, pathArgs));
+               return create().method(method).uri(f(uri, pathArgs));
        }
 
        private String method = "GET";
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/ArgException.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/ArgException.java
index 108ed3ee6f..12244c265a 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/ArgException.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/ArgException.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.rest.arg;
 
 import static org.apache.juneau.common.utils.StringUtils.*;
+import static org.apache.juneau.common.utils.Utils.*;
 
 import java.util.*;
 
@@ -46,7 +47,7 @@ public class ArgException extends InternalServerError {
         * @param args The message args.
         */
        public ArgException(ParameterInfo pi, String msg, Object...args) {
-               super(mformat(msg, args) + " on parameter " + pi.getIndex() + " 
of method " + pi.getMethod().getFullName() + ".");
+               super(f(msg, args) + " on parameter " + pi.getIndex() + " of 
method " + pi.getMethod().getFullName() + ".");
        }
 
        /**
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestObject.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestObject.java
index d9b42a2080..c2742a7334 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestObject.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestObject.java
@@ -70,7 +70,7 @@ public abstract class RestObject {
         * @param args Optional {@link MessageFormat}-style arguments.
         */
        public void log(Level level, String msg, Object...args) {
-               doLog(level, null, () -> mformat(msg, args));
+               doLog(level, null, fs(msg, args));
        }
 
        /**
@@ -85,7 +85,7 @@ public abstract class RestObject {
         * @param args Optional {@link MessageFormat}-style arguments.
         */
        public void log(Level level, Throwable cause, String msg, 
Object...args) {
-               doLog(level, cause, () -> mformat(msg, args));
+               doLog(level, cause, fs(msg, args));
        }
 
        /**
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestServlet.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestServlet.java
index bbeabfb3c9..2ba034f2a0 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestServlet.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestServlet.java
@@ -169,7 +169,7 @@ public abstract class RestServlet extends HttpServlet {
         * @param args Optional {@link MessageFormat}-style arguments.
         */
        public void log(Level level, String msg, Object...args) {
-               doLog(level, null, () -> StringUtils.mformat(msg, args));
+               doLog(level, null, fs(msg, args));
        }
 
        /**
@@ -184,7 +184,7 @@ public abstract class RestServlet extends HttpServlet {
         * @param args Optional {@link MessageFormat}-style arguments.
         */
        public void log(Level level, Throwable cause, String msg, 
Object...args) {
-               doLog(level, cause, () -> StringUtils.mformat(msg, args));
+               doLog(level, cause, fs(msg, args));
        }
 
        /**
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/SwaggerException.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/SwaggerException.java
index 35ff5feade..5bbed92ff1 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/SwaggerException.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/SwaggerException.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.rest.swagger;
 
 import static org.apache.juneau.common.utils.StringUtils.*;
+import static org.apache.juneau.common.utils.Utils.*;
 
 import org.apache.juneau.parser.*;
 
@@ -24,6 +25,6 @@ class SwaggerException extends ParseException {
        private static final long serialVersionUID = 1L;
 
        SwaggerException(Exception e, String location, Object...locationArgs) {
-               super(e, "Swagger exception:  at " + mformat(location, 
locationArgs));
+               super(e, "Swagger exception:  at " + f(location, locationArgs));
        }
 }
\ No newline at end of file
diff --git a/juneau-utest/src/test/java/org/apache/juneau/ComboInput.java 
b/juneau-utest/src/test/java/org/apache/juneau/ComboInput.java
index 68a1de1da6..10a4fa4952 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/ComboInput.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/ComboInput.java
@@ -75,7 +75,7 @@ public class ComboInput<T> {
        }
 
        public ComboInput<T> verify(Predicate<T> verify, String msg, 
Object...args) {
-               this.verify.add(x -> verify.test(x) ? null : mformat(msg, 
args));
+               this.verify.add(x -> verify.test(x) ? null : f(msg, args));
                return this;
        }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjects_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjects_Test.java
index 4b96ef8f14..7b52a59294 100755
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjects_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjects_Test.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.a.rttests;
 
 import static org.apache.juneau.common.utils.StringUtils.*;
+import static org.apache.juneau.common.utils.Utils.*;
 
 import java.util.*;
 
@@ -111,7 +112,7 @@ class RoundTripLargeObjects_Test extends TestBase {
 
                // Initialization run.
                r = s.serialize(a);
-               System.err.println(mformat("Serialized size: {0,number} ", (r 
instanceof String ? r.toString().length() : ((byte[])r).length))); // NOT DEBUG
+               System.err.println(f("Serialized size: {0,number} ", (r 
instanceof String ? r.toString().length() : ((byte[])r).length))); // NOT DEBUG
                p.parse(r, A.class);
 
                startTime = System.currentTimeMillis();
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/utils/FormatString_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/utils/FormatString_Test.java
deleted file mode 100644
index a8a4445544..0000000000
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/utils/FormatString_Test.java
+++ /dev/null
@@ -1,416 +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.common.utils;
-
-import static org.apache.juneau.common.utils.Utils.*;
-import static org.junit.jupiter.api.Assertions.*;
-import static java.util.stream.Collectors.*;
-
-import java.math.*;
-import java.text.*;
-import java.util.*;
-import java.util.stream.*;
-
-import org.apache.juneau.*;
-import org.apache.juneau.common.function.*;
-import org.junit.jupiter.api.*;
-
-class FormatString_Test extends TestBase {
-
-       private static StringFormat fs(String pattern) {
-               return StringFormat.of(pattern);
-       }
-
-       private static String stringify(ThrowingSupplier<String> supplier) {
-               try {
-                       return supplier.get();
-               } catch (Throwable t) {
-                       return t.getClass().getSimpleName() + ": " + 
t.getLocalizedMessage();
-               }
-       }
-
-       private static void assertStringFormat(String pattern, Locale locale, 
Object... args) {
-               var expected = stringify(()->String.format(locale, pattern, 
args));
-               var actual = "";
-               var fmt = (StringFormat)null;
-               try {
-                       var fmt2 = fs(pattern);
-                       fmt = fmt2;
-                       actual = stringify(()->fmt2.format(locale, args));
-               } catch (Throwable t) {
-                       actual = t.getClass().getSimpleName() + ": " + 
t.getLocalizedMessage();
-               }
-               if (!expected.equals(actual)) {
-                       System.out.println("Pattern: " + pattern);
-                       var toPattern = opt(fmt).map(x -> 
x.toPattern()).orElse(null);
-                       System.out.println("toPattern(): " + toPattern);
-                       fail("Pattern: " + pattern + ", toPattern(): " + 
toPattern + ", expected: <" + expected + "> but was: <" + actual + ">");
-               }
-       }
-
-       private static void assertStringFormat(String pattern, Object... args) {
-               var expected = stringify(()->String.format(pattern, args));
-               var actual = "";
-               var fmt = (StringFormat)null;
-               try {
-                       var fmt2 = fs(pattern);
-                       fmt = fmt2;
-                       actual = stringify(()->fmt2.format(args));
-               } catch (Throwable t) {
-                       actual = t.getClass().getSimpleName() + ": " + 
t.getLocalizedMessage();
-               }
-               if (!expected.equals(actual)) {
-                       System.out.println("Pattern: " + pattern);
-                       var toPattern = opt(fmt).map(x -> 
x.toPattern()).orElse(null);
-                       System.out.println("toPattern(): " + toPattern);
-                       fail("Pattern: " + pattern + ", toPattern(): " + 
toPattern + ", expected: <" + expected + "> but was: <" + actual + ">");
-               }
-       }
-
-       private static void assertMessageFormat(String pattern, Locale locale, 
Object... args) {
-               var expected = stringify(()->new MessageFormat(pattern, 
locale).format(args));
-               var actual = "";
-               var fmt = (StringFormat)null;
-               try {
-                       var fmt2 = fs(pattern);
-                       fmt = fmt2;
-                       actual = stringify(()->fmt2.format(locale, args));
-               } catch (Throwable t) {
-                       actual = t.getClass().getSimpleName() + ": " + 
t.getLocalizedMessage();
-               }
-               if (!expected.equals(actual)) {
-                       System.out.println("Pattern: " + pattern);
-                       var toPattern = opt(fmt).map(x -> 
x.toPattern()).orElse(null);
-                       System.out.println("toPattern(): " + toPattern);
-                       fail("Pattern: " + pattern + ", toPattern(): " + 
toPattern + ", expected: <" + expected + "> but was: <" + actual + ">");
-               }
-       }
-
-       private static void assertMessageFormat(String pattern, Object... args) 
{
-               var expected = stringify(()->MessageFormat.format(pattern, 
args));
-               var actual = "";
-               var fmt = (StringFormat)null;
-               try {
-                       var fmt2 = fs(pattern);
-                       fmt = fmt2;
-                       actual = stringify(()->fmt2.format(args));
-               } catch (Throwable t) {
-                       actual = t.getClass().getSimpleName() + ": " + 
t.getLocalizedMessage();
-               }
-               if (!expected.equals(actual)) {
-                       System.out.println("Pattern: " + pattern);
-                       var toPattern = opt(fmt).map(x -> 
x.toPattern()).orElse(null);
-                       System.out.println("toPattern(): " + toPattern);
-                       fail("Pattern: " + pattern + ", toPattern(): " + 
toPattern + ", expected: <" + expected + "> but was: <" + actual + ">");
-               }
-       }
-
-       private static void assertMixedFormat(String expected, String pattern, 
Locale locale, Object... args) {
-               var actual = "";
-               var fmt = (StringFormat)null;
-               try {
-                       var fmt2 = fs(pattern);
-                       fmt = fmt2;
-                       actual = stringify(()->fmt2.format(locale, args));
-               } catch (Throwable t) {
-                       actual = t.getClass().getSimpleName() + ": " + 
t.getLocalizedMessage();
-               }
-               if (!expected.equals(actual)) {
-                       System.out.println("Pattern: " + pattern);
-                       var toPattern = opt(fmt).map(x -> 
x.toPattern()).orElse(null);
-                       System.out.println("toPattern(): " + toPattern);
-                       fail("Pattern: " + pattern + ", toPattern(): " + 
toPattern + ", expected: <" + expected + "> but was: <" + actual + ">");
-               }
-       }
-
-       private static void assertMixedFormat(String expected, String pattern, 
Object... args) {
-               var actual = "";
-               var fmt = (StringFormat)null;
-               try {
-                       var fmt2 = fs(pattern);
-                       fmt = fmt2;
-                       actual = stringify(()->fmt2.format(args));
-               } catch (Throwable t) {
-                       actual = t.getClass().getSimpleName() + ": " + 
t.getLocalizedMessage();
-               }
-               if (!expected.equals(actual)) {
-                       System.out.println("Pattern: " + pattern);
-                       var toPattern = opt(fmt).map(x -> 
x.toPattern()).orElse(null);
-                       System.out.println("toPattern(): " + toPattern);
-                       fail("Pattern: " + pattern + ", toPattern(): " + 
toPattern + ", expected: <" + expected + "> but was: <" + actual + ">");
-               }
-       }
-
-       
//====================================================================================================
-       // MessageFormat tests
-       
//====================================================================================================
-       @Test void a01_messageFormat() {
-               assertMessageFormat("Hello {0}", "John");
-               assertMessageFormat("Price: {0,number,currency}", 19.99);
-               assertMessageFormat("{0} has {1} items and {2} friends", 
"John", 5, 3);
-               assertMessageFormat("Hello {0} world", "John");
-               assertMessageFormat("Count: {0,number,integer}", 1234);
-               assertMessageFormat("Date: {0,date,short}", new Date(0));
-               assertMessageFormat("Time: {0,time,short}", new Date(0));
-               // Simple {0} with Date - uses DATE_FORMAT_CACHE for formatting
-               assertMessageFormat("Date: {0}", new Date(0));
-               assertMessageFormat("Value: {0}", (String)null);
-               assertMessageFormat("Name: {0}", "");
-               assertMessageFormat("Text: {0}\nNewline\tTab", "Hello");
-               assertMessageFormat("Unicode: {0} 中文", "Test");
-               assertMessageFormat("{0}{1}", "A", "B");
-               assertMessageFormat("{0} and {0} again", "Hello");
-               assertMessageFormat("Price: {0,number,currency}, Count: 
{1,number,integer}, Date: {2,date,short}", 19.99, 42, new java.util.Date());
-               assertMessageFormat("Price: {0,number,currency}", Locale.US, 
19.99);
-               assertMessageFormat("Price: {0,number,currency}", 
Locale.FRANCE, 19.99);
-               assertMessageFormat("a '{0}' b");
-               assertMessageFormat("a ''{0}'' b", 1);
-               assertMessageFormat("'{0}'");
-               assertMessageFormat("''{0}''", 1);
-
-               // Errors
-               assertMessageFormat("Set: {{0}}", 50);
-               assertMessageFormat("Set: {{0}} and {{1}}", "A", "B");
-               assertMessageFormat("Hello {0}");
-               assertMessageFormat("{0} has {1} items and {2} friends", 
"John", 5);
-               assertMessageFormat("Hello {");
-               assertMessageFormat("Hello {0");
-               assertMessageFormat("Hello '");
-               assertMessageFormat("Hello 'x");
-       }
-
-       
//====================================================================================================
-       // StringFormat (printf) tests
-       
//====================================================================================================
-       @Test void a02_stringFormat() {
-               assertStringFormat("Hello %s", "John");
-               assertStringFormat("Price: $%.2f", 19.99);
-               assertStringFormat("Name: %-10s Age: %3d", "John", 25);
-               assertStringFormat("Color: #%06X", 0xFF5733);
-               assertStringFormat("Hello world");
-               assertStringFormat("Progress: %d%%", 50);
-               assertStringFormat("");
-               assertStringFormat("%1$s loves %2$s, and %1$s also loves %3$s", 
"Alice", "Bob", "Charlie");
-               assertStringFormat("Hello %1$s", "John");
-               assertStringFormat("Price: %1$.2f", 19.99);
-               assertStringFormat("Octal: %o", 64);
-               assertStringFormat("Octal: %o", 255);
-               assertStringFormat("Octal: %o", (Number)null);
-               assertStringFormat("Flag: %b", true);
-               assertStringFormat("Flag: %b", false);
-               assertStringFormat("Flag: %b", (Boolean)null);
-               // %B uppercase boolean formatting
-               assertStringFormat("Flag: %B", true);
-               assertStringFormat("Flag: %B", false);
-               assertStringFormat("Flag: %B", (Boolean)null);  // Line 281: 
null -> "FALSE"
-               assertStringFormat("Flag: %B", "hello");  // Line 285: 
non-Boolean -> "TRUE"
-               assertStringFormat("Flag: %B", 42);  // Line 285: non-Boolean 
-> "TRUE"
-               assertStringFormat("Char: %c", 'A');
-               assertStringFormat("Char: %c", "A");
-               assertStringFormat("Char: %c", (String)null);
-               assertStringFormat("Value: %.2e", 1234567.0);
-               assertStringFormat("Value: %.2e", (Number)null);
-               assertStringFormat("Number: %+10.2f", 19.99);
-               assertStringFormat("ID: %05d", 42);
-               assertStringFormat("Value: %d", (Number)null);  // Line 304: 
null -> "null"
-               assertStringFormat("Value: %s", (String)null);
-               assertStringFormat("Name: %s", "");
-               assertStringFormat("%s%s", "A", "B");
-               assertStringFormat("Progress: %d%% Complete: %d%%", 50, 75);
-               assertStringFormat("%1$s and %1$s again", "Hello");
-               assertStringFormat("Hex: 0x%08X, Decimal: %+d, Float: %10.3f", 
255, 42, 3.14159);
-               assertStringFormat("Price: %.2f", Locale.US, 19.99);
-               assertStringFormat("Price: %.2f", Locale.FRANCE, 19.99);
-               assertStringFormat("Value: %s", (Object)null);
-               assertStringFormat("Int: %d", 42);
-               assertStringFormat("Long: %d", 1234567890L);
-               assertStringFormat("Byte: %d", (byte)127);
-               assertStringFormat("Short: %d", (short)32767);
-               assertStringFormat("Int: %d", Locale.FRANCE, 1234);
-               assertStringFormat("Long: %d", Locale.GERMANY, 1234567L);
-               assertStringFormat("Value: %d", "not-a-number");
-               assertStringFormat("Hex: %x", 255);  // Line 328: Integer -> 
Integer.toHexString()
-               assertStringFormat("Hex: %x", 255L);
-               assertStringFormat("Hex: %x", (byte)255);
-               assertStringFormat("Hex: %x", (Number)null);  // Line 324: null 
-> "null"
-               assertStringFormat("Hex: %X", 255);
-               assertStringFormat("Hex: %X", 0xABCL);
-               assertStringFormat("Hex: %X", (short)255);
-               assertStringFormat("Hex: %X", (Number)null);  // Line 337: null 
-> "null"
-               assertStringFormat("Octal: %o", 255L);
-               assertStringFormat("Octal: %o", (byte)64);
-               assertStringFormat("Value: %b", "hello");
-               assertStringFormat("Value: %b", 42);
-               assertStringFormat("Char: %c", 65);
-               assertStringFormat("Char: %c", 65L);
-               assertStringFormat("Char: %c", "X");
-               assertStringFormat("Char: %C", (Character)null);  // Line 376: 
null -> "null"
-               assertStringFormat("Char: %C", 66);  // Line 382-383: Number 
(Integer) -> Character.toUpperCase((char)o2.intValue())
-               assertStringFormat("Char: %C", 66L);  // Line 382-383: Number 
(Long) -> Character.toUpperCase((char)o2.intValue())
-               assertStringFormat("Float: %f", 3.14f);
-               assertStringFormat("Double: %f", 3.14159);
-               assertStringFormat("Float: %f", Locale.FRANCE, 3.14f);
-               assertStringFormat("Double: %f", Locale.GERMANY, 1234.56);
-               assertStringFormat("Value: %f", (Number)null);  // Line 389: 
null -> "null"
-               assertStringFormat("Value: %f", "not-a-number");
-               assertStringFormat("Value: %.2e", 1234.56);
-               assertStringFormat("Value: %S", "hello");
-               assertStringFormat("Value: %S", (String)null);  // Line 297: 
null -> "null"
-               assertStringFormat("Value: %B", true);
-               assertStringFormat("Char: %C", 'a');
-               assertStringFormat("Float: %F", 3.14);
-
-               // Errors
-               assertStringFormat("Hello %s");
-               assertStringFormat("Hello %s and %s", "John");
-               assertStringFormat("Hello %");
-               assertStringFormat("Hello %s and %", "John");
-               assertStringFormat("Hello %x$s", "John");
-       }
-
-       
//====================================================================================================
-       // Mixed format tests
-       
//====================================================================================================
-       @Test void a03_mixedFormat() {
-               assertMixedFormat("Hello John, you have 5 items", "Hello {0}, 
you have %d items", "John", 5);
-               assertMixedFormat("User Alice has admin and 10 items", "User 
{0} has %s and {2} items", "Alice", "admin", 10);
-               assertMixedFormat("Alice loves Bob, and Alice also loves 
Charlie", "%1$s loves %2$s, and {0} also loves %3$s", "Alice", "Bob", 
"Charlie");
-               assertMixedFormat("Alice has 5 items, Bob has 3 items, total: 
8", "{0} has %d items, {2} has %d items, total: %d", "Alice", 5, "Bob", 3, 8);
-               assertMixedFormat("Alice Bob Charlie", "{0} %2$s {2}", "Alice", 
"Bob", "Charlie");
-               assertMixedFormat("Hello John, you have 5 items", "Hello {0}, 
you have %d items", "John", 5);
-               assertMixedFormat("A B B D C", "{0} %s {1} %s {2}", "A", "B", 
"C", "D");
-               assertMixedFormat("ABB", "{0}%s{1}", "A", "B", "C");
-               assertMixedFormat("Hello and Hello are the same", "{0} and %1$s 
are the same", "Hello");
-
-               // Errors
-               assertMixedFormat("MissingFormatArgumentException: Format 
specifier '%s'", "Hello {0} and %s", "John");
-               assertMixedFormat("John has 5 items and {2} friends", "{0} has 
%d items and {2} friends", "John", 5);
-               assertMixedFormat("MissingFormatArgumentException: Format 
specifier '%s'", "%1$s loves %2$s, and {0} also loves %3$s", "Alice", "Bob");
-       }
-
-       
//====================================================================================================
-       // Supported but deviates from MessageFormat/String.format
-       
//====================================================================================================
-       @Test void a04_supportedButDeviatesFromMessageFormat() {
-               // {} is not supported by MessageFormat, only by StringFormat 
as an extension
-               assertMixedFormat("Hello John world", "Hello {} world", "John");
-               assertMixedFormat("A B C", "{} {} {}", "A", "B", "C");
-               // BigDecimal with %d - String.format throws exception, but our 
optimized code handles it
-               assertMixedFormat("Number: 42", "Number: %d", new 
BigDecimal("42"));
-               // MessageFormat throws NullPointerException when locale is 
null, but StringFormat handles it
-               // So we test StringFormat's behavior directly instead of 
comparing with MessageFormat
-               assertMixedFormat("Price: $19.99", "Price: 
{0,number,currency}", (Locale)null, 19.99);
-       }
-
-       
//====================================================================================================
-       // Error handling
-       
//====================================================================================================
-       @Test void a05_errors() {
-               assertThrows(IllegalArgumentException.class, () -> new 
StringFormat(null));
-               assertThrows(IllegalArgumentException.class, () -> fs(null));
-       }
-
-       @Test void a06_caching() {
-               // Should return the same instance due to caching
-               assertSame(fs("Hello {0}"), fs("Hello {0}"));
-
-               // Different patterns should return different instances
-               assertNotSame(fs("Hello {0}"), fs("Hello %s"));
-
-               // Constructor doesn't use cache, so instances should be 
different
-               var fmt1 = new StringFormat("Hello {0}");
-               var fmt2 = new StringFormat("Hello {0}");
-               assertNotSame(fmt1, fmt2);
-               assertEquals(fmt1, fmt2); // But they should be equal
-       }
-
-       @Test void a07_equalsAndHashCode() {
-               var fmt1 = StringFormat.of("Hello {0}");
-               var fmt2 = StringFormat.of("Hello {0}");
-               var fmt3 = StringFormat.of("Hello %s");
-
-               assertEquals(fmt1, fmt2);
-               assertNotEquals(fmt1, fmt3);
-               assertEquals(fmt1.hashCode(), fmt2.hashCode());
-       }
-
-       @Test void a08_toString() {
-               assertEquals("Hello {0}", fs("Hello {0}").toString());
-       }
-
-       @Test void a09_toPattern() {
-               // Literal tokens
-               assertEquals("[L:Hello ]", fs("Hello ").toPattern());
-               assertEquals("[L:a ][L:{0}][L: b]", fs("a '{0}' 
b").toPattern());  // Single quotes don't escape MessageFormat
-
-               // MessageFormat tokens - simple (content == null) - Line 228: 
content == null branch
-               assertEquals("[L:Hello ][M:s0]", fs("Hello {0}").toPattern());
-               assertEquals("[L:Hello ][M:s0][L: ][M:s1]", fs("Hello {0} 
{1}").toPattern());
-
-               // MessageFormat tokens - complex (content != null) - Line 228: 
content != null branch
-               assertEquals("[L:Price: ][M:o0:{0,number,currency}]", 
fs("Price: {0,number,currency}").toPattern());
-               assertEquals("[L:Count: ][M:o0:{0,number,integer}]", fs("Count: 
{0,number,integer}").toPattern());
-               assertEquals("[L:Date: ][M:o0:{0,date,short}]", fs("Date: 
{0,date,short}").toPattern());
-
-               // StringFormat tokens - Line 406: StringFormatToken.toString()
-               assertEquals("[L:Hello ][S:s0:%s]", fs("Hello 
%s").toPattern());  // Simple format: 's'
-               assertEquals("[L:Number: ][S:d0:%d]", fs("Number: 
%d").toPattern());  // Simple format: 'd'
-               assertEquals("[L:Hex: ][S:x0:%x]", fs("Hex: %x").toPattern());  
// Simple format: 'x'
-               assertEquals("[L:Float: ][S:z0:%.2f]", fs("Float: 
%.2f").toPattern());  // Complex format: 'z' (other)
-               assertEquals("[L:ID: ][S:z0:%05d]", fs("ID: 
%05d").toPattern());  // Complex format: 'z' (other)
-
-               // Mixed formats
-               assertEquals("[L:Hello ][M:s0][L:, you have ][S:d1:%d][L: 
items]", fs("Hello {0}, you have %d items").toPattern());
-               assertEquals("[L:Price: ][M:o0:{0,number,currency}][L: and 
][S:s1:%s]", fs("Price: {0,number,currency} and %s").toPattern());
-
-               // Time conversions (2-character) - Line 529: 't' or 'T' 
handling
-               assertEquals("[L:Month: ][S:z0:%tm]", fs("Month: 
%tm").toPattern());  // %tm is 2-character time conversion
-               assertEquals("[L:Year: ][S:z0:%tY]", fs("Year: 
%tY").toPattern());  // %tY is 2-character time conversion
-               assertEquals("[L:Date: ][S:z0:%TD]", fs("Date: 
%TD").toPattern());  // %TD is 2-character time conversion
-       }
-
-       @Test void a10_veryLongPattern() {
-               var pattern = "Start: " + IntStream.range(0, 10).mapToObj(i -> 
"{" + i + "}").collect(joining(" ")) + " ";
-               var args = IntStream.range(0, 10).boxed().toArray();
-               assertMessageFormat(pattern, args);
-       }
-
-       @Test void a11_parseIndexErrors() {
-               assertThrows(IllegalArgumentException.class, () -> fs("Hello 
{abc}"));
-       }
-
-       @Test void a12_localeHandling() {
-               // Lines 259-260: Test locale null checks and default locale 
detection in StringFormatToken
-               // Line 259: var l = locale == null ? Locale.getDefault() : 
locale;
-               // Line 260: var dl = locale == null || 
locale.equals(Locale.getDefault());
-
-               // Test with null locale (covers locale == null on both lines)
-               assertStringFormat("Hello %s", (Locale)null, "John");
-               assertStringFormat("Number: %d", (Locale)null, 42);
-               assertStringFormat("Float: %.2f", (Locale)null, 3.14);  // Use 
.2f for consistent formatting
-
-               // Test with default locale (covers 
locale.equals(Locale.getDefault()) on line 260)
-               assertStringFormat("Hello %s", Locale.getDefault(), "John");
-               assertStringFormat("Number: %d", Locale.getDefault(), 42);
-               assertStringFormat("Float: %.2f", Locale.getDefault(), 3.14);  
// Use .2f for consistent formatting
-
-               // Test with non-default locale (covers else branch on line 259 
and false case on line 260)
-               assertStringFormat("Hello %s", Locale.FRANCE, "John");
-               assertStringFormat("Number: %d", Locale.GERMANY, 42);
-               assertStringFormat("Float: %.2f", Locale.JAPAN, 3.14);  // Use 
.2f for consistent formatting
-       }
-}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/utils/Utils_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/common/utils/Utils_Test.java
index 42438b8e5f..39ed92c678 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/common/utils/Utils_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/common/utils/Utils_Test.java
@@ -58,7 +58,7 @@ class Utils_Test extends TestBase {
 
                // Null handling
                assertEquals("Value: null", f("Value: %s", (String)null));
-               assertNull(f(null, "test"));
+               assertThrows(IllegalArgumentException.class, ()->f(null, 
"test"));
                assertEquals("test", f("test"));
        }
 
@@ -83,7 +83,7 @@ class Utils_Test extends TestBase {
 
                // Null handling
                var nullSupplier = fs(null, "test");
-               assertNull(nullSupplier.get());
+               assertThrows(IllegalArgumentException.class, 
()->nullSupplier.get());
 
                // Empty pattern
                var emptySupplier = fs("");
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Path_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Path_Test.java
index 6a119feb91..6df2ebe31b 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Path_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Path_Test.java
@@ -383,31 +383,28 @@ class Path_Test extends TestBase {
        public static class F  {
                @RestGet(path="/")
                public String a(RequestPathParams path) {
-                       return format("a: {0}", path.toString());
+                       return Utils.f("a: {0}", path.toString());
                }
                @RestGet(path="/*")
                public String b(RequestPathParams path) {
-                       return format("b: {0}", path.toString());
+                       return Utils.f("b: {0}", path.toString());
                }
                @RestGet(path="/fc")
                public String c(RequestPathParams path) {
-                       return format("c: {0}", path.toString());
+                       return Utils.f("c: {0}", path.toString());
                }
                @RestGet(path="/fd/{c}/{d}")
                public String d(RequestPathParams path) {
-                       return format("d: {0}", path.toString());
+                       return Utils.f("d: {0}", path.toString());
                }
                @RestGet(path="/fe/{a}/{b}")
                public String e(RequestPathParams path) {
-                       return format("e: {0}", path.toString());
+                       return Utils.f("e: {0}", path.toString());
                }
                @RestGet(path="/ff/{c}/{d}/*")
                public String f(RequestPathParams path) {
-                       return format("f: {0}", path.toString());
+                       return Utils.f("f: {0}", path.toString());
                }
-       private static String format(String msg, Object...args) {
-               return StringUtils.mformat(msg, args);
-       }
        }
 
        @Test void f01_pathVariablesOnClass() throws Exception {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_RestClient_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_RestClient_Test.java
index e7cfd068b2..dfc9ff237b 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_RestClient_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_RestClient_Test.java
@@ -316,7 +316,7 @@ class RestClient_Config_RestClient_Test extends TestBase {
                        super(builder);
                }
                @Override
-               public void log(Level level,String msg,Object...args) {
+               public void log(Level level,String msg, Object...args) {
                        lastMessage = msg;
                }
        }

Reply via email to