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

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


The following commit(s) were added to refs/heads/docs by this push:
     new 6657da9a07 Docs: 10.0.0 breaking-changes release note + 
placeholder-convention updates
6657da9a07 is described below

commit 6657da9a079dc0dceac1329e7d75c95416c82e4f
Author: James Bognar <[email protected]>
AuthorDate: Thu Jul 16 08:30:11 2026 -0400

    Docs: 10.0.0 breaking-changes release note + placeholder-convention updates
    
    Document the printf-only f()/fs() migration and mf()/mfs() split as a 10.0.0
    breaking change, and update the affected topic pages to reflect the new
    placeholder convention (%s printf vs {0} MessageFormat):
    
    - release-notes/10.0.0: add breaking-change entry.
    - topics: JuneauCommonsUtils, JuneauCommonsLang, JuneauCommonsLogging,
      JuneauBeanCommon, CustomErrorMessages, V10MigrationGuide.
    
    Co-authored-by: Cursor <[email protected]>
---
 pages/release-notes/10.0.0.md                |  6 +++++
 pages/topics/02.01.JuneauCommonsUtils.md     | 37 +++++++++++++++++-----------
 pages/topics/02.03.JuneauCommonsLang.md      |  9 +++++++
 pages/topics/02.15.JuneauCommonsLogging.md   |  2 +-
 pages/topics/05.05.JuneauBeanCommon.md       |  4 +--
 pages/topics/07.02.01.CustomErrorMessages.md | 22 ++++++++---------
 pages/topics/27.V10MigrationGuide.md         | 12 +++++++++
 7 files changed, 63 insertions(+), 29 deletions(-)

diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index d97dee2453..3f0c862c24 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -622,6 +622,12 @@ _TBD — to be filled in as development continues._
 
 - **Microservice demo resources moved out of the shipping jars into a new 
unpublished example module.** Seven low-value demo `@Rest` resources — 
`LogsResource`, `LogParser`, `DirectoryResource`, `ShutdownResource`, 
`SampleRootResource`, `ConfigResource` (formerly 
`org.apache.juneau.microservice.resources` in `juneau-microservice`) and 
`DebugResource` (formerly `org.apache.juneau.microservice.jetty.resources` in 
`juneau-microservice-jetty`) — have relocated to the new **`org.apache.juneau 
[...]
 
+- **String formatting helpers `f()` / `fs()` (and `StringUtils.format`) are 
now printf-only.** The general-purpose formatting helpers on 
`org.apache.juneau.commons.utils.Shorts` — `f(String,Object...)` and 
`fs(String,Object...)` — together with `StringUtils.format(...)`, previously 
accepted **both** `java.util.Formatter` printf syntax (`%s`, `%d`, `%1$s`) 
**and** `java.text.MessageFormat` syntax (`{0}`, `{1,number}`, `''`-quoting). 
As of 10.0.0 they are **printf-only**: a pattern is pass [...]
+  - **Drop-in MessageFormat replacement:** new `mf(String,Object...)` / 
`mfs(String,Object...)` helpers on `Shorts` (backed by 
`StringUtils.mformat(...)`) render the full MessageFormat grammar (`{0}`, 
`{1,number}`, `{2,date}`, `''`-quoting). Any caller that genuinely relied on 
MessageFormat behavior should migrate `f(...)` → `mf(...)` and `fs(...)` → 
`mfs(...)`.
+  - **Migration:** for the common case, change MessageFormat patterns to 
printf — `f("Value {0} of {1}", a, b)` → `f("Value %s of %s", a, b)`; for 
out-of-order/reused indices use positional printf — `{1} {0}` → `%2$s %1$s`, 
`{0}…{0}` → `%1$s…%1$s`. Any literal `%` in a pattern now needs escaping as 
`%%`. Where you truly need the MessageFormat engine (typed 
`{0,number}`/`{0,date}`, externally-authored i18n patterns), switch that call 
to `mf()`/`mfs()`.
+  - **i18n unaffected:** `Messages.getString(key, args)` (resource-bundle 
i18n) and `LogRecord.getMessage()` (logging) continue to render 
MessageFormat-style `.properties`/log patterns — they now route through `mf()` 
internally, so `{0}`-style entries in your resource bundles keep working 
exactly as before. Juneau's `Logger` MessageFormat-style logging is likewise 
unchanged.
+  - The dual-syntax `StringFormat` engine still exists and remains directly 
usable (`StringFormat.of(...)`) for callers that want both grammars in one 
pattern; only the `f()`/`fs()`/`StringUtils.format` entry points were narrowed 
to printf.
+
 _Other entries TBD — to be filled in before release. See also the major 
version bump note above._
 
 ### Deprecations
diff --git a/pages/topics/02.01.JuneauCommonsUtils.md 
b/pages/topics/02.01.JuneauCommonsUtils.md
index 4f1be224de..65ee4c209c 100644
--- a/pages/topics/02.01.JuneauCommonsUtils.md
+++ b/pages/topics/02.01.JuneauCommonsUtils.md
@@ -53,8 +53,10 @@ import static org.apache.juneau.commons.utils.Shorts.*;
 String name = or(nickname, username, "anonymous");
 String port = or(configuredPort, "8080");  // 2-arg form of the same varargs 
coalesce
 
-// Formatting shorthand (MessageFormat-style placeholders)
-String msg = f("User {0} has {1} items", "Alice", 10);
+// Formatting shorthand (printf-style placeholders — String.format semantics)
+String msg = f("User %s has %s items", "Alice", 10);
+// For MessageFormat-style {0} placeholders (e.g. i18n / .properties 
patterns), use mf():
+String msg2 = mf("User {0} has {1} items", "Alice", 10);
 
 // Comparisons
 cmp("apple", "banana");   // < 0
@@ -91,9 +93,10 @@ Reusable string utility methods for formatting, parsing, and 
manipulation.
 ```java
 import static org.apache.juneau.commons.utils.StringUtils.*;
 
-// Format strings (supports both MessageFormat and printf styles)
+// Format strings (printf-style — String.format semantics)
 format("Hello %s, you have %d items", "John", 5);
-format("Hello {0}, you have {1} items", "John", 5);
+// For MessageFormat-style {0} placeholders, use mformat():
+mformat("Hello {0}, you have {1} items", "John", 5);
 
 // Case-insensitive comparison
 equalsIgnoreCase("Hello", "hello");  // true
@@ -311,8 +314,8 @@ String value = assertArgNotNull("value", obj);
 // Assert not null or blank (returns the value, throws 
IllegalArgumentException otherwise)
 String str = assertArgNotNullOrBlank("str", input);
 
-// Assert an arbitrary condition (MessageFormat-style placeholders)
-assertArg(count > 0, "count must be positive, was {0}", count);
+// Assert an arbitrary condition (printf-style placeholders)
+assertArg(count > 0, "count must be positive, was %s", count);
 ```
 
 ### <java-class><a 
href="/site/apidocs/org/apache/juneau/commons/utils/PredicateUtils.html" 
target="_blank">PredicateUtils</a></java-class>
@@ -364,13 +367,13 @@ methods — they do **not** delegate to `ThrowableUtils`:
 ```java
 import static org.apache.juneau.commons.utils.Shorts.*;
 
-throw iaex("Value ''{0}'' is not valid", value);            // 
IllegalArgumentException
-throw isex("Component must be initialized before {0}", m);  // 
IllegalStateException
-throw rex("Unexpected error processing {0}", input);         // 
RuntimeException
-throw uoex("Method not supported for type {0}", type);       // 
UnsupportedOperationException
+throw iaex("Value '%s' is not valid", value);               // 
IllegalArgumentException
+throw isex("Component must be initialized before %s", m);   // 
IllegalStateException
+throw rex("Unexpected error processing %s", input);          // 
RuntimeException
+throw uoex("Method not supported for type %s", type);        // 
UnsupportedOperationException
 throw brex(MyBean.class, "Failed to create instance");        // 
BeanRuntimeException
-throw exex(cause, "Error invoking method {0}", method);       // 
ExecutableException
-throw ioex("Failed to read file {0}", path);                  // IOException
+throw exex(cause, "Error invoking method %s", method);        // 
ExecutableException
+throw ioex("Failed to read file %s", path);                  // IOException
 ```
 :::
 
@@ -391,7 +394,7 @@ Settings.get().setGlobal("juneau.enableVerboseExceptions", 
"true");
 import static org.apache.juneau.commons.utils.Shorts.*;
 import static org.apache.juneau.commons.utils.ThrowableUtils.log;
 
-throw log(iaex("Value ''{0}'' is not valid", value));
+throw log(iaex("Value '%s' is not valid", value));
 ```
 
 This is particularly useful when debugging swallowed exceptions or unexpected 
code paths where you need to identify exactly where an exception was created.
@@ -429,8 +432,12 @@ List<String> list = listBuilder(String.class)
 ```java
 import static org.apache.juneau.commons.utils.StringUtils.*;
 
-// Mixed format styles
-String result = format("User {0} has %d items", "Alice", 10);
+// printf-style formatting (String.format semantics)
+String result = format("User %s has %d items", "Alice", 10);
+// Returns: "User Alice has 10 items"
+
+// For MessageFormat-style {0} placeholders (i18n / .properties), use 
mformat():
+String result2 = mformat("User {0} has {1} items", "Alice", 10);
 // Returns: "User Alice has 10 items"
 ```
 
diff --git a/pages/topics/02.03.JuneauCommonsLang.md 
b/pages/topics/02.03.JuneauCommonsLang.md
index 3a27ebe37b..f801ed4681 100644
--- a/pages/topics/02.03.JuneauCommonsLang.md
+++ b/pages/topics/02.03.JuneauCommonsLang.md
@@ -121,6 +121,15 @@ String result3 = fmt3.format("Alice", "Bob", "Charlie");
 // Returns: "Alice loves Bob, and Alice also loves Charlie"
 ```
 
+:::note
+The `StringFormat` engine itself still understands **both** grammars in a 
single pattern (as shown above).
+However, as of 10.0.0 the terse formatting helpers `Shorts.f()` / 
`Shorts.fs()` and `StringUtils.format(...)`
+are **printf-only** — they route the pattern straight through `String.format` 
semantics, so `{0}`-style
+placeholders render literally. Use `Shorts.mf()` / `Shorts.mfs()` / 
`StringUtils.mformat(...)` (or a direct
+`StringFormat.of(...)`) when you need MessageFormat-style `{0}` / 
`''`-quoting. See the
+[10.0.0 release notes](/docs/release-notes/10.0.0) for migration details.
+:::
+
 ### <java-class><a 
href="/site/apidocs/org/apache/juneau/commons/lang/HashCode.html" 
target="_blank">HashCode</a></java-class>
 Utility class for generating integer hash codes.
 
diff --git a/pages/topics/02.15.JuneauCommonsLogging.md 
b/pages/topics/02.15.JuneauCommonsLogging.md
index f01c07d7de..224207a309 100644
--- a/pages/topics/02.15.JuneauCommonsLogging.md
+++ b/pages/topics/02.15.JuneauCommonsLogging.md
@@ -17,7 +17,7 @@ logger.warning("Low disk space");
 ```
 
 ### <java-class><a 
href="/site/apidocs/org/apache/juneau/commons/logging/LogRecord.html" 
target="_blank">LogRecord</a></java-class>
-A `java.util.logging.LogRecord` subclass that supports lazy message formatting 
— the message string is only built (via `StringUtils.format(String, 
Object...)`) if the record is actually published.
+A `java.util.logging.LogRecord` subclass that supports lazy message formatting 
— the message string is only built (via `StringUtils.mformat(String, 
Object...)`, using `MessageFormat`-style `{0}` placeholders to match the 
`java.util.logging` `LogRecord` contract) if the record is actually published.
 
 ## Test Support
 
diff --git a/pages/topics/05.05.JuneauBeanCommon.md 
b/pages/topics/05.05.JuneauBeanCommon.md
index ef02e623cd..d8b416fadd 100644
--- a/pages/topics/05.05.JuneauBeanCommon.md
+++ b/pages/topics/05.05.JuneauBeanCommon.md
@@ -33,10 +33,10 @@ LinkString link = new LinkString("Apache Juneau", 
"https://juneau.apache.org";);
 String html = HtmlSerializer.DEFAULT.serialize(link);
 ```
 
-The `uri` accepts `MessageFormat`-style arguments for parameterized links:
+The `uri` accepts `printf`-style (`String.format`) arguments for parameterized 
links:
 
 ```java
-LinkString view = new LinkString("View", "/items/{0}", itemId);
+LinkString view = new LinkString("View", "/items/%s", itemId);
 ```
 
 ## ResultSetList
diff --git a/pages/topics/07.02.01.CustomErrorMessages.md 
b/pages/topics/07.02.01.CustomErrorMessages.md
index 0e7b1b38b5..b43d459e23 100644
--- a/pages/topics/07.02.01.CustomErrorMessages.md
+++ b/pages/topics/07.02.01.CustomErrorMessages.md
@@ -40,19 +40,19 @@ import static org.apache.juneau.commons.utils.Shorts.fs;
 
 // Single placeholder
 String testName = "validateUser";
-assertBean(fs("Test {0} failed", testName),
+assertBean(fs("Test %s failed", testName),
           result, "status", "SUCCESS");
 
 // Multiple placeholders
 String userName = "Alice";
 int iteration = 5;
-assertBean(fs("User {0} validation failed on iteration {1}", userName, 
iteration),
+assertBean(fs("User %s validation failed on iteration %s", userName, 
iteration),
           user, "isValid", "true");
 
 // Contextual information
 String orderId = "ORD-123";
 String expectedStatus = "COMPLETED";
-assertBean(fs("Order {0} expected status {1}", orderId, expectedStatus),
+assertBean(fs("Order %s expected status %s", orderId, expectedStatus),
           order, "status", expectedStatus);
 ```
 
@@ -95,7 +95,7 @@ void testMultipleOrders() {
     
     for (int i = 0; i < orders.size(); i++) {
         Order order = orders.get(i);
-        assertBean(fs("Order validation failed at index {0}", i),
+        assertBean(fs("Order validation failed at index %s", i),
                   order, "status,total", "PENDING,99.99");
     }
 }
@@ -114,7 +114,7 @@ void testUsersByRole() {
         String role = entry.getKey();
         User user = entry.getValue();
         
-        assertBean(fs("User validation failed for role: {0}", role),
+        assertBean(fs("User validation failed for role: %s", role),
                   user, "role,isActive", role + ",true");
     }
 }
@@ -143,7 +143,7 @@ void testConfiguration() {
     Config config = loadConfig();
     String environment = System.getProperty("env", "unknown");
     
-    assertBean(fs("Config validation failed in environment: {0}", environment),
+    assertBean(fs("Config validation failed in environment: %s", environment),
               config, "database.host,database.port", "localhost,5432");
 }
 ```
@@ -206,7 +206,7 @@ void setUp() {
 }
 
 // Use custom message with the converter
-assertBean(fs("Date validation failed for user {0}", userId),
+assertBean(fs("Date validation failed for user %s", userId),
     user, "birthDate", "1990-01-15");
 
 @AfterEach
@@ -225,7 +225,7 @@ import static org.apache.juneau.commons.utils.Shorts.fs;
 void testUserEmails(String email) {
     User user = findUserByEmail(email);
     
-    assertBean(fs("User validation failed for email: {0}", email),
+    assertBean(fs("User validation failed for email: %s", email),
               user, "email,isVerified", email + ",true");
 }
 ```
@@ -241,7 +241,7 @@ Stream<DynamicTest> testOrders() {
         .map(order -> DynamicTest.dynamicTest(
             "Test Order #" + order.getId(),
             () -> assertBean(
-                fs("Order {0} validation failed", order.getId()),
+                fs("Order %s validation failed", order.getId()),
                 order, "status", "PENDING")));
 }
 ```
@@ -340,7 +340,7 @@ void testBatchProcessing() {
         processOrder(order);
         
         assertBean(fs(
-            "Batch processing failed at index {0} of {1}, Order ID: {2}",
+            "Batch processing failed at index %s of %s, Order ID: %s",
             i, orders.size(), order.getId()),
             order, "status", "PROCESSED");
     }
@@ -358,7 +358,7 @@ void testUserPermissions() {
     String expectedRole = user.isAdmin() ? "ADMIN" : "USER";
     
     assertBean(fs(
-        "Permission check failed for {0} user (expected role: {1})",
+        "Permission check failed for %s user (expected role: %s)",
         user.isAdmin() ? "admin" : "regular",
         expectedRole),
         user, "role,hasAccess", expectedRole + ",true");
diff --git a/pages/topics/27.V10MigrationGuide.md 
b/pages/topics/27.V10MigrationGuide.md
index 49ede32a93..a131890d5c 100644
--- a/pages/topics/27.V10MigrationGuide.md
+++ b/pages/topics/27.V10MigrationGuide.md
@@ -810,6 +810,18 @@ domain classes directly (`ObjectUtils.isNotNull(x)`, 
`StringUtils.format(...)`,
 `CollectionUtils.array(...)`, etc.) — the `Shorts` aliases and the canonical 
methods are
 interchangeable.
 
+:::caution
+**`f()` / `fs()` / `StringUtils.format(...)` are now printf-only.** These 
helpers previously accepted
+**both** printf (`%s`) and `MessageFormat` (`{0}`) placeholder styles. As of 
10.0.0 they route straight
+through `String.format` semantics, so `{0}`-style placeholders render 
**literally** and doubled single
+quotes (`''`) are no longer collapsed. Migrate MessageFormat patterns to 
printf (`{0}`→`%s`; out-of-order
+`{1} {0}`→`%2$s %1$s`; escape any literal `%` as `%%`), or switch to the new 
`Shorts.mf()` / `mfs()` /
+`StringUtils.mformat(...)` helpers, which own the full `MessageFormat` 
grammar. The i18n path
+(`Messages.getString`, `.properties` bundles) and `Logger` / `LogRecord` 
MessageFormat logging are
+unaffected — they route through `mf()` internally. See the
+[10.0.0 release notes](/docs/release-notes/10.0.0) for the full breakdown.
+:::
+
 ## Miscellaneous Utility Removals
 
 | Old | New | Notes |

Reply via email to