You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/10/25 12:04:12 UTC
(juneau) branch docs updated: Retext Javadoc doc links to their topic titles; fix or suppress the open Sonar CRITICAL and MAJOR findings (including a Settings NPE when the global store is disabled); stop ArgsPropertySource throwing NumberFormatException for non-numeric keys; create the Jetty microservice logs directory at startup; add the juneau-examples-guides module; make scripts/test.py --profile actually write JFR recordings; use the full width for wrapped juneau-run unit bars; and drop the removed .simple() call f [...]
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 e749524dd8 Retext Javadoc doc links to their topic titles; fix or suppress the open Sonar CRITICAL and MAJOR findings (including a Settings NPE when the global store is disabled); stop ArgsPropertySource throwing NumberFormatException for non-numeric keys; create the Jetty microservice logs directory at startup; add the juneau-examples-guides module; make scripts/test.py --profile actually write JFR recordings; use the full width for wrapped juneau-run unit bars; and drop the rem [...]
e749524dd8 is described below
commit e749524dd87189099d73e9931ba16463ef348823
Author: James Bognar <ja...@apache.org>
AuthorDate: Sun Oct 25 08:04:04 2026 -0400
Retext Javadoc doc links to their topic titles; fix or suppress the open Sonar CRITICAL and MAJOR findings (including a Settings NPE when the global store is disabled); stop ArgsPropertySource throwing NumberFormatException for non-numeric keys; create the Jetty microservice logs directory at startup; add the juneau-examples-guides module; make scripts/test.py --profile actually write JFR recordings; use the full width for wrapped juneau-run unit bars; and drop the removed .simple() c [...]
---
artifact-paths.json | 1 +
docusaurus.config.ts | 60 +-
package-lock.json | 71 +-
package.json | 1 -
pages/developer-info/01.DeveloperLinks.md | 2 +-
pages/developer-info/02.04.NewReleaseGuidelines.md | 2 +-
pages/developer-info/02.09.AiKnowledgeAuthoring.md | 56 +
pages/developer-info/02.10.TopicPageTemplate.md | 80 +
pages/developer-info/04.01.Scripts.md | 5 +-
pages/developer-info/04.03.03.ScriptBuildDocs.md | 2 +-
pages/guides/00.Guides.md | 19 +
pages/guides/01.SpringBootRestApi.md | 410 ++
pages/guides/02.SecureAnEndpoint.md | 390 ++
pages/guides/03.TestAnEndpoint.md | 247 +
pages/guides/04.TypedProxyClient.md | 341 +
pages/guides/05.CustomSwaps.md | 282 +
pages/guides/06.ExternalizeConfig.md | 215 +
pages/guides/07.OpenApiDocs.md | 241 +
pages/release-notes/10.0.0-marshall.md | 2938 +++++++++
pages/release-notes/10.0.0-microservice.md | 465 ++
pages/release-notes/10.0.0-other.md | 355 +
pages/release-notes/10.0.0-rest-client.md | 143 +
pages/release-notes/10.0.0-rest-server.md | 2884 +++++++++
pages/release-notes/10.0.0.md | 6802 +-------------------
pages/release-notes/5.0.0.00.md | 43 -
pages/release-notes/5.0.0.01.md | 13 -
pages/release-notes/5.0.0.02.md | 31 -
pages/release-notes/5.0.0.03.md | 16 -
pages/release-notes/5.0.0.04.md | 14 -
pages/release-notes/5.0.0.05.md | 30 -
pages/release-notes/5.0.0.06.md | 9 -
pages/release-notes/5.0.0.07.md | 105 -
pages/release-notes/5.0.0.08.md | 14 -
pages/release-notes/5.0.0.09.md | 48 -
pages/release-notes/5.0.0.10.md | 34 -
pages/release-notes/5.0.0.11.md | 18 -
pages/release-notes/5.0.0.12.md | 32 -
pages/release-notes/5.0.0.13.md | 38 -
pages/release-notes/5.0.0.14.md | 83 -
pages/release-notes/5.0.0.15.md | 16 -
pages/release-notes/5.0.0.16.md | 12 -
pages/release-notes/5.0.0.17.md | 12 -
pages/release-notes/5.0.0.18.md | 25 -
pages/release-notes/5.0.0.19.md | 16 -
pages/release-notes/5.0.0.20.md | 56 -
pages/release-notes/5.0.0.21.md | 30 -
pages/release-notes/5.0.0.22.md | 17 -
pages/release-notes/5.0.0.23.md | 16 -
pages/release-notes/5.0.0.24.md | 40 -
pages/release-notes/5.0.0.25.md | 23 -
pages/release-notes/5.0.0.26.md | 20 -
pages/release-notes/5.0.0.27.md | 20 -
pages/release-notes/5.0.0.28.md | 14 -
pages/release-notes/5.0.0.29.md | 28 -
pages/release-notes/5.0.0.30.md | 13 -
pages/release-notes/5.0.0.31.md | 20 -
pages/release-notes/5.0.0.32.md | 38 -
pages/release-notes/5.0.0.33.md | 43 -
pages/release-notes/5.0.0.34.md | 69 -
pages/release-notes/5.0.0.35.md | 12 -
pages/release-notes/5.0.0.36.md | 17 -
pages/release-notes/5.1.0.00.md | 54 -
pages/release-notes/5.1.0.01.md | 23 -
pages/release-notes/5.1.0.02.md | 19 -
pages/release-notes/5.1.0.03.md | 51 -
pages/release-notes/5.1.0.04.md | 24 -
pages/release-notes/5.1.0.05.md | 30 -
pages/release-notes/5.1.0.06.md | 24 -
pages/release-notes/5.1.0.07.md | 37 -
pages/release-notes/5.1.0.08.md | 27 -
pages/release-notes/5.1.0.09.md | 35 -
pages/release-notes/5.1.0.10.md | 69 -
pages/release-notes/5.1.0.11.md | 83 -
pages/release-notes/5.1.0.12.md | 23 -
pages/release-notes/5.1.0.13.md | 44 -
pages/release-notes/5.1.0.14.md | 34 -
pages/release-notes/5.1.0.15.md | 50 -
pages/release-notes/5.1.0.16.md | 45 -
pages/release-notes/5.1.0.17.md | 111 -
pages/release-notes/5.1.0.18.md | 25 -
pages/release-notes/5.1.0.19.md | 42 -
pages/release-notes/5.1.0.20.md | 100 -
pages/release-notes/5.2.0.0.md | 416 --
pages/release-notes/5.2.0.1.md | 70 -
pages/release-notes/5.x.md | 2401 +++++++
pages/release-notes/6.0.0.md | 177 -
pages/release-notes/6.0.1.md | 23 -
pages/release-notes/6.1.0.md | 265 -
pages/release-notes/6.2.0.md | 316 -
pages/release-notes/6.3.0.md | 405 --
pages/release-notes/6.3.1.md | 209 -
pages/release-notes/6.4.0.md | 299 -
pages/release-notes/6.x.md | 1690 +++++
pages/release-notes/7.0.0.md | 36 -
pages/release-notes/7.0.1.md | 125 -
pages/release-notes/7.1.0.md | 359 --
pages/release-notes/7.2.1.md | 42 -
pages/release-notes/7.2.2.md | 9 -
pages/release-notes/{7.2.0.md => 7.x.md} | 578 +-
pages/release-notes/8.0.0.md | 105 -
pages/release-notes/8.1.0.md | 303 -
pages/release-notes/8.1.1.md | 87 -
pages/release-notes/8.1.2.md | 57 -
pages/release-notes/8.1.3.md | 63 -
pages/release-notes/8.2.0.md | 462 --
pages/release-notes/8.x.md | 1074 ++++
pages/release-notes/9.0.0.md | 18 +-
pages/release-notes/9.2.0.md | 82 +-
pages/topics/00.00.GettingStarted.md | 25 +-
pages/topics/00.01.GettingStartedInstall.md | 90 +
pages/topics/00.02.GettingStartedSerializer.md | 65 +-
pages/topics/00.03.GettingStartedRestApp.md | 58 +-
pages/topics/00.04.GettingStartedClient.md | 52 +-
pages/topics/01.00.JuneauEcosystemOverview.md | 6 +-
pages/topics/01.02.WhyJuneau.md | 22 +-
pages/topics/02.00.JuneauCommons.md | 38 +-
pages/topics/02.01.JuneauCommonsUtils.md | 4 +-
pages/topics/02.02.JuneauCommonsCollections.md | 4 +-
pages/topics/02.03.JuneauCommonsLang.md | 4 +-
pages/topics/02.04.JuneauCommonsReflection.md | 4 +-
pages/topics/02.05.JuneauCommonsBeans.md | 6 +-
pages/topics/02.06.JuneauCommonsSettings.md | 4 +-
pages/topics/02.09.JuneauCommonsIO.md | 4 +-
pages/topics/02.10.00.JuneauCommonsInject.md | 4 +
pages/topics/02.10.01.ValueAnnotation.md | 2 +-
pages/topics/02.11.SimpleVariableLanguage.md | 3 +
pages/topics/02.13.JuneauCommonsHttpPart.md | 53 +-
pages/topics/02.16.JuneauCommonsRuntime.md | 5 +
pages/topics/02.17.JuneauCommonsTime.md | 2 +-
pages/topics/03.00.JuneauMarshall.md | 26 +-
pages/topics/03.02.SerializersAndParsers.md | 6 +-
pages/topics/03.03.00.BeanContexts.md | 12 +-
pages/topics/03.03.01.JavaBeansSupport.md | 4 +-
pages/topics/03.03.03.BeanTypeAnnotation.md | 22 +-
pages/topics/03.03.04.BeanPropAnnotation.md | 12 +-
pages/topics/03.03.05.BeanCtorAnnotation.md | 2 +-
pages/topics/03.03.06.BeanIgnoreAnnotation.md | 8 +-
pages/topics/03.03.09.PojoBuilders.md | 10 +-
pages/topics/03.03.10.BypassSerialization.md | 2 +-
pages/topics/03.03.12.MarshalledAnnotation.md | 6 +-
pages/topics/03.03.14.MarshalledFilter.md | 2 +
pages/topics/03.03.15.ExampleAnnotation.md | 2 +-
pages/topics/03.04.HttpPartSerializersParsers.md | 11 +-
pages/topics/03.07.ContextAnnotations.md | 6 +-
pages/topics/03.08.JsonMap.md | 10 +
pages/topics/03.12.SerializerSetsParserSets.md | 103 +-
pages/topics/03.13.00.Swaps.md | 108 +-
pages/topics/03.13.02.AutoSwaps.md | 12 +-
...3.04.OneWaySwaps.md => 03.13.03.OneWaySwaps.md} | 4 +-
pages/topics/03.13.03.PerMediaTypeSwaps.md | 97 -
...wapAnnotation.md => 03.13.04.SwapAnnotation.md} | 4 +-
pages/topics/03.13.05.TemplatedSwaps.md | 89 +
...gateClasses.md => 03.13.06.SurrogateClasses.md} | 0
pages/topics/03.13.06.TemplatedSwaps.md | 35 -
.../topics/03.14.DynamicallyAppliedAnnotations.md | 4 +-
pages/topics/03.15.00.BeanDictionaries.md | 6 +-
pages/topics/03.19.MarshallingUris.md | 5 +-
pages/topics/03.21.PojoCategories.md | 3 +-
.../03.22.00.MarshallSimpleVariableLanguage.md | 2 +-
pages/topics/03.22.01.SvlVariables.md | 4 +
pages/topics/03.22.02.VarResolvers.md | 77 +-
pages/topics/03.22.03.DefaultVarResolver.md | 1 +
pages/topics/03.23.MarshallEncoders.md | 6 +-
pages/topics/03.25.00.JsonSupport.md | 4 +
pages/topics/03.25.02.JsonSerializers.md | 83 +-
pages/topics/03.25.04.JsonParsers.md | 90 +-
pages/topics/03.25.05.JsonAnnotation.md | 48 +-
pages/topics/03.25.06.JsonSchemaDetails.md | 2 +-
pages/topics/03.26.00.XmlSupport.md | 124 +
pages/topics/03.26.01.XmlMethodology.md | 9 +-
pages/topics/03.26.02.XmlSerializers.md | 12 +-
pages/topics/03.26.03.XmlParsers.md | 71 +-
pages/topics/03.26.04.XmlBeanTypeNameAnnotation.md | 15 +-
...ation.md => 03.26.05.00.XmlFormatAnnotation.md} | 5 +-
...ormatAttrs.md => 03.26.05.01.XmlFormatAttrs.md} | 12 +-
...lapsed.md => 03.26.05.02.XmlFormatCollapsed.md} | 5 +-
...lements.md => 03.26.05.03.XmlFormatElements.md} | 6 +-
...lFormatText.md => 03.26.05.04.XmlFormatText.md} | 6 +-
...space.md => 03.26.05.05.XmlFormatWhitespace.md} | 3 +-
pages/topics/03.26.05.XmlChildNameAnnotation.md | 121 -
....XmlNamespaces.md => 03.26.06.XmlNamespaces.md} | 0
.../{03.26.08.SoapXml.md => 03.26.07.SoapXml.md} | 0
pages/topics/03.27.00.HtmlSupport.md | 4 +-
pages/topics/03.27.01.HtmlMethodology.md | 9 +-
pages/topics/03.27.02.HtmlSerializers.md | 15 +-
pages/topics/03.27.03.HtmlParsers.md | 66 +-
pages/topics/03.27.04.HtmlAnnotation.md | 108 +-
...Serializer.md => 03.27.05.HtmlDocSerializer.md} | 91 +-
pages/topics/03.27.05.HtmlRenderAnnotation.md | 104 -
...emplates.md => 03.27.06.HtmlCustomTemplates.md} | 42 +-
pages/topics/03.27.07.BasicHtmlDocTemplate.md | 41 -
...emaSupport.md => 03.27.07.HtmlSchemaSupport.md} | 36 +-
pages/topics/03.28.00.UonSupport.md | 95 +-
pages/topics/03.28.01.UonMethodology.md | 90 -
pages/topics/03.28.01.UonSerializers.md | 78 +
pages/topics/03.28.02.UonParsers.md | 74 +
pages/topics/03.28.02.UonSerializers.md | 26 -
pages/topics/03.28.03.UonParsers.md | 25 -
pages/topics/03.29.00.UrlEncodingSupport.md | 93 +-
pages/topics/03.29.01.UrlEncMethodology.md | 90 -
pages/topics/03.29.01.UrlEncSerializers.md | 81 +
pages/topics/03.29.02.UrlEncParsers.md | 72 +
pages/topics/03.29.02.UrlEncSerializers.md | 26 -
pages/topics/03.29.03.UrlEncParsers.md | 25 -
...tation.md => 03.29.03.UrlEncodingAnnotation.md} | 2 +-
pages/topics/03.30.00.MessagePackSupport.md | 6 +-
pages/topics/03.30.01.MsgPackSerializers.md | 66 +-
pages/topics/03.30.02.MsgPackParsers.md | 63 +-
pages/topics/03.31.00.OpenApiSupport.md | 67 +-
pages/topics/03.31.01.OpenApiMethodology.md | 17 +-
pages/topics/03.31.02.OpenApiSerializers.md | 216 +-
pages/topics/03.31.03.OpenApiParsers.md | 155 +-
pages/topics/03.32.TomlSupport.md | 2 +-
pages/topics/03.33.Prototext.md | 4 +-
pages/topics/03.35.Parquet.md | 2 +-
pages/topics/03.36.00.YamlSupport.md | 2 +-
pages/topics/03.36.02.YamlSerializers.md | 2 +-
pages/topics/03.36.04.YamlAnnotation.md | 65 +-
pages/topics/03.37.01.CsvSerializers.md | 2 +-
pages/topics/03.38.Markdown.md | 2 +-
pages/topics/03.39.00.JsonlSupport.md | 4 +-
pages/topics/03.39.01.Json5l.md | 2 +-
pages/topics/03.40.Hjson.md | 4 +-
pages/topics/03.41.Jcs.md | 2 +-
pages/topics/03.42.Bson.md | 4 +-
pages/topics/03.43.Cbor.md | 4 +-
pages/topics/03.44.HoconSupport.md | 4 +-
pages/topics/03.45.Ini.md | 2 +-
.../{03.49.PlainText.md => 03.46.PlainText.md} | 0
pages/topics/{03.46.Sse.md => 03.47.Sse.md} | 1 +
...reaming.md => 03.48.00.TokenRecordStreaming.md} | 9 +-
...ordStreaming.md => 03.48.01.RecordStreaming.md} | 0
...okenStreaming.md => 03.48.02.TokenStreaming.md} | 0
...reaming.md => 03.48.03.ArrayRecordStreaming.md} | 3 +-
...ion.md => 03.48.04.RestStreamingIntegration.md} | 0
...ams.md => 03.48.05.ReadingContinuousStreams.md} | 0
...eaming.md => 03.48.06.LargeDatasetStreaming.md} | 0
....48.BestPractices.md => 03.49.BestPractices.md} | 0
pages/topics/04.00.JuneauMarshallRdf.md | 46 +
pages/topics/04.01.Rdf.md | 1 +
pages/topics/05.01.JuneauBeanHtml5.md | 3 +-
pages/topics/05.02.JuneauBeanAtom.md | 88 +-
pages/topics/05.03.JuneauBeanJsonSchema.md | 4 +-
pages/topics/05.04.JuneauBeanOpenApi3.md | 4 +-
pages/topics/05.06.JuneauBeanSwagger2.md | 2 +-
pages/topics/05.08.JuneauBeanRfc7807.md | 2 +-
pages/topics/05.09.JuneauBeanHal.md | 2 +-
pages/topics/05.10.JuneauBeanJsonApi.md | 2 +-
pages/topics/05.11.JuneauBeanJsonPatch.md | 2 +-
pages/topics/06.00.JuneauConfig.md | 67 +
pages/topics/06.01.00.Overview.md | 4 +
pages/topics/06.02.00.ReadingEntries.md | 176 +
....02.03.JCFObjects.md => 06.02.01.JCFObjects.md} | 3 +-
pages/topics/06.02.01.Pojos.md | 60 -
pages/topics/06.02.02.Arrays.md | 58 -
pages/topics/06.02.04.BinaryData.md | 59 -
pages/topics/06.03.00.Variables.md | 7 +
pages/topics/06.03.02.PropertySources.md | 54 +-
pages/topics/06.06.00.SettingValues.md | 44 +-
pages/topics/06.06.02.CustomEntrySerialization.md | 25 -
pages/topics/06.06.03.BulkSettingValues.md | 17 -
pages/topics/06.07.ConfigListeners.md | 66 +-
.../topics/{06.09.Imports.md => 06.08.Imports.md} | 0
pages/topics/06.08.SerializingConfigs.md | 59 -
pages/topics/06.09.00.ConfigStores.md | 284 +
...lasspathStore.md => 06.09.01.ClasspathStore.md} | 4 +-
pages/topics/06.10.00.ConfigStores.md | 72 -
pages/topics/06.10.01.MemoryStore.md | 49 -
pages/topics/06.10.02.FileStore.md | 36 -
pages/topics/06.10.03.CustomStores.md | 108 -
pages/topics/06.10.04.StoreListeners.md | 23 -
...ReadOnlyConfigs.md => 06.10.ReadOnlyConfigs.md} | 2 +-
...2.ClosingConfigs.md => 06.11.ClosingConfigs.md} | 0
...faultConfig.md => 06.12.SystemDefaultConfig.md} | 0
pages/topics/06.13.YamlConfigFiles.md | 78 +
...md => 06.14.ConfigProfilesAndRelaxedBinding.md} | 0
pages/topics/06.14.YamlConfigFiles.md | 49 -
...IniConfigFormat.md => 06.15.IniConfigFormat.md} | 2 +
pages/topics/07.00.JuneauTest.md | 16 +-
pages/topics/07.01.AssertionsOverview.md | 2 +-
pages/topics/07.02.00.BeanCentricTesting.md | 30 +-
pages/topics/07.02.01.CustomErrorMessages.md | 12 +-
pages/topics/07.02.02.00.Customization.md | 15 +-
pages/topics/07.02.02.01.Stringifiers.md | 10 +-
pages/topics/07.02.02.02.Listifiers.md | 10 +-
pages/topics/07.02.02.03.Swappers.md | 12 +-
pages/topics/07.02.03.PropertyExtractors.md | 10 +-
pages/topics/07.03.JuneauTestJUnitExtensions.md | 4 +-
pages/topics/08.JuneauRest.md | 27 +-
pages/topics/09.00.JuneauRestCommon.md | 4 +
pages/topics/09.01.HelperClasses.md | 6 +-
pages/topics/09.02.Annotations.md | 2 +-
pages/topics/09.04.HttpParts.md | 9 +-
pages/topics/09.05.HttpEntitiesAndResources.md | 4 +-
pages/topics/09.07.RemoteProxyInterfaces.md | 29 +-
pages/topics/10.00.JuneauRestServer.md | 14 +-
pages/topics/10.01.RestServerOverview.md | 10 +-
pages/topics/10.02.00.RestAnnotatedClasses.md | 4 +
pages/topics/10.02.01.PredefinedClasses.md | 14 +-
pages/topics/10.02.02.ChildResources.md | 10 +-
pages/topics/10.02.03.PathVariables.md | 4 +-
pages/topics/10.02.04.Deployment.md | 2 +-
pages/topics/10.02.05.LifecycleHooks.md | 4 +-
pages/topics/10.02.06.RestAnnotationAttributes.md | 6 +-
pages/topics/10.03.00.RestOpAnnotatedMethods.md | 21 +-
.../topics/10.03.01.InferredHttpMethodsAndPaths.md | 12 +-
pages/topics/10.03.02.JavaMethodParameters.md | 2 +-
pages/topics/10.03.05.PathPatterns.md | 4 +-
pages/topics/10.03.06.Matchers.md | 2 +-
pages/topics/10.03.07.OverloadingHttpMethods.md | 4 +-
pages/topics/10.03.08.AdditionalInformation.md | 17 -
pages/topics/10.04.00.RestServerHttpParts.md | 57 +-
pages/topics/10.04.01.PartMarshallers.md | 2 +-
pages/topics/10.04.02.HttpPartAnnotations.md | 26 +-
pages/topics/10.04.03.DefaultParts.md | 4 +-
pages/topics/10.04.04.RequestBeans.md | 6 +-
pages/topics/10.04.05.ResponseBeans.md | 4 +-
pages/topics/10.04.06.HttpPartApis.md | 40 -
...alidation.md => 10.04.06.HttpPartValidation.md} | 2 +-
pages/topics/10.05.Marshalling.md | 4 +-
pages/topics/10.06.HandlingFormPosts.md | 4 +-
pages/topics/10.07.RestServerComposition.md | 18 +-
pages/topics/10.08.RestServerMixinSubContexts.md | 26 +-
pages/topics/10.13.RestServerSse.md | 6 +
pages/topics/10.14.Guards.md | 6 +-
pages/topics/10.15.Converters.md | 2 +-
pages/topics/10.17.RestServerEncoders.md | 22 +-
pages/topics/10.18.ConfigurationFiles.md | 16 +-
pages/topics/10.19.RestServerSvlVariables.md | 18 +-
pages/topics/10.21.StaticFilesMixin.md | 2 +-
pages/topics/10.22.ConventionEndpointsMixins.md | 2 +-
pages/topics/10.23.OpsIntrospectionMixins.md | 2 +-
pages/topics/10.24.JspViewSupport.md | 4 +-
pages/topics/10.25.ThymeleafViewSupport.md | 4 +-
pages/topics/10.26.MustacheViewSupport.md | 4 +-
pages/topics/10.27.FreemarkerViewSupport.md | 4 +-
pages/topics/10.28.00.Swagger.md | 151 +
...gger.md => 10.28.01.BasicRestServletSwagger.md} | 11 +-
....ApiDocsMixins.md => 10.28.02.ApiDocsMixins.md} | 19 +-
...SwaggerInfo.md => 10.28.03.BasicSwaggerInfo.md} | 8 +-
...9.04.SwaggerTags.md => 10.28.04.SwaggerTags.md} | 4 +-
...Parameters.md => 10.28.05.SwaggerParameters.md} | 4 +-
....SwaggerModels.md => 10.28.06.SwaggerModels.md} | 0
pages/topics/10.28.ClientVersioning.md | 44 -
...tion.md => 10.29.00.HtmlDocConfigAnnotation.md} | 8 +-
pages/topics/10.29.00.Swagger.md | 18 -
....31.01.HtmlUIvsDI.md => 10.29.01.HtmlUIvsDI.md} | 0
...1.02.HtmlWidgets.md => 10.29.02.HtmlWidgets.md} | 2 +-
...idgets.md => 10.29.03.HtmlPredefinedWidgets.md} | 8 +-
...mization.md => 10.29.04.HtmlUiCustomization.md} | 4 +-
...lStylesheets.md => 10.29.05.HtmlStylesheets.md} | 4 +-
pages/topics/10.29.05.SwaggerOperations.md | 42 -
pages/topics/10.29.07.SwaggerResponses.md | 39 -
pages/topics/10.29.09.SwaggerStylesheet.md | 12 -
...g.md => 10.30.RestServerLoggingAndDebugging.md} | 39 +-
...HttpStatusCodes.md => 10.31.HttpStatusCodes.md} | 0
...etails.md => 10.32.RestServerProblemDetails.md} | 2 +-
...nalGet.md => 10.33.RestServerConditionalGet.md} | 0
...md => 10.34.RestServerRateLimitAndRequestId.md} | 4 +-
pages/topics/10.35.ClientVersioning.md | 59 +
...Validation.md => 10.36.RestServerValidation.md} | 2 +-
pages/topics/10.38.RestServerAuthGuards.md | 2 +
...erFramework.md => 10.39.AuthFilterFramework.md} | 3 +
...ticator.md => 10.40.RestServerAuthenticator.md} | 13 +-
...SamlAuthSupport.md => 10.41.SamlAuthSupport.md} | 0
...uthAuthSupport.md => 10.42.OAuthAuthSupport.md} | 7 +-
...dcRelyingParty.md => 10.43.OidcRelyingParty.md} | 3 +
...ability.md => 10.44.RestServerObservability.md} | 12 +-
...ics.md => 10.45.RestServerMicrometerMetrics.md} | 6 +-
...elTracing.md => 10.46.RestServerOtelTracing.md} | 6 +-
...nStatistics.md => 10.47.ExecutionStatistics.md} | 0
...ing.md => 10.48.RestServerManagementLogging.md} | 8 +-
...ispatch.md => 10.49.RestServerAsyncDispatch.md} | 4 +-
...rverReactive.md => 10.50.RestServerReactive.md} | 0
...tInParameters.md => 10.51.BuiltInParameters.md} | 0
...1.SessionOptions.md => 10.52.SessionOptions.md} | 6 +-
pages/topics/10.52.UsingWithOsgi.md | 64 -
pages/topics/10.53.UsingWithOsgi.md | 49 +
.../{10.53.RestContext.md => 10.54.RestContext.md} | 0
....54.RestOpContext.md => 10.55.RestOpContext.md} | 2 +-
...seProcessors.md => 10.56.ResponseProcessors.md} | 0
.../topics/{10.56.RestRpc.md => 10.57.RestRpc.md} | 2 +-
pages/topics/10.57.SerializingUris.md | 77 -
pages/topics/10.58.SerializingUris.md | 111 +
...10.58.UtilityBeans.md => 10.59.UtilityBeans.md} | 0
.../{10.59.HtmlBeans.md => 10.60.HtmlBeans.md} | 0
pages/topics/10.60.OtherNotes.md | 8 -
pages/topics/10.61.Log4j.md | 33 -
....63.RestServerRdf.md => 10.61.RestServerRdf.md} | 4 +-
...oleChrome.md => 10.62.00.AdminConsoleChrome.md} | 4 +
...olePage.md => 10.62.01.BuildingAConsolePage.md} | 0
...encies.md => 10.62.02.CardAssetDependencies.md} | 2 +-
....64.03.ChartCards.md => 10.62.03.ChartCards.md} | 0
....64.04.JobLogCard.md => 10.62.04.JobLogCard.md} | 0
...Rendering.md => 10.63.MarkdownViewRendering.md} | 0
.../{10.66.DataTables.md => 10.64.DataTables.md} | 0
...ativeWidgets.md => 10.65.DeclarativeWidgets.md} | 2 +-
...ichTableViews.md => 10.66.00.RichTableViews.md} | 0
...atalog.md => 10.66.01.RichTableViewsCatalog.md} | 0
...ctions.md => 10.66.02.RichTableViewsActions.md} | 0
...rers.md => 10.66.03.RichTableViewsRenderers.md} | 0
...urity.md => 10.66.04.RichTableViewsSecurity.md} | 0
...ipt.md => 10.66.05.RichTableViewsJavaScript.md} | 0
...tings.md => 10.66.06.RichTableViewsSettings.md} | 0
...egions.md => 10.66.07.RichTableViewsRegions.md} | 0
...Jobs.md => 10.66.08.RichTableViewsAsyncJobs.md} | 0
...ewsIcons.md => 10.66.09.RichTableViewsIcons.md} | 0
....RunViewRegion.md => 10.66.10.RunViewRegion.md} | 0
...erminalRegion.md => 10.66.11.TerminalRegion.md} | 0
.../{10.68.13.RowJobs.md => 10.66.12.RowJobs.md} | 0
...MessageBus.md => 10.66.13.ConsoleMessageBus.md} | 0
...wSettings.md => 10.67.DatatableViewSettings.md} | 0
pages/topics/11.01.JuneauMcpQuickstart.md | 17 +
pages/topics/12.00.JuneauRestServerSpringboot.md | 3 +
pages/topics/12.01.SpringBootOverview.md | 3 +
pages/topics/13.00.JuneauRestClient.md | 14 +-
pages/topics/13.01.ChoosingARestClient.md | 17 +-
pages/topics/13.02.PojoMarshalling.md | 2 +-
pages/topics/13.08.CustomCallHandlers.md | 47 -
...13.09.Interceptors.md => 13.08.Interceptors.md} | 0
...0.00.RestProxies.md => 13.09.00.RestProxies.md} | 6 +-
.../{13.10.01.Remote.md => 13.09.01.Remote.md} | 18 +-
...02.RemoteMethod.md => 13.09.02.RemoteMethod.md} | 4 +-
.../{13.10.03.Content.md => 13.09.03.Content.md} | 2 +-
.../{13.10.04.FormData.md => 13.09.04.FormData.md} | 0
.../{13.10.05.Query.md => 13.09.05.Query.md} | 0
.../{13.10.06.Header.md => 13.09.06.Header.md} | 0
.../topics/{13.10.07.Path.md => 13.09.07.Path.md} | 0
.../{13.10.08.Request.md => 13.09.08.Request.md} | 4 +-
.../{13.10.09.Response.md => 13.09.09.Response.md} | 4 +-
...rfaces.md => 13.09.10.DualPurposeInterfaces.md} | 4 +-
...g.md => 13.10.RestClientLoggingAndDebugging.md} | 0
pages/topics/13.11.CustomizingHttpClient.md | 59 +
pages/topics/13.12.CustomizingHttpClient.md | 30 -
pages/topics/13.12.ExtendingRestClient.md | 108 +
pages/topics/13.13.Authentication.md | 281 +
pages/topics/13.13.ExtendingRestClient.md | 31 -
pages/topics/13.14.00.Authentication.md | 24 -
...RestClient.md => 13.14.00.NextGenRestClient.md} | 16 +-
pages/topics/13.14.01.AuthenticationBASIC.md | 26 -
....15.01.NgRequests.md => 13.14.01.NgRequests.md} | 0
pages/topics/13.14.02.AuthenticationForm.md | 83 -
...5.02.NgResponses.md => 13.14.02.NgResponses.md} | 0
pages/topics/13.14.03.AuthenticationOIDC.md | 128 -
...yConverters.md => 13.14.03.NgBodyConverters.md} | 0
...04.NgAssertions.md => 13.14.04.NgAssertions.md} | 0
...gInterceptors.md => 13.14.05.NgInterceptors.md} | 0
...emoteProxies.md => 13.14.06.NgRemoteProxies.md} | 0
...apters.md => 13.15.00.HttpTransportAdapters.md} | 8 +-
...md => 13.15.01.RestClientApacheHttpClient45.md} | 5 -
...md => 13.15.02.RestClientApacheHttpClient50.md} | 5 -
...lientOkHttp.md => 13.15.03.RestClientOkHttp.md} | 5 -
...tClientJetty.md => 13.15.04.RestClientJetty.md} | 5 -
...reamingCursors.md => 13.16.StreamingCursors.md} | 6 -
...otiation.md => 13.17.ContentTypeNegotiation.md} | 5 -
...ClientSecurity.md => 13.18.NgClientSecurity.md} | 0
pages/topics/14.00.JuneauRestMock.md | 6 +
pages/topics/14.01.MockRestClientOverview.md | 2 +
pages/topics/14.02.NgMockRestClient.md | 18 +-
pages/topics/15.00.JuneauMicroservice.md | 34 +
pages/topics/15.01.MicroserviceOverview.md | 1 +
pages/topics/15.02.Args.md | 2 +-
pages/topics/15.03.Manifest.md | 2 +-
pages/topics/{16.05.Config.md => 15.04.Config.md} | 42 +-
pages/topics/15.04.SystemProperties.md | 30 -
pages/topics/15.05.VarResolver.md | 21 +-
pages/topics/15.07.MicroserviceCoreListeners.md | 50 +-
pages/topics/16.00.JuneauMicroserviceJetty.md | 11 +-
pages/topics/16.01.MicroserviceJettyOverview.md | 6 +-
pages/topics/16.04.PredefinedResourceClasses.md | 4 +
.../{16.06.JettyXml.md => 16.05.JettyXml.md} | 0
...UiCustomization.md => 16.06.UiCustomization.md} | 35 +-
.../{16.08.Extending.md => 16.07.Extending.md} | 6 +-
...16.09.HealthProbes.md => 16.08.HealthProbes.md} | 0
...acefulShutdown.md => 16.09.GracefulShutdown.md} | 0
...gementSurface.md => 16.10.ManagementSurface.md} | 2 +-
pages/topics/17.JuneauMicroserviceTomcat.md | 2 +-
pages/topics/18.01.ScServerOverview.md | 2 +-
pages/topics/19.00.JuneauPetstore.md | 16 +-
pages/topics/19.01.PetstoreConsoleTour.md | 2 +-
pages/topics/19.07.PetstoreJobs.md | 2 +-
pages/topics/19.09.PetstoreCustomCards.md | 2 +-
pages/topics/19.10.PetstoreThemes.md | 2 +-
pages/topics/19.11.PetstorePendingChanges.md | 2 +-
pages/topics/19.12.PetstoreUsers.md | 2 +-
pages/topics/20.JuneauExamples.md | 2 +
pages/topics/21.00.DependencyManagement.md | 8 +
pages/topics/23.00.JuneauShaded.md | 2 +
pages/topics/23.02.JuneauShadedRestClient.md | 2 +-
pages/topics/24.StarterProjects.md | 13 +
pages/topics/25.00.Security.md | 12 +-
pages/topics/25.01.MarshallingSecurity.md | 6 +-
pages/topics/25.02.SvlSecurity.md | 4 +-
pages/topics/25.03.RestSecurity.md | 6 +-
pages/topics/25.04.ContentSecurityPolicy.md | 6 +-
pages/topics/27.V10MigrationGuide.md | 4 +-
scripts/JavaFenceParse.java | 62 +
scripts/apply-renames.md | 48 +-
scripts/apply-renames.py | 161 +-
scripts/build-docs.md | 4 +-
scripts/build-docs.py | 33 +
scripts/check-code-fences.md | 46 +
scripts/check-code-fences.py | 62 +
scripts/check-doc-inventory-allowlist.txt | 1 +
scripts/check-guide-snippets.md | 54 +
scripts/check-guide-snippets.py | 185 +
scripts/check-java-fences.md | 55 +
scripts/check-java-fences.py | 77 +
scripts/check-topic-links.md | 7 +-
scripts/check-topic-links.py | 77 +-
scripts/check-topic-template.md | 57 +
scripts/check-topic-template.py | 188 +
scripts/code_fences.py | 50 +
scripts/juneau_root.py | 4 +-
scripts/renumber-topics.md | 60 +
scripts/renumber-topics.py | 338 +
scripts/tests/README.md | 8 +
scripts/tests/test_apply_renames.py | 110 +
scripts/tests/test_build_docs.py | 47 +
scripts/tests/test_check_fences.py | 91 +
scripts/tests/test_check_guide_snippets.py | 166 +
scripts/tests/test_check_topic_links.py | 70 +
scripts/tests/test_check_topic_template.py | 133 +
scripts/tests/test_renumber_topics.py | 263 +
scripts/tests/test_verify_docs.py | 226 +
scripts/verify-docs.md | 11 +-
scripts/verify-docs.py | 147 +-
sidebars.ts | 5374 ++++++++--------
src/components/GiscusComments.tsx | 67 -
src/components/HomepageFeatures/index.tsx | 84 -
src/css/custom.css | 70 +-
.../styles.module.css => pages/404.tsx} | 26 +-
src/pages/about.md | 222 +-
src/pages/downloads.md | 53 +-
src/pages/index.module.css | 79 +-
src/pages/index.tsx | 93 +-
src/pages/markdown-page.md | 7 -
src/pages/security.md | 11 +-
src/theme/MDXComponents.tsx | 2 +
src/theme/{DocItem => Mermaid}/index.tsx | 25 +-
src/theme/{DocItem => TOCItems/Tree}/index.tsx | 25 +-
static/.htaccess | 18 +
static/ai/juneau-knowledge.jsonl | 622 +-
static/ai/manifest.json | 8 +-
static/ai/taxonomy.json | 52 +-
static/assets/.htaccess | 4 +
static/llms.txt | 121 +-
static/topics/JuneauPetstore.Tour.1.webp | Bin 0 -> 100664 bytes
548 files changed, 26142 insertions(+), 19585 deletions(-)
diff --git a/artifact-paths.json b/artifact-paths.json
index 99fd2a836f..fa11c8e4ba 100644
--- a/artifact-paths.json
+++ b/artifact-paths.json
@@ -24,6 +24,7 @@
"juneau-distrib": "juneau-distrib",
"juneau-examples": "juneau-examples",
"juneau-examples-core": "juneau-examples/juneau-examples-core/build-overlay",
+ "juneau-examples-guides": "juneau-examples/juneau-examples-guides",
"juneau-examples-mcp": "juneau-examples/juneau-examples-mcp/build-overlay",
"juneau-integration-tests": "juneau-integration-tests",
"juneau-marshall": "juneau-core/juneau-marshall",
diff --git a/docusaurus.config.ts b/docusaurus.config.ts
index 9ea96f5995..f3820123ce 100644
--- a/docusaurus.config.ts
+++ b/docusaurus.config.ts
@@ -15,7 +15,6 @@ import * as fs from 'fs';
import * as path from 'path';
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
-import type {GiscusProps} from '@giscus/react';
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
@@ -56,33 +55,20 @@ const juneauModulePaths = loadArtifactMap('JUNEAU_ARTIFACT_PATHS', 'artifact-pat
// fallback. Artifacts absent from the map fall back to the aggregate index.
const juneauJavadocPackages = loadArtifactMap('JUNEAU_JAVADOC_PACKAGES', 'artifact-packages.json');
-type GiscusThemeConfig = Partial<GiscusProps>;
-
-type ThemeConfigWithGiscus = Preset.ThemeConfig & {
- giscus?: GiscusThemeConfig;
-};
-
// Site URL - used for external links that need to be environment-aware
const siteUrl = process.env.SITE_URL || 'https://juneau.apache.org';
-const giscusConfig: GiscusThemeConfig = {
- repo: 'apache/juneau',
- repoId: process.env.GISCUS_REPO_ID ?? 'MDEwOlJlcG9zaXRvcnk2NDczMjY2NQ==',
- category: process.env.GISCUS_CATEGORY ?? 'Announcements',
- categoryId: process.env.GISCUS_CATEGORY_ID ?? 'DIC_kwDOA9u9-c4CvzB6',
- mapping: 'pathname',
- reactionsEnabled: '1',
- emitMetadata: '0',
- strict: '0',
- inputPosition: 'top',
- theme: 'preferred_color_scheme',
- lang: 'en',
- loading: 'lazy',
-};
-
const baseThemeConfig: Preset.ThemeConfig = {
// Replace with your project's social card
image: 'img/docusaurus-social-card.jpg',
+ announcementBar: {
+ id: 'prerelease-10-0',
+ content:
+ 'These docs describe 10.0 (pre-release, 10.0.0-SNAPSHOT). The latest release is <a href="/downloads">9.2.0</a>.',
+ backgroundColor: '#fff3cd',
+ textColor: '#533f03',
+ isCloseable: false,
+ },
navbar: {
title: 'Apache Juneau',
logo: {
@@ -97,8 +83,21 @@ const baseThemeConfig: Preset.ThemeConfig = {
position: 'left',
label: 'Documentation',
},
+ {to: '/docs/guides', label: 'Guides', position: 'left', activeBasePath: '/docs/guides'},
+ {
+ type: 'docSidebar',
+ sidebarId: 'developerSidebar',
+ position: 'left',
+ label: 'Developer Info',
+ },
+ {
+ type: 'docSidebar',
+ sidebarId: 'releaseNotesSidebar',
+ position: 'left',
+ label: 'Release Notes',
+ },
{to: '/downloads', label: 'Downloads', position: 'left'},
- {to: '/security', label: 'Security', position: 'left'},
+ {to: '/security', label: 'Security Analysis', position: 'left'},
{to: '/apache', label: 'Apache', position: 'left'},
{
href: 'https://github.com/apache/juneau',
@@ -168,7 +167,7 @@ const baseThemeConfig: Preset.ThemeConfig = {
href: 'http://www.apache.org/licenses/',
},
{
- label: 'Security',
+ label: 'ASF Security',
href: 'http://www.apache.org/security',
},
],
@@ -184,10 +183,7 @@ const baseThemeConfig: Preset.ThemeConfig = {
},
} satisfies Preset.ThemeConfig;
-const themeConfig: ThemeConfigWithGiscus = {
- ...baseThemeConfig,
- giscus: giscusConfig,
-};
+const themeConfig: Preset.ThemeConfig = baseThemeConfig;
const config: Config = {
title: 'Apache Juneau',
@@ -209,6 +205,8 @@ const config: Config = {
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/',
+ // Every page is served from <path>/index.html, so sitemap, canonical URLs and the server agree on one URL form.
+ trailingSlash: true,
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
@@ -267,6 +265,12 @@ const config: Config = {
indexBlog: false,
indexPages: true,
docsRouteBasePath: '/docs',
+ // Contributor material and release history get their own indexes so a reader
+ // searching from the user docs does not see them (search from those pages instead).
+ searchContextByPaths: [
+ { label: 'Developer Info', path: 'docs/developer-info' },
+ { label: 'Release Notes', path: 'docs/release-notes' },
+ ],
},
],
],
diff --git a/package-lock.json b/package-lock.json
index 897c02619d..d6c037bbc9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,6 @@
"@docusaurus/core": "3.8.1",
"@docusaurus/preset-classic": "3.8.1",
"@docusaurus/theme-mermaid": "3.8.1",
- "@giscus/react": "^3.1.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"jsdom": "^27.0.0",
@@ -4260,18 +4259,6 @@
"tslib": "^2.4.0"
}
},
- "node_modules/@giscus/react": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/@giscus/react/-/react-3.1.0.tgz",
- "integrity": "sha512-0TCO2TvL43+oOdyVVGHDItwxD1UMKP2ZYpT6gXmhFOqfAJtZxTzJ9hkn34iAF/b6YzyJ4Um89QIt9z/ajmAEeg==",
- "dependencies": {
- "giscus": "^1.6.0"
- },
- "peerDependencies": {
- "react": "^16 || ^17 || ^18 || ^19",
- "react-dom": "^16 || ^17 || ^18 || ^19"
- }
- },
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -4394,21 +4381,6 @@
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
"license": "MIT"
},
- "node_modules/@lit-labs/ssr-dom-shim": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz",
- "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@lit/reactive-element": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz",
- "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@lit-labs/ssr-dom-shim": "^1.4.0"
- }
- },
"node_modules/@mdx-js/mdx": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz",
@@ -6278,7 +6250,8 @@
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
- "license": "MIT"
+ "license": "MIT",
+ "optional": true
},
"node_modules/@types/unist": {
"version": "3.0.3",
@@ -10442,15 +10415,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/giscus": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/giscus/-/giscus-1.6.0.tgz",
- "integrity": "sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ==",
- "license": "MIT",
- "dependencies": {
- "lit": "^3.2.1"
- }
- },
"node_modules/github-slugger": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz",
@@ -12122,37 +12086,6 @@
"uc.micro": "^2.0.0"
}
},
- "node_modules/lit": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.1.tgz",
- "integrity": "sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@lit/reactive-element": "^2.1.0",
- "lit-element": "^4.2.0",
- "lit-html": "^3.3.0"
- }
- },
- "node_modules/lit-element": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.1.tgz",
- "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@lit-labs/ssr-dom-shim": "^1.4.0",
- "@lit/reactive-element": "^2.1.0",
- "lit-html": "^3.3.0"
- }
- },
- "node_modules/lit-html": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.1.tgz",
- "integrity": "sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@types/trusted-types": "^2.0.2"
- }
- },
"node_modules/loader-runner": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
diff --git a/package.json b/package.json
index fb0f504090..e1c356648d 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,6 @@
"@docusaurus/core": "3.8.1",
"@docusaurus/preset-classic": "3.8.1",
"@docusaurus/theme-mermaid": "3.8.1",
- "@giscus/react": "^3.1.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"jsdom": "^27.0.0",
diff --git a/pages/developer-info/01.DeveloperLinks.md b/pages/developer-info/01.DeveloperLinks.md
index f475e9eb23..934afe3f62 100644
--- a/pages/developer-info/01.DeveloperLinks.md
+++ b/pages/developer-info/01.DeveloperLinks.md
@@ -12,7 +12,7 @@ Useful links for Apache Juneau developers:
- **[Confluence Blog](https://cwiki.apache.org/confluence/display/JUNEAU/Blog)** - Project updates and announcements
- **[Twitter](https://twitter.com/apachejuneau)** - Follow Apache Juneau on Twitter
- **[Slack](https://apachejuneau.slack.com)** - Real-time discussions and collaboration
-- **[Apache Mailing Lists](https://juneau.apache.org/community.html)** - Project communication
+- **[Apache Mailing Lists](https://lists.apache.org/list.html?dev@juneau.apache.org)** - Project communication
- **dev@juneau.apache.org** - Development discussions
- **commits@juneau.apache.org** - Commit notifications
- **users@juneau.apache.org** - User discussions
diff --git a/pages/developer-info/02.04.NewReleaseGuidelines.md b/pages/developer-info/02.04.NewReleaseGuidelines.md
index 6cee198da6..8eddde360e 100644
--- a/pages/developer-info/02.04.NewReleaseGuidelines.md
+++ b/pages/developer-info/02.04.NewReleaseGuidelines.md
@@ -231,7 +231,7 @@ Tick the correct **staging repository** (perhaps do a quick check of an SHA1 sum
Wait 24 hours for the download mirrors to pick up the new release from **dist.apache.org**, otherwise eager users (or users who just happened to want to download that day) may get **404 Not Found** errors.
-Update (or make) the corresponding pages under **downloads.html** including the correct version number for the Maven dependencies and the release dates.
+Update (or make) the corresponding pages under **downloads.html** including the correct version number for the Maven dependencies and the release dates. Also update `LATEST_RELEASE` in `src/pages/index.tsx` (the home page's "Latest release" strip) and the announcement bar in `docusaurus.config.ts`.
Download links should be using the mirror redirector **https://www.apache.org/dyn/closer.cgi**, e.g. **https://www.apache.org/dyn/closer.cgi/juneau/source/juneau-7.0.0/juneau-7.0.0-source-release.zip**
diff --git a/pages/developer-info/02.09.AiKnowledgeAuthoring.md b/pages/developer-info/02.09.AiKnowledgeAuthoring.md
index 23b6954ab8..b248e9aab6 100644
--- a/pages/developer-info/02.09.AiKnowledgeAuthoring.md
+++ b/pages/developer-info/02.09.AiKnowledgeAuthoring.md
@@ -61,10 +61,66 @@ The first command regenerates deterministic artifacts. The second verifies:
- Javadocs link paths in docs config are consistent,
- canonical release-note source for current release exists.
+## Java Code Fences
+
+Every ` ```java ` fence is parsed by `scripts/check-java-fences.py` (JDK parser only: no symbol resolution, no
+compile). The docs build and the docs gate run it with `--strict`, so a fence that is not valid Java fails the build.
+
+A fence passes if it parses as a compilation unit, as class-body members, as statements, or as a sequence of
+those (for example a class declaration followed by loose statements).
+
+When a snippet is deliberately not complete Java, add the word `fragment` to the fence info string:
+
+````markdown
+```java fragment
+.setMaximum(100)
+.setExclusiveMaximum(true)
+```
+````
+
+Use `fragment` for:
+
+- placeholders such as `...` or `{...}`,
+- a method-chain tail, a bare expression, or a signature without a body,
+- an annotation or constructor shown without its enclosing class.
+
+Do not use it to hide a real mistake (unbalanced braces, a stray `;`, a missing parenthesis): fix the sample.
+If the content is not Java at all (properties, XML, shell), give the fence its real language instead.
+The word is invisible in the rendered page; Docusaurus highlights the fence as Java either way.
+
+## Guide Fences
+
+Guide pages (`pages/guides/`) show code that lives in the Juneau repository's `juneau-examples/juneau-examples-guides`
+test module, which the Juneau build compiles and tests. Each fence names its source with a `guide=FILE#REGION` word
+in the info string (the class here is illustrative, not one of the guides):
+
+````markdown
+```java guide=PetResource.java#resource
+@Rest(path="/pets")
+public class PetResource extends BasicRestResource { ... }
+```
+````
+
+- `FILE` is a file under the module's `src/test/java` or `src/test/resources`. A bare file name (`PetResource.java`)
+ must match exactly one file; a relative path (`SpringBootRestApi/pom.xml`) is tried under each of the two roots.
+- `REGION` is the text between the comment lines `region NAME` and `endregion NAME` in that file, written as `//`,
+ `#` or `<!-- -->` comments.
+- `scripts/check-guide-snippets.py` compares each fence with its region, after expanding tabs, dropping trailing
+ spaces, the common indent and leading and trailing blank lines, and prints a diff when they differ. The docs build
+ (Links stage) and the docs gate run it.
+- On a guide page every fence must carry a pointer unless its language is `bash`, `sh`, `shell`, `console` or `text`.
+ A `text` fence may carry one, so the "Run it" output can point at a file the guide's test asserts against.
+- `guide=` and `fragment` are independent: a guide's ` ```java ` fence is still parse-checked unless it is also
+ marked `fragment`.
+
+To change a guide's code, edit the module file first, run the module's tests
+(`mvn -q test -f juneau-examples/juneau-examples-guides/pom.xml`), then copy the region into the page.
+
## Definition Of Done (Docs-Affecting PRs)
- New or changed feature docs are added under canonical source locations.
- AI artifacts are regenerated if source content changed.
- `check-ai-artifacts.py` passes locally.
+- `check-java-fences.py --strict` passes locally.
- `summary` text is concise and `description` adds non-redundant detail.
- Source provenance remains traceable via `source_path` in JSONL output.
diff --git a/pages/developer-info/02.10.TopicPageTemplate.md b/pages/developer-info/02.10.TopicPageTemplate.md
new file mode 100644
index 0000000000..af654c469d
--- /dev/null
+++ b/pages/developer-info/02.10.TopicPageTemplate.md
@@ -0,0 +1,80 @@
+---
+title: "Topic Page Template"
+slug: TopicPageTemplate
+---
+
+This page defines the layout of a reference topic under `pages/topics`: five parts in a fixed order, and the voice they are written in.
+[JSON Serializers](/docs/topics/JsonSerializers) and [JSON Parsers](/docs/topics/JsonParsers) follow it and are the worked examples.
+
+## The five parts
+
+A reference topic has up to five parts, in this order. Skip a part that does not apply; never reorder them.
+
+| # | Part | Heading | Content |
+|---|---|---|---|
+| 1 | What it is | none | One or two sentences, as the page's first paragraph. The page title already names the topic, so this part has no heading. `generate-ai-artifacts.py` uses this paragraph as the page summary. |
+| 2 | When to use it | `## When to use it` | The situations the API is for, and the neighbouring API to use otherwise. |
+| 3 | A complete example | `## Example` | A complete `java` fence where possible: a whole class or method, not the tail of a builder chain. Put it under the heading, not in a `:::tip Example` admonition. |
+| 4 | Options and configuration | `## Options` | A table of up to about eight builder methods or annotation attributes readers reach for first, with their defaults, then a link to the builder or annotation Javadoc for the full list. For a serializer or parser, the pre-configured instances (`DEFAULT`, `DEFAULT_READABLE`, ...) count as options. Skip the part when there is nothing to configure. |
+| 5 | See also | `## See also` | The guide that covers the topic, when one exists, first; then related topics. |
+
+A short topic may stay under 150 words when each part is short. The example is what matters.
+
+## Voice
+
+- Present tense, second person: say what the API does and what you do with it.
+- No tutorial narrative ("let's go back to our Person bean"); that belongs in a guide.
+- No maintainer notes (commit hashes, snapshot pins, "TODO" remarks).
+
+## Examples
+
+Fences follow the [AI Knowledge Authoring Guide](/docs/developer-info/AiKnowledgeAuthoring): every `java` fence is parsed by `check-java-fences.py --strict`, and `fragment` is allowed only for the cases that guide lists.
+The check parses the code but does not compile it or resolve names, so check every class and method name against the current Javadoc by hand.
+
+## Skeleton
+
+The [JSON Serializers](/docs/topics/JsonSerializers) page has this shape:
+
+````markdown
+---
+title: "JSON Serializers"
+slug: JsonSerializers
+---
+
+The JsonSerializer class serializes POJOs into JSON text.
+
+## When to use it
+
+Use JsonSerializer when ... For a one-line conversion, use the Json marshaller instead.
+
+## Example
+
+```java
+public class JsonSerializerExample {
+ public static void main(String[] args) throws Exception {
+ String json = JsonSerializer.DEFAULT.write(new Person());
+ }
+}
+```
+
+## Options
+
+| Instance or builder method | Effect |
+|---|---|
+| `JsonSerializer.DEFAULT_READABLE` | JSON with whitespace and indentation. |
+| `keepNullProperties()` | Write bean properties whose value is `null`. |
+
+## See also
+
+- [JSON Parsers](/docs/topics/JsonParsers)
+````
+
+## Checking a page
+
+`scripts/check-topic-template.py` reports pages under 150 words, pages with no code fence, and pages whose template headings are out of order. It is advisory and always exits 0.
+
+```bash
+python3 scripts/check-topic-template.py --chapter 03
+```
+
+It checks structure only; review the voice by hand.
diff --git a/pages/developer-info/04.01.Scripts.md b/pages/developer-info/04.01.Scripts.md
index 302e0802db..adb96c460d 100644
--- a/pages/developer-info/04.01.Scripts.md
+++ b/pages/developer-info/04.01.Scripts.md
@@ -53,8 +53,11 @@ These scripts are located in the `docs` branch (the `juneau-docs` repository/che
| [create-mvn-site.py](/docs/developer-info/ScriptCreateMvnSite) | Generate Maven site for testing | [Learn more →](/docs/developer-info/ScriptCreateMvnSite) |
| [start-docusaurus.py](/docs/developer-info/ScriptStartDocusaurus) | Start Docusaurus dev server | [Learn more →](/docs/developer-info/ScriptStartDocusaurus) |
| [check-topic-links.py](/docs/developer-info/ScriptCheckTopicLinks) | Validate documentation links | [Learn more →](/docs/developer-info/ScriptCheckTopicLinks) |
+| `check-guide-snippets.py` | Checks that every code block on a guide page matches its tested region in the Juneau checkout's `juneau-examples-guides` module | See [AI Knowledge Authoring](/docs/developer-info/AiKnowledgeAuthoring) ("Guide Fences") |
| [generate-ai-artifacts.py / check-ai-artifacts.py](/docs/developer-info/ScriptAiArtifacts) | Generate and validate machine-consumable AI knowledge artifacts | [Learn more →](/docs/developer-info/ScriptAiArtifacts) |
-| `apply-renames.py` | Mechanical bulk URL/text substitution pass across `pages/` | — |
+| `apply-renames.py` | Bulk URL/text substitution pass across `pages/`, driven by a rules table (`--table`, `--dry-run`) | — |
+| `check-topic-template.py` | Advisory report of topic pages that drift from the [topic page template](/docs/developer-info/TopicPageTemplate): stubs, pages with no code fence, headings out of order | — |
+| `renumber-topics.py` | Renumbers `pages/topics` files gap-free in sidebar order and patches `sidebars.ts` (dry run unless `--apply`) | — |
| `normalize-headings.py` | Normalizes Markdown heading structure under `pages/topics/` | — |
<div class="info">
diff --git a/pages/developer-info/04.03.03.ScriptBuildDocs.md b/pages/developer-info/04.03.03.ScriptBuildDocs.md
index 528df7677a..54781ba80f 100644
--- a/pages/developer-info/04.03.03.ScriptBuildDocs.md
+++ b/pages/developer-info/04.03.03.ScriptBuildDocs.md
@@ -78,7 +78,7 @@ When the Juneau checkout includes [juneau-run](/docs/topics/JuneauRun), the scri
| Copy | Current javadocs into `static/javadocs/<version>/` and the Maven site into `static/site/` |
| Docs build | `npm run build` |
| Verify | `verify-docs.py` |
-| Links | `check-topic-links.py` and `check-ai-artifacts.py` |
+| Links | `check-topic-links.py`, `check-ai-artifacts.py`, `check-code-fences.py`, `check-java-fences.py --strict` and `check-guide-snippets.py` |
A stage turned off by a `--skip-*` flag (or by a running dev server, for npm and Docs build) shows as a
skipped row with the reason. A failing stage turns its row red, with the tool's failure lines under it.
diff --git a/pages/guides/00.Guides.md b/pages/guides/00.Guides.md
new file mode 100644
index 0000000000..0d16d626ce
--- /dev/null
+++ b/pages/guides/00.Guides.md
@@ -0,0 +1,19 @@
+---
+id: 00.Guides
+title: "Guides"
+slug: /guides
+---
+
+Each guide does one task end to end: what you need first, the complete code, how to run it, and where to go next.
+
+New to Juneau? Work through [Getting Started](/docs/topics/GettingStarted) first; the guides assume you have.
+
+1. [Build a REST API on Spring Boot](/docs/guides/SpringBootRestApi): a JSON pet API on Spring Boot's Tomcat, with a Spring bean injected into the resource. About 15 minutes.
+2. [Secure an endpoint with JWT and OAuth](/docs/guides/SecureAnEndpoint): require a bearer token, accept JWTs and opaque OAuth tokens, and check scopes. About 20 minutes.
+3. [Test an endpoint with MockRestClient](/docs/guides/TestAnEndpoint): unit-test a REST resource in-process, with no server or port. About 10 minutes.
+4. [Call an existing REST API with a typed proxy](/docs/guides/TypedProxyClient): describe an API as a Java interface and call it through the next-generation client. About 15 minutes.
+5. [Serialize a type Juneau doesn't know](/docs/guides/CustomSwaps): write a swap so a class you can't change round-trips through JSON. About 10 minutes.
+6. [Externalize config with INI/YAML and variables](/docs/guides/ExternalizeConfig): move settings into an INI or YAML file and fill them from system properties and the environment. About 15 minutes.
+7. [Generate OpenAPI docs for your API](/docs/guides/OpenApiDocs): serve OpenAPI 3.1, Swagger v2, Redoc and Swagger UI from your resource's annotations. About 10 minutes.
+8. [Building a Console Page](/docs/topics/BuildingAConsolePage): add a page to a Juneau admin console, from the host resource and templates to a table card fed by a data endpoint. About 30 minutes.
+9. [MCP Quickstart](/docs/topics/JuneauMcpQuickstart): get a Model Context Protocol server and client talking to each other. About 5 minutes.
diff --git a/pages/guides/01.SpringBootRestApi.md b/pages/guides/01.SpringBootRestApi.md
new file mode 100644
index 0000000000..75d3e52198
--- /dev/null
+++ b/pages/guides/01.SpringBootRestApi.md
@@ -0,0 +1,410 @@
+---
+id: 01.SpringBootRestApi
+title: "Build a REST API on Spring Boot"
+slug: SpringBootRestApi
+---
+
+## What you'll build
+
+A small pet API that runs on Spring Boot's embedded Tomcat. It lists pets, returns one pet by id and adds a
+pet, and the same methods answer in JSON, XML, HTML and the other formats Juneau registers. The pet store is
+an ordinary Spring bean that Spring injects into the Juneau resource. When you are done,
+`curl -H "Accept: application/json" http://localhost:8080/pets/1` returns `{"id":1,"name":"Fido"}`.
+
+## Prerequisites and time
+
+- Java 17 or newer and Maven 3.9 or newer.
+- `org.apache.juneau:juneau-rest-server-springboot:10.0.0-SNAPSHOT`, which brings `juneau-rest-server` and
+ `juneau-marshall` with it.
+- `org.springframework.boot:spring-boot-starter-web:4.1.1`, plus the `spring-boot-maven-plugin` to run the app.
+- Port 8080 free.
+- About 15 minutes.
+
+`10.0.0-SNAPSHOT` needs the Apache snapshots repository, which the `pom.xml` below declares; Maven Central has
+only the 9.2.0 release today (see [Install Juneau](/docs/topics/GettingStartedInstall) and [Downloads](/downloads)).
+
+## Steps
+
+### 1. Create the project
+
+Make an empty directory and save this as `pom.xml`:
+
+```xml guide=SpringBootRestApi/pom.xml#pom
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.example</groupId>
+ <artifactId>pets</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <maven.compiler.release>17</maven.compiler.release>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <juneau.version>10.0.0-SNAPSHOT</juneau.version>
+ <spring-boot.version>4.1.1</spring-boot.version>
+ </properties>
+
+ <repositories>
+ <repository>
+ <id>apache-snapshots</id>
+ <url>https://repository.apache.org/snapshots/</url>
+ <releases><enabled>false</enabled></releases>
+ <snapshots><enabled>true</enabled></snapshots>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-springboot</artifactId>
+ <version>${juneau.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <version>${spring-boot.version}</version>
+ <exclusions>
+ <!-- Juneau does the JSON, so Jackson is not needed. -->
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-jackson</artifactId>
+ </exclusion>
+ <!-- Spring Boot then logs through java.util.logging. -->
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring-boot.version}</version>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+```
+
+Put the Java files from the next steps in `src/main/java/com/example/pets/`, and start each one with
+`package com.example.pets;`.
+
+### 2. Write the bean
+
+`Pet.java` is a plain bean. Juneau serializes its public fields, so it needs no annotations:
+
+```java guide=Pet.java#pet
+/**
+ * A pet, serialized as {@code {"id":1,"name":"Fido"}}.
+ */
+public class Pet {
+
+ /** The pet's id, assigned by {@link PetStore}. */
+ public int id;
+
+ /** The pet's name. */
+ public String name;
+
+ /** Used by the parser. */
+ public Pet() {}
+
+ /**
+ * Creates a pet.
+ *
+ * @param id The id.
+ * @param name The name.
+ */
+ public Pet(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+}
+```
+
+### 3. Add a store
+
+`PetStore.java` keeps the pets in memory. Spring creates one instance and shares it:
+
+```java guide=PetStore.java#store
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+/**
+ * An in-memory pet store. Spring creates one and injects it into {@link PetResource}.
+ */
+public class PetStore {
+
+ private final Map<Integer,Pet> pets = new ConcurrentSkipListMap<>();
+ private final AtomicInteger nextId = new AtomicInteger(1);
+
+ /** Creates the store with one pet in it. */
+ public PetStore() {
+ add("Fido");
+ }
+
+ /**
+ * Adds a pet.
+ *
+ * @param name The pet's name.
+ * @return The new pet, with its id.
+ */
+ public Pet add(String name) {
+ var pet = new Pet(nextId.getAndIncrement(), name);
+ pets.put(pet.id, pet);
+ return pet;
+ }
+
+ /**
+ * Looks up a pet.
+ *
+ * @param id The id.
+ * @return The pet, or empty if there is none with that id.
+ */
+ public Optional<Pet> get(int id) {
+ return Optional.ofNullable(pets.get(id));
+ }
+
+ /**
+ * Returns every pet, in id order.
+ *
+ * @return The pets.
+ */
+ public List<Pet> list() {
+ return new ArrayList<>(pets.values());
+ }
+}
+```
+
+### 4. Write the resource
+
+`PetResource.java` is the API. It extends `BasicRestResource`, the base class for child resources, and takes
+the store in its constructor:
+
+```java guide=PetResource.java#resource
+import java.util.*;
+
+import org.apache.juneau.http.*;
+import org.apache.juneau.http.response.*;
+import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.servlet.*;
+import org.springframework.beans.factory.annotation.*;
+
+/**
+ * The pet API, mounted at {@code /pets} under {@link PetRootServlet}.
+ */
+@Rest(path = "/pets", title = "Pets")
+public class PetResource extends BasicRestResource {
+
+ private final PetStore store;
+
+ /**
+ * Called by Spring.
+ *
+ * @param store The pet store bean.
+ */
+ @Autowired
+ public PetResource(PetStore store) {
+ this.store = store;
+ }
+
+ /**
+ * Lists the pets.
+ *
+ * @return Every pet.
+ */
+ @RestGet("/")
+ public List<Pet> listPets() {
+ return store.list();
+ }
+
+ /**
+ * Returns one pet.
+ *
+ * @param id The pet id from the URL.
+ * @return The pet.
+ * @throws NotFound If there is no pet with that id.
+ */
+ @RestGet("/{id}")
+ public Pet getPet(@Path("id") int id) throws NotFound {
+ return store.get(id).orElseThrow(() -> new NotFound("No pet with id " + id));
+ }
+
+ /**
+ * Adds a pet.
+ *
+ * @param pet The pet to add; only {@code name} is used.
+ * @return The new pet, with its id.
+ */
+ @RestPost("/")
+ public Pet addPet(@Content Pet pet) {
+ return store.add(pet.name);
+ }
+}
+```
+
+### 5. Add the root servlet
+
+`PetRootServlet.java` is the servlet Spring Boot mounts. It extends `BasicSpringRestServletGroup`, so its
+children are looked up as Spring beans:
+
+```java guide=PetRootServlet.java#root
+import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.springboot.*;
+
+/**
+ * The servlet Spring Boot mounts. Its children are looked up as Spring beans.
+ */
+@Rest(title = "Pet service", children = { PetResource.class })
+public class PetRootServlet extends BasicSpringRestServletGroup {
+ private static final long serialVersionUID = 1L;
+}
+```
+
+### 6. Wire it into Spring Boot
+
+`PetApplication.java` declares the beans and mounts the root servlet at `/*`:
+
+```java guide=PetApplication.java#application
+import org.springframework.boot.autoconfigure.*;
+import org.springframework.boot.builder.*;
+import org.springframework.boot.web.servlet.*;
+import org.springframework.context.annotation.*;
+
+/**
+ * Starts Spring Boot with the pet API on embedded Tomcat (port 8080 by default).
+ */
+@SpringBootApplication
+public class PetApplication {
+
+ /**
+ * Starts the application.
+ *
+ * @param args Spring Boot arguments, for example {@code --server.port=9090}.
+ */
+ public static void main(String[] args) {
+ new SpringApplicationBuilder(PetApplication.class).run(args);
+ }
+
+ /**
+ * The store shared by every request.
+ *
+ * @return A new store.
+ */
+ @Bean
+ public PetStore petStore() {
+ return new PetStore();
+ }
+
+ /**
+ * The pet API, as a Spring bean so the store is injected.
+ *
+ * @param store The store bean.
+ * @return The resource.
+ */
+ @Bean
+ public PetResource petResource(PetStore store) {
+ return new PetResource(store);
+ }
+
+ /**
+ * The root servlet.
+ *
+ * @return The servlet.
+ */
+ @Bean
+ public PetRootServlet petRootServlet() {
+ return new PetRootServlet();
+ }
+
+ /**
+ * Mounts the root servlet at {@code /*}, so the API answers at {@code /pets}.
+ *
+ * @param servlet The root servlet bean.
+ * @return The registration.
+ */
+ @Bean
+ public ServletRegistrationBean<PetRootServlet> petServlet(PetRootServlet servlet) {
+ return new ServletRegistrationBean<>(servlet, "/*");
+ }
+}
+```
+
+## Run it
+
+Start the app:
+
+```bash
+mvn spring-boot:run
+```
+
+When the log shows `Tomcat started on port 8080`, call it from a second terminal:
+
+```bash
+curl -H "Accept: application/json" http://localhost:8080/pets/1
+```
+
+```text guide=SpringBootRestApi/run-output.txt#get-pet
+{"id":1,"name":"Fido"}
+```
+
+Add a pet:
+
+```bash
+curl -H "Accept: application/json" -H "Content-Type: application/json" -d '{"name":"Rex"}' http://localhost:8080/pets
+```
+
+```text guide=SpringBootRestApi/run-output.txt#add-pet
+{"id":2,"name":"Rex"}
+```
+
+List them:
+
+```bash
+curl -H "Accept: application/json" http://localhost:8080/pets
+```
+
+```text guide=SpringBootRestApi/run-output.txt#list-pets
+[{"id":1,"name":"Fido"},{"id":2,"name":"Rex"}]
+```
+
+Ask for a pet that does not exist and print only the status code:
+
+```bash
+curl -s -o /dev/null -w "%{http_code}\n" -H "Accept: application/json" http://localhost:8080/pets/99
+```
+
+```text guide=SpringBootRestApi/run-output.txt#missing-pet
+404
+```
+
+Leave out the `Accept` header, or open `http://localhost:8080/pets` in a browser, and you get the HTML view
+of the same data. Press Ctrl+C to stop the app.
+
+## How it works
+
+- **Mounting.** Spring Boot registers `PetRootServlet` through the `ServletRegistrationBean`, the same pattern
+ [Spring Boot Overview](/docs/topics/SpringBootOverview) uses. If you would rather not write that bean, the
+ opt-in auto-configuration (`juneau.rest.auto-register=true`) mounts servlets from their own `@Rest`
+ paths; see [REST Server Self-Registration](/docs/topics/RestServerSelfRegistration).
+- **Spring beans as children.** `BasicSpringRestServletGroup` resolves each class in `children` from the
+ Spring context before it tries to create one itself, so `PetResource` is the `petResource` bean with
+ `PetStore` already injected. The module and its auto-configuration classes are described in
+ [juneau-rest-server-springboot](/docs/topics/JuneauRestServerSpringboot).
+- **Operations.** `@RestGet` and `@RestPost` map methods to HTTP verbs and paths, `@Path` binds the `{id}`
+ segment, and `@Content` parses the request body into a `Pet`. Throwing `NotFound` sends a 404. The same
+ annotations are introduced in [Your first REST app](/docs/topics/GettingStartedRestApp).
+- **Formats.** `BasicRestResource` carries Juneau's universal configuration, so the `Accept` header picks the
+ serializer. JSON is Juneau's job here, which is why the `pom.xml` excludes Spring's Jackson starter.
+
+## Next steps
+
+- [Test an endpoint with MockRestClient](/docs/guides/TestAnEndpoint): test resources like this one without
+ starting a server.
+- [Secure an endpoint with JWT and OAuth](/docs/guides/SecureAnEndpoint): put a guard in front of the API.
+- [Generate OpenAPI docs for your API](/docs/guides/OpenApiDocs): publish a description of it.
diff --git a/pages/guides/02.SecureAnEndpoint.md b/pages/guides/02.SecureAnEndpoint.md
new file mode 100644
index 0000000000..1e2092cd31
--- /dev/null
+++ b/pages/guides/02.SecureAnEndpoint.md
@@ -0,0 +1,390 @@
+---
+id: 02.SecureAnEndpoint
+title: "Secure an endpoint with JWT and OAuth"
+slug: SecureAnEndpoint
+---
+
+Protect a REST endpoint so that only callers with a valid bearer token and the right scope get through. The same endpoint accepts signed JWTs, checked locally against the issuer's public keys, and opaque tokens, checked by an OAuth 2.0 introspection call.
+
+## What you'll build
+
+An `Orders API` with one endpoint, `GET /orders`. A bearer-token guard runs before the endpoint and rejects missing, forged, expired and wrong-audience tokens with `401`. The endpoint then checks for the `orders:read` scope and answers `403` without it. To stay self-contained, the guide issues its own JWTs from a generated RSA key and runs a small local stand-in for the introspection endpoint, so you don't need a real identity provider.
+
+When you run it, you'll see one line per kind of caller:
+
+```text
+no token: 401 Bearer realm="orders"
+valid JWT: 200 "orders for alice"
+...
+```
+
+## Prerequisites and time
+
+- Java 17 or later and Maven 3.
+- These dependencies, all shown in the POM in step 1:
+ - `org.apache.juneau:juneau-rest-server-auth-jwt:10.0.0-SNAPSHOT`
+ - `org.apache.juneau:juneau-rest-server-auth-oauth:10.0.0-SNAPSHOT`
+ - `org.apache.juneau:juneau-rest-mock:10.0.0-SNAPSHOT`, used here to call the endpoint without starting a server
+ - `com.nimbusds:nimbus-jose-jwt:10.9.1` and `com.nimbusds:oauth2-oidc-sdk:11.38.2`. The two Juneau auth modules mark these as `provided`, so you declare them yourself.
+- About 20 minutes.
+
+`10.0.0-SNAPSHOT` is not on Maven Central, so the POM adds the Apache snapshots repository. See the note in the [MCP Quickstart](/docs/topics/JuneauMcpQuickstart) and [Install Juneau](/docs/topics/GettingStartedInstall).
+
+## Steps
+
+Put each class in `src/main/java/com/example/` and start each file with `package com.example;`. Any package works if you use it everywhere.
+
+### 1. Create the project
+
+Create an empty directory with this `pom.xml`:
+
+```xml guide=SecureAnEndpoint/pom.xml#pom
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.example</groupId>
+ <artifactId>secureanendpoint</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <maven.compiler.release>17</maven.compiler.release>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <juneau.version>10.0.0-SNAPSHOT</juneau.version>
+ </properties>
+
+ <repositories>
+ <repository>
+ <id>apache-snapshots</id>
+ <url>https://repository.apache.org/snapshots/</url>
+ <releases><enabled>false</enabled></releases>
+ <snapshots><enabled>true</enabled></snapshots>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-auth-jwt</artifactId>
+ <version>${juneau.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ <version>10.9.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-auth-oauth</artifactId>
+ <version>${juneau.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>oauth2-oidc-sdk</artifactId>
+ <version>11.38.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-mock</artifactId>
+ <version>${juneau.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>3.6.4</version>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+```
+
+### 2. Write the protected resource
+
+The resource registers a `BearerTokenGuard` as a bean. The guard reads the `Authorization: Bearer ...` header, hands the token to a `TokenValidator`, and either rejects the request with `401` and a `WWW-Authenticate` challenge or stores the validated claims for the endpoint. The endpoint receives them as an `@Auth ClaimsPrincipal` and does its own scope check.
+
+```java guide=SecureResource.java#resource
+import java.util.*;
+
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.http.response.*;
+import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.auth.*;
+import org.apache.juneau.rest.server.config.*;
+import org.apache.juneau.rest.server.guard.*;
+import org.apache.juneau.rest.server.servlet.*;
+
+/**
+ * An endpoint that only answers requests carrying a valid bearer token.
+ */
+@Rest(title="Orders API")
+public class SecureResource extends RestServlet implements BasicJsonConfig {
+
+ private static final long serialVersionUID = 1L;
+
+ private final transient TokenValidator validator;
+
+ public SecureResource(TokenValidator validator) {
+ this.validator = validator;
+ }
+
+ // Every request must pass this guard before any @RestOp method runs.
+ @Bean
+ public RestGuardList guards(BeanStore beanStore) {
+ return RestGuardList.create(beanStore)
+ .append(BearerTokenGuard.create().realm("orders").validator(validator).build())
+ .build();
+ }
+
+ @RestGet(path="/orders")
+ public String orders(@Auth ClaimsPrincipal caller) {
+ if (! hasScope(caller, "orders:read"))
+ throw new Forbidden("Missing scope orders:read");
+ return "orders for " + caller.getName();
+ }
+
+ private static boolean hasScope(ClaimsPrincipal caller, String wanted) {
+ // A JWT carries "scope" as a space-separated string; an introspection result carries it as a list.
+ var scope = caller.getClaims().get("scope");
+ if (scope instanceof Collection<?> c)
+ return c.contains(wanted);
+ return scope != null && Arrays.asList(scope.toString().split(" ")).contains(wanted);
+ }
+}
+```
+
+### 3. Choose how tokens are validated
+
+`JwtTokenValidator` checks a JWT's signature against the issuer's JSON Web Key Set, and checks its issuer, audience and expiry. In production, point it at the issuer's JWKS URL. `OAuthIntrospectionValidator` posts an opaque token to the authorization server's introspection endpoint and requires the listed scopes. `either` sends anything shaped like a JWT (three dot-separated parts) to the first and everything else to the second.
+
+```java guide=SecureValidators.java#validators
+import java.net.*;
+
+import org.apache.juneau.rest.server.auth.*;
+import org.apache.juneau.rest.server.auth.jwt.*;
+import org.apache.juneau.rest.server.auth.oauth.*;
+
+import com.nimbusds.jose.jwk.source.*;
+import com.nimbusds.jose.proc.*;
+
+/**
+ * The token validators the endpoint can use.
+ */
+public final class SecureValidators {
+
+ public static final String ISSUER = "https://issuer.example.com/";
+ public static final String AUDIENCE = "https://api.example.com";
+
+ private SecureValidators() {}
+
+ /** Production: verify JWTs against the identity provider's published keys. */
+ public static TokenValidator jwt(URI jwksUrl) {
+ return JwtTokenValidator.create().jwksUrl(jwksUrl).issuer(ISSUER).audience(AUDIENCE).build();
+ }
+
+ /** Same checks, with the keys supplied directly (used by the test). */
+ public static TokenValidator jwt(JWKSource<SecurityContext> keys) {
+ return JwtTokenValidator.create().jwkSource(keys).issuer(ISSUER).audience(AUDIENCE).build();
+ }
+
+ /** Opaque OAuth 2.0 tokens: ask the authorization server whether the token is active (RFC 7662). */
+ public static TokenValidator introspection(URI endpoint, String clientId, String clientSecret) {
+ return OAuthIntrospectionValidator.create()
+ .introspectionEndpoint(endpoint)
+ .clientId(clientId)
+ .clientSecret(clientSecret)
+ .requiredScopes("orders:read")
+ .build();
+ }
+
+ /** Accept either kind: a JWT has three dot-separated parts, an opaque token doesn't. */
+ public static TokenValidator either(TokenValidator jwt, TokenValidator opaque) {
+ return token -> token.split("\\.", -1).length == 3 ? jwt.validate(token) : opaque.validate(token);
+ }
+}
+```
+
+### 4. Add a test token issuer
+
+To run without an identity provider, generate an RSA key pair, sign tokens with the private key, and publish the public key as the key set the validator trusts. A real issuer does exactly this on its side.
+
+```java guide=TokenIssuer.java#issuer
+import java.time.*;
+import java.util.*;
+
+import com.nimbusds.jose.*;
+import com.nimbusds.jose.crypto.*;
+import com.nimbusds.jose.jwk.*;
+import com.nimbusds.jose.jwk.gen.*;
+import com.nimbusds.jwt.*;
+
+/**
+ * Stands in for your identity provider: owns an RSA key pair and signs short-lived JWTs with it.
+ */
+public class TokenIssuer {
+
+ private final RSAKey key;
+
+ public TokenIssuer() throws JOSEException {
+ key = new RSAKeyGenerator(2048).keyID("demo-key").algorithm(JWSAlgorithm.RS256).generate();
+ }
+
+ /** The public half, as a JWKS document would publish it. */
+ public JWKSet publicKeys() {
+ return new JWKSet(key.toPublicJWK());
+ }
+
+ public String issue(String subject, String scope, String audience) throws JOSEException {
+ var now = Instant.now();
+ var claims = new JWTClaimsSet.Builder()
+ .issuer(SecureValidators.ISSUER)
+ .audience(audience)
+ .subject(subject)
+ .claim("scope", scope)
+ .issueTime(Date.from(now))
+ .notBeforeTime(Date.from(now))
+ .expirationTime(Date.from(now.plus(Duration.ofMinutes(5))))
+ .build();
+ var jwt = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(key.getKeyID()).build(), claims);
+ jwt.sign(new RSASSASigner(key));
+ return jwt.serialize();
+ }
+}
+```
+
+### 5. Add a local introspection endpoint
+
+A tiny JDK HTTP server stands in for the authorization server's introspection endpoint. It treats `opaque-good` as an active token with the `orders:read` scope, and every other token as revoked.
+
+```java guide=TokenIntrospectionStub.java#stub
+import java.io.*;
+import java.net.*;
+import java.nio.charset.*;
+
+import com.sun.net.httpserver.*;
+
+/**
+ * Stands in for an OAuth 2.0 authorization server's introspection endpoint.
+ * The token "opaque-good" is active with scope orders:read; every other token is inactive.
+ */
+public class TokenIntrospectionStub implements Closeable {
+
+ private final HttpServer server;
+
+ public TokenIntrospectionStub() throws IOException {
+ server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
+ server.createContext("/introspect", exchange -> {
+ var form = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
+ var body = form.contains("token=opaque-good")
+ ? "{\"active\":true,\"sub\":\"reporting-job\",\"scope\":\"orders:read\"}"
+ : "{\"active\":false}";
+ var bytes = body.getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().add("Content-Type", "application/json");
+ exchange.sendResponseHeaders(200, bytes.length);
+ try (var out = exchange.getResponseBody()) {
+ out.write(bytes);
+ }
+ });
+ server.start();
+ }
+
+ public URI endpoint() {
+ return URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/introspect");
+ }
+
+ @Override
+ public void close() {
+ server.stop(0);
+ }
+}
+```
+
+### 6. Call the endpoint as different callers
+
+`MockRestClient` calls the resource in-process, with no servlet container. Each call sends a different token.
+
+```java guide=SecureDemo.java#demo
+import java.util.*;
+
+import org.apache.juneau.rest.mock.classic.*;
+
+import com.nimbusds.jose.jwk.source.*;
+
+/**
+ * Calls the secured endpoint in-process with no token, good tokens and bad tokens.
+ */
+public class SecureDemo {
+
+ public static void main(String[] args) throws Exception {
+ System.out.println(run());
+ }
+
+ public static String run() throws Exception {
+ var issuer = new TokenIssuer();
+ try (var authServer = new TokenIntrospectionStub()) {
+ var validator = SecureValidators.either(
+ SecureValidators.jwt(new ImmutableJWKSet<>(issuer.publicKeys())),
+ SecureValidators.introspection(authServer.endpoint(), "orders-api", "change-me"));
+
+ try (var client = MockRestClient.buildLax(new SecureResource(validator))) {
+ var lines = new ArrayList<String>();
+
+ var res = client.get("/orders").run();
+ lines.add("no token: " + res.getStatusCode() + " " + res.getHeader("WWW-Authenticate").asString().orElse(""));
+
+ var good = issuer.issue("alice", "orders:read", SecureValidators.AUDIENCE);
+ lines.add("valid JWT: " + call(client, good));
+
+ var otherApi = issuer.issue("alice", "orders:read", "https://other.example.com");
+ lines.add("wrong audience: " + call(client, otherApi));
+
+ var noScope = issuer.issue("bob", "profile", SecureValidators.AUDIENCE);
+ lines.add("missing scope: " + call(client, noScope));
+
+ lines.add("opaque token: " + call(client, "opaque-good"));
+ lines.add("revoked token: " + call(client, "opaque-revoked"));
+ return String.join("\n", lines);
+ }
+ }
+ }
+
+ private static String call(MockRestClient client, String token) throws Exception {
+ var res = client.get("/orders").header("Authorization", "Bearer " + token).run();
+ return res.getStatusCode() == 200 ? "200 " + res.getContent().asString() : String.valueOf(res.getStatusCode());
+ }
+}
+```
+
+## Run it
+
+From the project directory:
+
+```bash
+mvn -q compile exec:java -Dexec.mainClass=com.example.SecureDemo
+```
+
+Expected output:
+
+```text guide=SecureAnEndpoint/run-output.txt#output
+no token: 401 Bearer realm="orders"
+valid JWT: 200 "orders for alice"
+wrong audience: 401
+missing scope: 403
+opaque token: 200 "orders for reporting-job"
+revoked token: 401
+```
+
+## How it works
+
+- **The guard runs first.** A `RestGuardList` bean applies to every operation in the resource. `BearerTokenGuard` answers `401` with `WWW-Authenticate: Bearer realm="orders"` when the header is missing or the validator rejects the token, so your `@RestOp` methods never see an unauthenticated call. See [AuthN Guards](/docs/topics/RestServerAuthGuards).
+- **Validation is pluggable.** `TokenValidator` is a single-method interface, which is why `either` can be a lambda. With a JWKS URL, `JwtTokenValidator` caches the key set for 5 minutes and refetches it early when a token names a key id it hasn't seen, so key rotation needs no restart. Invalid signatures, expired tokens and a wrong `aud` or `iss` all fail validation, so the wrong-audience call gets `401`, not `403`. See [OAuth 2.0 / OIDC AuthN Support](/docs/topics/OAuthAuthSupport).
+- **Authorization stays in your code.** The guard only proves who the caller is. The scope check in `orders` decides what they may do, and answers `403`. A JWT carries `scope` as a space-separated string and an introspection response carries it as a list, so `hasScope` accepts both.
+- **Other ways in.** For authentication that runs at the servlet-filter layer, or that covers several resources at once, see the [AuthN Filter Framework](/docs/topics/AuthFilterFramework) and the [REST Authenticator](/docs/topics/RestServerAuthenticator).
+
+## Next steps
+
+- Let users log in through a browser with an OpenID Connect provider: [OIDC Relying Party Login](/docs/topics/OidcRelyingParty).
+- Get tokens from a real authorization server with the client-credentials or authorization-code (PKCE) flows, and set up OIDC discovery: [OAuth 2.0 / OIDC AuthN Support](/docs/topics/OAuthAuthSupport).
+- Write JUnit tests for the endpoint: [Test an endpoint with MockRestClient](/docs/guides/TestAnEndpoint).
+- Publish the API's contract: [Generate OpenAPI docs for your API](/docs/guides/OpenApiDocs).
diff --git a/pages/guides/03.TestAnEndpoint.md b/pages/guides/03.TestAnEndpoint.md
new file mode 100644
index 0000000000..7a0779714c
--- /dev/null
+++ b/pages/guides/03.TestAnEndpoint.md
@@ -0,0 +1,247 @@
+---
+id: 03.TestAnEndpoint
+title: "Test an endpoint with MockRestClient"
+slug: TestAnEndpoint
+---
+
+## What you'll build
+
+A small greeting resource and a JUnit test class that calls it through `MockRestClient`. The mock client
+sends real requests and parses real responses, but hands them straight to the resource in the same JVM, so no
+server starts and no port is opened. The three tests check a JSON response, a request body round-trip and a
+400 error, and `mvn test` reports them passing.
+
+## Prerequisites and time
+
+- Java 17 or newer and Maven 3.9 or newer.
+- `org.apache.juneau:juneau-rest-server:10.0.0-SNAPSHOT` for the resource.
+- `org.apache.juneau:juneau-rest-mock:10.0.0-SNAPSHOT` (test scope), which brings the REST client with it.
+- `org.junit.jupiter:junit-jupiter:6.0.3` (test scope).
+- About 10 minutes.
+
+`10.0.0-SNAPSHOT` needs the Apache snapshots repository, which the `pom.xml` below declares; Maven Central has
+only the 9.2.0 release today (see [Install Juneau](/docs/topics/GettingStartedInstall) and [Downloads](/downloads)).
+
+## Steps
+
+### 1. Create the project
+
+Make an empty directory and save this as `pom.xml`:
+
+```xml guide=TestAnEndpoint/pom.xml#pom
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.example</groupId>
+ <artifactId>greetings</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <maven.compiler.release>17</maven.compiler.release>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <juneau.version>10.0.0-SNAPSHOT</juneau.version>
+ </properties>
+
+ <repositories>
+ <repository>
+ <id>apache-snapshots</id>
+ <url>https://repository.apache.org/snapshots/</url>
+ <releases><enabled>false</enabled></releases>
+ <snapshots><enabled>true</enabled></snapshots>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server</artifactId>
+ <version>${juneau.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-mock</artifactId>
+ <version>${juneau.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <version>6.0.3</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>3.5.4</version>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+```
+
+The two main classes go in `src/main/java/com/example/greetings/` and the test in
+`src/test/java/com/example/greetings/`. Start each file with `package com.example.greetings;`.
+
+### 2. Write the bean
+
+`Greeting.java`:
+
+```java guide=Greeting.java#greeting
+/**
+ * The bean the greeting service returns.
+ */
+public class Greeting {
+
+ /** Who is greeted. */
+ public String name;
+
+ /** The greeting text. */
+ public String message;
+
+ /** Used by the parser. */
+ public Greeting() {}
+
+ /**
+ * Creates a greeting for a name.
+ *
+ * @param name Who is greeted.
+ */
+ public Greeting(String name) {
+ this.name = name;
+ this.message = "Hello, " + name + "!";
+ }
+}
+```
+
+### 3. Write the resource
+
+`GreetingResource.java` has one GET and one POST. The POST rejects a blank name with a 400:
+
+```java guide=GreetingResource.java#resource
+import org.apache.juneau.http.*;
+import org.apache.juneau.http.response.*;
+import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.servlet.*;
+
+/**
+ * A small greeting service: one GET, one POST that validates its body.
+ */
+@Rest(path = "/greetings", title = "Greeting service")
+public class GreetingResource extends BasicRestServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Greets the name in the path.
+ *
+ * @param name The name from the URL.
+ * @return The greeting.
+ */
+ @RestGet("/{name}")
+ public Greeting greet(@Path("name") String name) {
+ return new Greeting(name);
+ }
+
+ /**
+ * Greets the name in the request body.
+ *
+ * @param body A greeting with only {@code name} set.
+ * @return The completed greeting.
+ * @throws BadRequest If the name is missing or blank.
+ */
+ @RestPost("/")
+ public Greeting create(@Content Greeting body) throws BadRequest {
+ if (body.name == null || body.name.isBlank())
+ throw new BadRequest("name is required");
+ return new Greeting(body.name);
+ }
+}
+```
+
+### 4. Write the test
+
+`GreetingResource_Test.java`:
+
+```java guide=GreetingResource_Test.java#test
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.marshall.json.*;
+import org.apache.juneau.rest.mock.*;
+import org.junit.jupiter.api.*;
+
+class GreetingResource_Test {
+
+ @Test
+ void a01_getReturnsJson() throws Exception {
+ try (var mock = MockRestClient.create(GreetingResource.class);
+ var resp = mock.get("/Ada").header("Accept", "application/json").run()) {
+ resp.assertThat().isOk().body().contains("\"message\":\"Hello, Ada!\"");
+ }
+ }
+
+ @Test
+ void a02_postParsesBodyIntoBean() throws Exception {
+ try (var mock = MockRestClient.builder(GreetingResource.class).defaultParser(JsonParser.DEFAULT).build();
+ var resp = mock.post("/").body(new Greeting("Grace")).run()) {
+ resp.assertOk();
+ var greeting = resp.body().as(Greeting.class);
+ assertEquals("Hello, Grace!", greeting.message);
+ }
+ }
+
+ @Test
+ void a03_blankNameIsRejected() throws Exception {
+ try (var mock = MockRestClient.builder(GreetingResource.class).noTrace().build();
+ var resp = mock.post("/").bodyString("{\"name\":\"\"}").header("Content-Type", "application/json").run()) {
+ resp.assertStatus(400);
+ }
+ }
+}
+```
+
+## Run it
+
+```bash
+mvn test
+```
+
+The end of the output:
+
+```text guide=TestAnEndpoint/run-output.txt#mvn-test
+[INFO] Results:
+[INFO]
+[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
+[INFO]
+[INFO] BUILD SUCCESS
+```
+
+Each request also leaves a one-line `INFO` access record in the log, with its status, method and path.
+
+## How it works
+
+- **No server.** `MockRestClient.create(GreetingResource.class)` builds the resource's `RestContext` and gives
+ you a `RestClient` whose transport calls it directly. The context is built once per resource class and
+ reused, so creating a mock client in every test is cheap. See [NG Mock REST Client](/docs/topics/NgMockRestClient).
+- **Paths are relative to the resource.** The mock client's root URL already ends in the resource's
+ `@Rest(path)`, so `mock.get("/Ada")` reaches `/greetings/Ada`.
+- **Ask for the format you test.** With no `Accept` header the resource answers with its HTML view, so the
+ first test sends `Accept: application/json`. To read a response as a bean with `body().as(...)`, give the
+ builder a parser with `defaultParser(...)`, as the second test does.
+- **Expected errors stay quiet.** `noTrace()` sends `No-Trace: true`, so the 400 the third test expects does
+ not put a stack trace in the log.
+- **Two flavors.** This guide uses `org.apache.juneau.rest.mock.MockRestClient`, which pairs with the
+ next-generation REST client. Tests written against the classic client use
+ `org.apache.juneau.rest.mock.classic.MockRestClient`, described in
+ [Mock REST Client Overview](/docs/topics/MockRestClientOverview). Both ship in
+ [juneau-rest-mock](/docs/topics/JuneauRestMock).
+
+## Next steps
+
+- [Build a REST API on Spring Boot](/docs/guides/SpringBootRestApi): a fuller resource to test the same way.
+- [Call an existing REST API with a typed proxy](/docs/guides/TypedProxyClient): the client side; test it
+ with `MockHttpTransport` from [NG Mock REST Client](/docs/topics/NgMockRestClient#mockhttptransport-test-client-code).
+- [Secure an endpoint with JWT and OAuth](/docs/guides/SecureAnEndpoint): test a guarded resource.
diff --git a/pages/guides/04.TypedProxyClient.md b/pages/guides/04.TypedProxyClient.md
new file mode 100644
index 0000000000..cbf4259fac
--- /dev/null
+++ b/pages/guides/04.TypedProxyClient.md
@@ -0,0 +1,341 @@
+---
+id: 04.TypedProxyClient
+title: "Call an existing REST API with a typed proxy"
+slug: TypedProxyClient
+---
+
+## What you'll build
+
+A typed Java client for a books REST API. You describe the API's endpoints as a Java interface, the
+next-generation `RestClient` implements the interface for you, and your code calls ordinary methods that
+send HTTP requests and return beans. So the guide runs anywhere, step 2 starts a small stand-in API on the
+JDK's built-in HTTP server; point the client at your real API instead when you have one. The client prints
+five lines, ending with `Book 99: 404 Not Found`.
+
+## Prerequisites and time
+
+- Java 17 or newer and Maven 3.9 or newer.
+- `org.apache.juneau:juneau-rest-client:10.0.0-SNAPSHOT`. It sends requests with the JDK's `HttpClient`, so
+ there is no other HTTP library to add.
+- Port 8080 free, for the stand-in API.
+- About 15 minutes.
+
+`10.0.0-SNAPSHOT` needs the Apache snapshots repository, which the `pom.xml` below declares; Maven Central has
+only the 9.2.0 release today (see [Install Juneau](/docs/topics/GettingStartedInstall) and [Downloads](/downloads)).
+
+## Steps
+
+### 1. Create the project
+
+Make an empty directory and save this as `pom.xml`:
+
+```xml guide=TypedProxyClient/pom.xml#pom
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.example</groupId>
+ <artifactId>books</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <maven.compiler.release>17</maven.compiler.release>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <juneau.version>10.0.0-SNAPSHOT</juneau.version>
+ </properties>
+
+ <repositories>
+ <repository>
+ <id>apache-snapshots</id>
+ <url>https://repository.apache.org/snapshots/</url>
+ <releases><enabled>false</enabled></releases>
+ <snapshots><enabled>true</enabled></snapshots>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-client</artifactId>
+ <version>${juneau.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>3.6.4</version>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+```
+
+Put the Java files from the next steps in `src/main/java/com/example/books/`, and start each one with
+`package com.example.books;`.
+
+### 2. Start a stand-in API
+
+`BookStubServer.java` answers `GET /books`, `GET /books/1` and `POST /books` with fixed JSON, and 404 for
+anything else. Skip this step if you already have an API to call:
+
+```java guide=BookStubServer.java#stub
+import static java.nio.charset.StandardCharsets.*;
+
+import java.io.*;
+import java.net.*;
+
+import com.sun.net.httpserver.*;
+
+/**
+ * A stand-in for the real books API, on the JDK's built-in HTTP server. Answers with fixed JSON, and echoes a
+ * posted book back with id 3.
+ */
+public class BookStubServer {
+
+ /**
+ * Serves the stand-in API on port 8080 until the process is stopped.
+ *
+ * @param args Unused.
+ * @throws IOException If the port cannot be bound.
+ */
+ public static void main(String[] args) throws IOException {
+ start(8080);
+ System.out.println("Books API stand-in on http://localhost:8080/books");
+ }
+
+ /**
+ * Starts the server.
+ *
+ * @param port The port, or {@code 0} for any free port.
+ * @return The running server; call {@code stop(0)} to shut it down.
+ * @throws IOException If the port cannot be bound.
+ */
+ public static HttpServer start(int port) throws IOException {
+ var server = HttpServer.create(new InetSocketAddress("localhost", port), 0);
+ server.createContext("/books", x -> {
+ var path = x.getRequestURI().getPath();
+ var method = x.getRequestMethod();
+ if (method.equals("GET") && path.equals("/books"))
+ reply(x, 200, "[{\"id\":1,\"title\":\"Dune\",\"author\":\"Frank Herbert\"},"
+ + "{\"id\":2,\"title\":\"Emma\",\"author\":\"Jane Austen\"}]");
+ else if (method.equals("GET") && path.equals("/books/1"))
+ reply(x, 200, "{\"id\":1,\"title\":\"Dune\",\"author\":\"Frank Herbert\"}");
+ else if (method.equals("POST") && path.equals("/books"))
+ reply(x, 200, new String(x.getRequestBody().readAllBytes(), UTF_8).replace("\"id\":0", "\"id\":3"));
+ else
+ reply(x, 404, "{\"error\":\"Not found\"}");
+ });
+ server.start();
+ return server;
+ }
+
+ private static void reply(HttpExchange x, int status, String json) throws IOException {
+ var bytes = json.getBytes(UTF_8);
+ x.getResponseHeaders().set("Content-Type", "application/json");
+ x.sendResponseHeaders(status, bytes.length);
+ try (var out = x.getResponseBody()) {
+ out.write(bytes);
+ }
+ }
+}
+```
+
+### 3. Write the bean
+
+`Book.java` matches the JSON the API sends:
+
+```java guide=Book.java#book
+/**
+ * A book, as the books API sends it: {@code {"id":1,"title":"Dune","author":"Frank Herbert"}}.
+ */
+public class Book {
+
+ /** The id the API assigned. */
+ public int id;
+
+ /** The title. */
+ public String title;
+
+ /** The author. */
+ public String author;
+
+ /** Used by the parser. */
+ public Book() {}
+
+ /**
+ * Creates a book that has no id yet.
+ *
+ * @param title The title.
+ * @param author The author.
+ */
+ public Book(String title, String author) {
+ this.title = title;
+ this.author = author;
+ }
+}
+```
+
+### 4. Describe the API
+
+`BookApi.java` is the typed proxy interface. `@Remote(path)` is the common prefix, and each method says
+which request it sends:
+
+```java guide=BookApi.java#api
+import java.util.*;
+
+import org.apache.juneau.http.*;
+import org.apache.juneau.http.remote.*;
+import org.apache.juneau.http.response.*;
+
+/**
+ * The books API, described as a Java interface. {@code RestClient.remote(BookApi.class)} implements it.
+ */
+@Remote(path = "/books")
+public interface BookApi {
+
+ /**
+ * Calls {@code GET /books}.
+ *
+ * @return Every book.
+ */
+ @RemoteGet
+ List<Book> listBooks();
+
+ /**
+ * Calls {@code GET /books/{id}}.
+ *
+ * @param id The book id.
+ * @return The book.
+ * @throws NotFound If the API answers 404.
+ */
+ @RemoteGet("/{id}")
+ Book getBook(@Path("id") int id) throws NotFound;
+
+ /**
+ * Calls {@code POST /books} with the book as a JSON body.
+ *
+ * @param book The book to add.
+ * @return The stored book, with its id.
+ */
+ @RemotePost
+ Book addBook(@Content Book book);
+}
+```
+
+### 5. Call it
+
+`BookClient.java` builds the client, asks it for a `BookApi` and calls the methods:
+
+```java guide=BookClient.java#client
+import java.io.*;
+
+import org.apache.juneau.http.response.*;
+import org.apache.juneau.marshall.json.*;
+import org.apache.juneau.rest.client.*;
+
+/**
+ * Calls the books API through {@link BookApi} and prints what comes back.
+ */
+public class BookClient {
+
+ /**
+ * Runs against the API root given as the first argument, or {@code http://localhost:8080}.
+ *
+ * @param args The API root URL, optional.
+ * @throws IOException If the API cannot be reached.
+ */
+ public static void main(String[] args) throws IOException {
+ run(args.length > 0 ? args[0] : "http://localhost:8080", System.out);
+ }
+
+ /**
+ * Makes the calls and prints the results.
+ *
+ * @param rootUrl The API root URL.
+ * @param out Where to print.
+ * @throws IOException If the API cannot be reached.
+ */
+ public static void run(String rootUrl, PrintStream out) throws IOException {
+ try (var client = RestClient.builder()
+ .rootUrl(rootUrl)
+ .defaultSerializer(JsonSerializer.DEFAULT)
+ .defaultParser(JsonParser.DEFAULT)
+ .allowPrivateUrls(true) // Only because the stand-in API runs on localhost.
+ .build()) {
+
+ BookApi books = client.remote(BookApi.class);
+
+ for (var b : books.listBooks())
+ out.println(b.id + ": " + b.title + " by " + b.author);
+
+ var added = books.addBook(new Book("Kindred", "Octavia E. Butler"));
+ out.println("Added " + added.title + " as id " + added.id);
+
+ out.println("Book 1 is " + books.getBook(1).title);
+
+ try {
+ books.getBook(99);
+ } catch (NotFound e) {
+ out.println("Book 99: 404 Not Found");
+ }
+ }
+ }
+}
+```
+
+## Run it
+
+Start the stand-in API in one terminal:
+
+```bash
+mvn compile exec:java -Dexec.mainClass=com.example.books.BookStubServer
+```
+
+Run the client in a second terminal:
+
+```bash
+mvn exec:java -Dexec.mainClass=com.example.books.BookClient
+```
+
+It prints:
+
+```text guide=TypedProxyClient/run-output.txt#client-output
+1: Dune by Frank Herbert
+2: Emma by Jane Austen
+Added Kindred as id 3
+Book 1 is Dune
+Book 99: 404 Not Found
+```
+
+The client also logs one `INFO` line per request, with its status, method and URL. To call your own API, pass
+its root URL with `-Dexec.args=https://api.example.com` and remove the `allowPrivateUrls(true)` line.
+
+## How it works
+
+- **The proxy.** `client.remote(BookApi.class)` returns a dynamic proxy. Each call joins the client's root URL,
+ the `@Remote` path and the method's path, fills `{id}` from the `@Path` argument, serializes the `@Content`
+ argument as the body and parses the response into the return type. The annotations are listed in
+ [Remote Proxy Interfaces](/docs/topics/RemoteProxyInterfaces) and explained in
+ [REST Proxies](/docs/topics/RestProxies).
+- **Errors.** Because `getBook` declares `throws NotFound`, a 404 response is thrown as `NotFound`. Declare the
+ error types your code wants to handle.
+- **Formats are explicit.** The 10.0 client has no implicit JSON fallback, so the builder names the
+ serializer and parser it uses with `defaultSerializer(...)` and `defaultParser(...)`.
+- **Private addresses are refused by default.** Proxy calls to loopback, private and cloud-metadata
+ addresses are blocked to guard against server-side request forgery. The stand-in runs on `localhost`, so
+ the client opts out with `allowPrivateUrls(true)`. See [NG Client Security](/docs/topics/NgClientSecurity).
+- **Which client.** This is the next-generation client, `org.apache.juneau.rest.client.RestClient`, the
+ recommended one for new code; [Choosing a REST Client](/docs/topics/ChoosingARestClient) compares it with
+ the classic client. Its proxies also support constant headers, timeouts, retries and more; see
+ [Declarative Remote-Proxy Features](/docs/topics/NextGenRestClient#declarative-remote-proxy-features-next-gen).
+
+## Next steps
+
+- [NG Remote Proxies](/docs/topics/NgRemoteProxies): constant headers and query values, retries, timeouts and
+ multipart bodies on the same interface.
+- [Test an endpoint with MockRestClient](/docs/guides/TestAnEndpoint): test the server side in-process.
+- [HTTP Transport Adapters](/docs/topics/HttpTransportAdapters): swap the JDK `HttpClient` for Apache
+ HttpClient, OkHttp or Jetty.
diff --git a/pages/guides/05.CustomSwaps.md b/pages/guides/05.CustomSwaps.md
new file mode 100644
index 0000000000..ebaf6fe246
--- /dev/null
+++ b/pages/guides/05.CustomSwaps.md
@@ -0,0 +1,282 @@
+---
+id: 05.CustomSwaps
+title: "Serialize a type Juneau doesn't know"
+slug: CustomSwaps
+---
+
+Teach Juneau to write and read a class that isn't a bean and that you can't change, by giving it a swap: a small class that converts the type to a simpler one and back.
+
+## What you'll build
+
+An `Invoice` bean whose fields use a third-party-style `Money` type. Without help, Juneau writes `Money` through its `toString()` and can't parse it back. You'll add a `MoneySwap` that turns `Money` into a string such as `"12.50 EUR"` and back again, register it two ways, and round-trip an invoice through JSON.
+
+The finished program prints:
+
+```text
+with swap: {"number":"INV-1001","payments":["10.00 EUR","2.50 EUR"],"total":"12.50 EUR"}
+round trip: Money(amount=12.50, currency=EUR), equal=true
+```
+
+## Prerequisites and time
+
+- Java 17 or later and Maven 3.
+- `org.apache.juneau:juneau-marshall:10.0.0-SNAPSHOT`, shown in the POM in step 1.
+- About 10 minutes.
+
+`10.0.0-SNAPSHOT` is not on Maven Central, so the POM adds the Apache snapshots repository. See the note in the [MCP Quickstart](/docs/topics/JuneauMcpQuickstart) and [Install Juneau](/docs/topics/GettingStartedInstall).
+
+## Steps
+
+Put each class in `src/main/java/com/example/` and start each file with `package com.example;`. Any package works if you use it everywhere.
+
+### 1. Create the project
+
+Create an empty directory with this `pom.xml`:
+
+```xml guide=CustomSwaps/pom.xml#pom
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.example</groupId>
+ <artifactId>customswaps</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <maven.compiler.release>17</maven.compiler.release>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <juneau.version>10.0.0-SNAPSHOT</juneau.version>
+ </properties>
+
+ <repositories>
+ <repository>
+ <id>apache-snapshots</id>
+ <url>https://repository.apache.org/snapshots/</url>
+ <releases><enabled>false</enabled></releases>
+ <snapshots><enabled>true</enabled></snapshots>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-marshall</artifactId>
+ <version>${juneau.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>3.6.4</version>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+```
+
+### 2. Add the type Juneau doesn't know
+
+`Money` stands in for a class from a library you don't control. It has a private constructor, record-style accessors instead of getters, and a `toString()` meant for debugging, so it isn't a bean and Juneau has no way to rebuild it.
+
+```java guide=Money.java#money
+import java.math.*;
+import java.util.*;
+
+/**
+ * An amount of money in one currency.
+ *
+ * <p>
+ * Stands in for a type you don't control: no public constructor, no getters or setters, and a
+ * debugging-only toString(), so Juneau has no way to turn it into JSON and back.
+ */
+public final class Money {
+
+ private final BigDecimal amount;
+ private final Currency currency;
+
+ private Money(BigDecimal amount, Currency currency) {
+ this.amount = amount;
+ this.currency = currency;
+ }
+
+ public static Money of(String amount, String currencyCode) {
+ return new Money(new BigDecimal(amount), Currency.getInstance(currencyCode));
+ }
+
+ public BigDecimal amount() { return amount; }
+
+ public Currency currency() { return currency; }
+
+ @Override
+ public String toString() {
+ return "Money(amount=" + amount + ", currency=" + currency + ")";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof Money m && amount.equals(m.amount) && currency.equals(m.currency);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(amount, currency);
+ }
+}
+```
+
+### 3. Use it in a bean
+
+`Invoice` is an ordinary bean with public fields, one of them a `Money` and one a list of them.
+
+```java guide=Invoice.java#invoice
+import java.util.*;
+
+/**
+ * A plain bean with {@link Money} properties.
+ */
+public class Invoice {
+
+ public String number;
+ public Money total;
+ public List<Money> payments = new ArrayList<>();
+
+ public Invoice number(String value) { number = value; return this; }
+
+ public Invoice total(Money value) { total = value; return this; }
+
+ public Invoice payment(Money value) { payments.add(value); return this; }
+}
+```
+
+### 4. Write the swap
+
+A `StringSwap<Money>` says "represent `Money` as a `String`". `swap` runs when serializing and `unswap` when parsing. The session and `ClassMeta` arguments give access to settings and the exact target type, which this swap doesn't need.
+
+```java guide=MoneySwap.java#swap
+import org.apache.juneau.marshall.*;
+import org.apache.juneau.marshall.swap.spi.*;
+
+/**
+ * Serializes {@link Money} as a string such as "12.50 EUR", and parses it back.
+ */
+public class MoneySwap extends StringSwap<Money> {
+
+ @Override
+ public String swap(MarshallingSession session, Money money) {
+ return money.amount().toPlainString() + " " + money.currency().getCurrencyCode();
+ }
+
+ @Override
+ public Money unswap(MarshallingSession session, String value, ClassMeta<?> hint) {
+ var parts = value.trim().split("\\s+");
+ return Money.of(parts[0], parts[1]);
+ }
+}
+```
+
+### 5. Or attach the swap to a field
+
+If you'd rather not register the swap on every serializer and parser, annotate the field. `@Swap` also works on a class you own, and on getters and setters.
+
+```java guide=InvoiceAnnotated.java#annotated
+import org.apache.juneau.marshall.*;
+
+/**
+ * The same idea with the swap attached to the property, so no serializer setup is needed.
+ */
+public class InvoiceAnnotated {
+
+ public String number;
+
+ @Swap(MoneySwap.class)
+ public Money total;
+}
+```
+
+### 6. Serialize and parse
+
+The program first tries the invoice without the swap, then registers `MoneySwap` on a serializer and a parser built from the default ones, and finally writes the annotated invoice with the plain default serializer.
+
+```java guide=InvoiceDemo.java#demo
+import org.apache.juneau.marshall.parser.*;
+import org.apache.juneau.marshall.json.*;
+
+/**
+ * Serializes an invoice with and without the swap, then parses it back.
+ */
+public class InvoiceDemo {
+
+ public static void main(String[] args) throws Exception {
+ System.out.println(run());
+ }
+
+ public static String run() throws Exception {
+ var invoice = new Invoice()
+ .number("INV-1001")
+ .total(Money.of("12.50", "EUR"))
+ .payment(Money.of("10.00", "EUR"))
+ .payment(Money.of("2.50", "EUR"));
+
+ // Without the swap, Juneau falls back to toString(), which can't be parsed back.
+ var plain = JsonSerializer.DEFAULT.write(invoice);
+ String plainParse;
+ try {
+ JsonParser.DEFAULT.read(plain, Invoice.class);
+ plainParse = "parsed";
+ } catch (ParseException e) {
+ plainParse = "fails (" + e.getClass().getSimpleName() + ")";
+ }
+
+ // Register the swap on the serializer and the parser.
+ var serializer = JsonSerializer.create().swaps(MoneySwap.class).build();
+ var parser = JsonParser.create().swaps(MoneySwap.class).build();
+
+ var json = serializer.write(invoice);
+ var parsed = parser.read(json, Invoice.class);
+
+ // The annotated bean needs no registration at all.
+ var annotated = new InvoiceAnnotated();
+ annotated.number = "INV-1002";
+ annotated.total = Money.of("99.95", "USD");
+
+ return String.join("\n",
+ "without swap: " + plain,
+ "parse it: " + plainParse,
+ "with swap: " + json,
+ "round trip: " + parsed.total + ", equal=" + parsed.total.equals(invoice.total),
+ "annotated: " + JsonSerializer.DEFAULT.write(annotated));
+ }
+}
+```
+
+## Run it
+
+From the project directory:
+
+```bash
+mvn -q compile exec:java -Dexec.mainClass=com.example.InvoiceDemo
+```
+
+Expected output:
+
+```text guide=CustomSwaps/run-output.txt#output
+without swap: {"number":"INV-1001","payments":["Money(amount=10.00, currency=EUR)","Money(amount=2.50, currency=EUR)"],"total":"Money(amount=12.50, currency=EUR)"}
+parse it: fails (ParseException)
+with swap: {"number":"INV-1001","payments":["10.00 EUR","2.50 EUR"],"total":"12.50 EUR"}
+round trip: Money(amount=12.50, currency=EUR), equal=true
+annotated: {"number":"INV-1002","total":"99.95 USD"}
+```
+
+## How it works
+
+- **Why the first attempt fails.** Juneau serializes a non-bean by calling `toString()`. To parse it back, it needs a public constructor taking a `String`, or a public static method taking one with a name such as `fromString`, `valueOf` or `parse`. `Money` has neither, so parsing fails. See [Swap](/docs/topics/Swaps).
+- **A swap replaces the type for marshalling.** With `MoneySwap` registered, every serializer and parser built with it treats `Money` as a `String`, wherever it appears: a field, a list element, or a map value. `swaps(...)` accepts any number of swap classes.
+- **Registration or annotation.** Registering on the builder keeps the model class untouched and lets different serializers make different choices. `@Swap` travels with the class or field, so the default serializers pick it up. See [@Swap Annotation](/docs/topics/SwapAnnotation).
+- **Other shapes.** A swap can produce a map or a bean instead of a string, can be one-way for types you only write, and can render through a template. See [One-way Swaps](/docs/topics/OneWaySwaps) and [Templated Swaps](/docs/topics/TemplatedSwaps).
+
+## Next steps
+
+- Use the swap in a REST API: put `@MarshalledConfig(swaps=MoneySwap.class)` on the resource class, or keep the `@Swap` field annotation, then follow [Build a REST API on Spring Boot](/docs/guides/SpringBootRestApi).
+- Read every swap variant and the built-in swaps for dates, enums and more: [Swap](/docs/topics/Swaps).
+- Load settings from files instead of code: [Externalize config with INI/YAML and variables](/docs/guides/ExternalizeConfig).
diff --git a/pages/guides/06.ExternalizeConfig.md b/pages/guides/06.ExternalizeConfig.md
new file mode 100644
index 0000000000..e22b9603da
--- /dev/null
+++ b/pages/guides/06.ExternalizeConfig.md
@@ -0,0 +1,215 @@
+---
+id: 06.ExternalizeConfig
+title: "Externalize config with INI/YAML and variables"
+slug: ExternalizeConfig
+---
+
+Move settings out of your code into a config file, in INI or YAML, and let variables fill in values from system properties, environment variables and other entries when they are read.
+
+## What you'll build
+
+A small program that loads the same settings from `app.ini` and from `app.yaml`, reads single entries as typed values, reads a whole section through a Java interface, and overrides the port with `-Dapp.port` without editing either file.
+
+The program prints:
+
+```text
+yaml server section: localhost 8080 http://localhost:8080
+with -Dapp.port=9090: http://localhost:9090
+```
+
+## Prerequisites and time
+
+- Java 17 or later and Maven 3.
+- `org.apache.juneau:juneau-config:10.0.0-SNAPSHOT`, shown in the POM in step 1. It brings in `juneau-marshall`, which converts entries to Java types.
+- About 15 minutes.
+
+`10.0.0-SNAPSHOT` is not on Maven Central, so the POM adds the Apache snapshots repository. See the note in the [MCP Quickstart](/docs/topics/JuneauMcpQuickstart) and [Install Juneau](/docs/topics/GettingStartedInstall).
+
+## Steps
+
+Put each class in `src/main/java/com/example/` and start each file with `package com.example;`. Any package works if you use it everywhere. The two config files go in `src/main/resources/ExternalizeConfig/`.
+
+### 1. Create the project
+
+Create an empty directory with this `pom.xml`:
+
+```xml guide=ExternalizeConfig/pom.xml#pom
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.example</groupId>
+ <artifactId>externalizeconfig</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <maven.compiler.release>17</maven.compiler.release>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <juneau.version>10.0.0-SNAPSHOT</juneau.version>
+ </properties>
+
+ <repositories>
+ <repository>
+ <id>apache-snapshots</id>
+ <url>https://repository.apache.org/snapshots/</url>
+ <releases><enabled>false</enabled></releases>
+ <snapshots><enabled>true</enabled></snapshots>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-config</artifactId>
+ <version>${juneau.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>3.6.4</version>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+```
+
+### 2. Write the INI file
+
+Save this as `src/main/resources/ExternalizeConfig/app.ini`. Sections are in brackets, and a `#` line before an entry is its comment. The values use three variables:
+
+- `$S{app.port,8080}` reads the `app.port` system property, defaulting to `8080`.
+- `$E{APP_DB_USER,app}` reads the `APP_DB_USER` environment variable, defaulting to `app`.
+- `$C{server/host}` reads another entry from the same config.
+
+```ini guide=ExternalizeConfig/app.ini#settings
+[server]
+# Host name clients use to reach the service.
+host = localhost
+
+# Defaults to 8080; start the JVM with -Dapp.port=9090 to override.
+port = $S{app.port,8080}
+
+# Built from the two entries above.
+baseUrl = http://$C{server/host}:$C{server/port}
+
+[database]
+# Read from the APP_DB_USER environment variable, falling back to "app".
+user = $E{APP_DB_USER,app}
+poolSize = 10
+```
+
+### 3. Or write the same settings in YAML
+
+Save this as `src/main/resources/ExternalizeConfig/app.yaml`. Top-level keys are sections, nested keys are entries, and the variables work the same way. You need only one of the two files; this guide loads both to show they read the same.
+
+```yaml guide=ExternalizeConfig/app.yaml#settings
+server:
+ # Host name clients use to reach the service.
+ host: localhost
+ # Defaults to 8080; start the JVM with -Dapp.port=9090 to override.
+ port: $S{app.port,8080}
+ # Built from the two entries above.
+ baseUrl: http://$C{server/host}:$C{server/port}
+database:
+ # Read from the APP_DB_USER environment variable, falling back to "app".
+ user: $E{APP_DB_USER,app}
+ poolSize: 10
+```
+
+### 4. Describe a section as an interface
+
+Juneau can implement an interface over a section for you: each getter reads the entry with the matching name and converts it to the return type.
+
+```java guide=AppSettings.java#settings
+/**
+ * Typed view of the [server] section; Juneau implements it for you.
+ */
+public interface AppSettings {
+
+ String getHost();
+
+ int getPort();
+
+ String getBaseUrl();
+}
+```
+
+### 5. Load and read the config
+
+```java guide=ConfigDemo.java#demo
+import org.apache.juneau.config.*;
+import org.apache.juneau.config.store.*;
+
+/**
+ * Loads the same settings from INI and from YAML, reads them three ways, and shows a system-property override.
+ */
+public class ConfigDemo {
+
+ public static void main(String[] args) throws Exception {
+ System.out.println(run());
+ }
+
+ public static String run() throws Exception {
+ // The file extension picks the format; the classpath store reads from src/main/resources.
+ var ini = Config.create("ExternalizeConfig/app.ini").store(ClasspathStore.DEFAULT).build();
+ var yaml = Config.create("ExternalizeConfig/app.yaml").store(ClasspathStore.DEFAULT).build();
+
+ // One key at a time, converted to the type you ask for.
+ var port = ini.get("server/port").asInteger().orElse(0);
+ var user = ini.get("database/user").asString().orElse(null);
+ var poolSize = yaml.get("database/poolSize").asInteger().orElse(0);
+
+ // A whole section through an interface.
+ var server = yaml.getSection("server").asInterface(AppSettings.class).orElseThrow();
+
+ // $S{app.port,8080} is resolved every time the value is read.
+ System.setProperty("app.port", "9090");
+ String overridden;
+ try {
+ overridden = ini.get("server/baseUrl").asString().orElse(null);
+ } finally {
+ System.clearProperty("app.port");
+ }
+
+ return String.join("\n",
+ "ini server/port: " + port,
+ "ini database/user: " + user,
+ "yaml database/poolSize: " + poolSize,
+ "yaml server section: " + server.getHost() + " " + server.getPort() + " " + server.getBaseUrl(),
+ "with -Dapp.port=9090: " + overridden);
+ }
+}
+```
+
+## Run it
+
+From the project directory:
+
+```bash
+mvn -q compile exec:java -Dexec.mainClass=com.example.ConfigDemo
+```
+
+Expected output, unless `APP_DB_USER` is set in your environment:
+
+```text guide=ExternalizeConfig/run-output.txt#output
+ini server/port: 8080
+ini database/user: app
+yaml database/poolSize: 10
+yaml server section: localhost 8080 http://localhost:8080
+with -Dapp.port=9090: http://localhost:9090
+```
+
+## How it works
+
+- **One API, two formats.** `Config.create(name)` picks the format from the file extension: `.yaml` or `.yml` is YAML, anything else is INI. Both produce the same sections and entries, so the code that reads them doesn't change. See [INI Config Format](/docs/topics/IniConfigFormat) and [YAML Config Files](/docs/topics/YamlConfigFiles).
+- **Where the file comes from.** `ClasspathStore` reads from the classpath, which suits defaults packaged with the application. The default store, `FileStore.DEFAULT`, reads files from the working directory instead and can save changes back. See [Config Stores](/docs/topics/ConfigStores).
+- **Variables resolve on read.** Entries keep the `$S{...}`, `$E{...}` and `$C{...}` text. Juneau resolves them each time you read the entry, which is why setting `app.port` after loading still changes `baseUrl`. The default after the comma applies when the property or variable is missing. See [Variables](/docs/topics/Variables) and the [Simple Variable Language](/docs/topics/SimpleVariableLanguage).
+- **Typed reads.** `get(key)` returns an entry; `asInteger()`, `asString()` and the other `as...` methods convert it and return an `Optional`, empty when the entry is missing. `getSection(name).asInterface(type)` maps a whole section.
+
+## Next steps
+
+- Use config values in a REST resource with `$C{...}` in annotations, then follow [Build a REST API on Spring Boot](/docs/guides/SpringBootRestApi).
+- Read more about sections, imports, listeners and saving changes: [juneau-config](/docs/topics/JuneauConfig).
+- Control how your own types are written: [Serialize a type Juneau doesn't know](/docs/guides/CustomSwaps).
diff --git a/pages/guides/07.OpenApiDocs.md b/pages/guides/07.OpenApiDocs.md
new file mode 100644
index 0000000000..0cf931a3f4
--- /dev/null
+++ b/pages/guides/07.OpenApiDocs.md
@@ -0,0 +1,241 @@
+---
+id: 07.OpenApiDocs
+title: "Generate OpenAPI docs for your API"
+slug: OpenApiDocs
+---
+
+Get an OpenAPI 3.1 document, a Swagger v2 document and two browsable UIs for a REST resource, generated from the resource's own annotations with no separate spec file to maintain.
+
+## What you'll build
+
+A small `Task API` that lists, fetches and creates to-do items. Adding two documentation mixins to it serves the API's contract at `/openapi.json`, `/openapi.yaml` and `/api`, and interactive pages at `/redoc` and `/swagger`. A short program fetches the OpenAPI document and prints what it describes:
+
+```text
+openapi: 3.1.0
+title: Task API
+GET /tasks - List tasks
+```
+
+## Prerequisites and time
+
+- Java 17 or later and Maven 3.
+- These dependencies, shown in the POM in step 1:
+ - `org.apache.juneau:juneau-rest-server:10.0.0-SNAPSHOT`, which contains the documentation mixins
+ - `org.apache.juneau:juneau-rest-mock:10.0.0-SNAPSHOT`, used here to call the API without starting a server
+- About 10 minutes.
+
+`10.0.0-SNAPSHOT` is not on Maven Central, so the POM adds the Apache snapshots repository. See the note in the [MCP Quickstart](/docs/topics/JuneauMcpQuickstart) and [Install Juneau](/docs/topics/GettingStartedInstall).
+
+## Steps
+
+Put each class in `src/main/java/com/example/` and start each file with `package com.example;`. Any package works if you use it everywhere.
+
+### 1. Create the project
+
+Create an empty directory with this `pom.xml`:
+
+```xml guide=OpenApiDocs/pom.xml#pom
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.example</groupId>
+ <artifactId>openapidocs</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <maven.compiler.release>17</maven.compiler.release>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <juneau.version>10.0.0-SNAPSHOT</juneau.version>
+ </properties>
+
+ <repositories>
+ <repository>
+ <id>apache-snapshots</id>
+ <url>https://repository.apache.org/snapshots/</url>
+ <releases><enabled>false</enabled></releases>
+ <snapshots><enabled>true</enabled></snapshots>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server</artifactId>
+ <version>${juneau.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-mock</artifactId>
+ <version>${juneau.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>3.6.4</version>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+```
+
+### 2. Describe the data
+
+`@Schema` adds descriptions to the bean and its properties. They end up in the generated schema for `Task`.
+
+```java guide=Task.java#task
+import org.apache.juneau.commons.*;
+
+/**
+ * A to-do item.
+ */
+@Schema(description="A to-do item.")
+public class Task {
+
+ @Schema(description="Server-assigned identifier.")
+ public int id;
+
+ @Schema(description="What needs doing.")
+ public String title;
+
+ @Schema(description="Whether the task is finished.")
+ public boolean done;
+
+ public Task() {}
+
+ public Task(int id, String title, boolean done) {
+ this.id = id;
+ this.title = title;
+ this.done = done;
+ }
+}
+```
+
+### 3. Write the resource and add the docs mixins
+
+The `title` and `description` in `@Rest`, and the `summary` and `description` on each operation, become the document's `info` block and the operation summaries. `mixins` grafts in the documentation endpoints: `SwaggerUiMixin` brings `/swagger` and `/api`, and `RedocMixin` brings `/redoc` and the `/openapi` documents. `BasicUniversalConfig` adds serializers for HTML as well as JSON, which the two UI pages need.
+
+```java guide=TaskResource.java#resource
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+import org.apache.juneau.commons.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.http.response.*;
+import org.apache.juneau.rest.server.*;
+import org.apache.juneau.rest.server.config.*;
+import org.apache.juneau.rest.server.docs.*;
+import org.apache.juneau.rest.server.servlet.*;
+
+/**
+ * A small task API that publishes its own Swagger 2.0 and OpenAPI 3.1 documents.
+ */
+@Rest(
+ title="Task API",
+ description="Create and list to-do items.",
+ mixins={SwaggerUiMixin.class, RedocMixin.class}
+)
+public class TaskResource extends RestServlet implements BasicUniversalConfig {
+
+ private static final long serialVersionUID = 1L;
+
+ private final Map<Integer,Task> tasks = new ConcurrentSkipListMap<>();
+ private final AtomicInteger nextId = new AtomicInteger(1);
+
+ @RestGet(path="/tasks", summary="List tasks", description="Returns every task, oldest first.")
+ public Collection<Task> list() {
+ return tasks.values();
+ }
+
+ @RestGet(path="/tasks/{id}", summary="Get a task")
+ public Task get(@Path("id") @Schema(description="Task identifier.") int id) {
+ var t = tasks.get(id);
+ if (t == null)
+ throw new NotFound("No task with id {0}", id);
+ return t;
+ }
+
+ @RestPost(path="/tasks", summary="Create a task")
+ public Task create(@Content Task task) {
+ task.id = nextId.getAndIncrement();
+ tasks.put(task.id, task);
+ return task;
+ }
+}
+```
+
+### 4. Read the generated document
+
+`MockRestClient` runs the resource in-process. The program fetches `/openapi.json` and prints its version, title and the task operations.
+
+```java guide=TaskDocsDemo.java#demo
+import java.util.*;
+
+import org.apache.juneau.marshall.collections.*;
+import org.apache.juneau.rest.mock.classic.*;
+
+/**
+ * Starts {@link TaskResource} in-process, fetches /openapi.json and prints the task operations it documents.
+ */
+public class TaskDocsDemo {
+
+ public static void main(String[] args) throws Exception {
+ System.out.println(run());
+ }
+
+ public static String run() throws Exception {
+ try (var client = MockRestClient.buildJson(TaskResource.class)) {
+ var json = client.get("/openapi.json").run().assertStatus(200).getContent().asString();
+ var doc = JsonMap.ofString(json);
+
+ var lines = new ArrayList<String>();
+ lines.add("openapi: " + doc.getString("openapi"));
+ lines.add("title: " + doc.getMap("info").getString("title"));
+ var paths = doc.getMap("paths");
+ for (var path : new TreeSet<>(paths.keySet())) {
+ if (! path.startsWith("/tasks"))
+ continue; // Skip the documentation endpoints, which document themselves too.
+ var ops = paths.getMap(path);
+ for (var method : new TreeSet<>(ops.keySet()))
+ lines.add(method.toUpperCase() + " " + path + " - " + ops.getMap(method).getString("summary"));
+ }
+ return String.join("\n", lines);
+ }
+ }
+}
+```
+
+## Run it
+
+From the project directory:
+
+```bash
+mvn -q compile exec:java -Dexec.mainClass=com.example.TaskDocsDemo
+```
+
+Expected output:
+
+```text guide=OpenApiDocs/run-output.txt#output
+openapi: 3.1.0
+title: Task API
+GET /tasks - List tasks
+POST /tasks - Create a task
+GET /tasks/{id} - Get a task
+```
+
+To browse the UIs, deploy `TaskResource` like any Juneau servlet, for example with [Build a REST API on Spring Boot](/docs/guides/SpringBootRestApi), then open `/redoc` or `/swagger` under its path.
+
+## How it works
+
+- **The document comes from your code.** Juneau builds it from the resource and operation annotations, the Java signatures (`@Path`, `@Content`, the return types) and the bean schemas, so it changes when the code does.
+- **Mixins choose the endpoints.** Each mixin is a small `@Rest` resource merged into yours. The OpenAPI document lists the mixins' own endpoints as well, which is why the program filters to `/tasks`. `BasicRestServlet` and `BasicRestResource` include both mixins by default; with a plain `RestServlet`, as here, you pick the ones you want. See [API-Docs Mixin Pack](/docs/topics/ApiDocsMixins).
+- **Two spec versions.** `/openapi`, `/openapi.json` and `/openapi.yaml` serve OpenAPI 3.1, and `/api` serves Swagger v2. `/openapi` and `/api` follow the request's `Accept` header; the `.json` and `.yaml` URLs always return that format. See [BasicRestServlet Swagger and OpenAPI 3.1](/docs/topics/BasicRestServletSwagger) and [Swagger](/docs/topics/Swagger).
+
+## Next steps
+
+- Test the API and its documentation endpoints in JUnit: [Test an endpoint with MockRestClient](/docs/guides/TestAnEndpoint).
+- Call the API from Java through a typed interface: [Call an existing REST API with a typed proxy](/docs/guides/TypedProxyClient).
+- Require a bearer token on the API: [Secure an endpoint with JWT and OAuth](/docs/guides/SecureAnEndpoint).
diff --git a/pages/release-notes/10.0.0-marshall.md b/pages/release-notes/10.0.0-marshall.md
new file mode 100644
index 0000000000..eadbcb639f
--- /dev/null
+++ b/pages/release-notes/10.0.0-marshall.md
@@ -0,0 +1,2938 @@
+---
+id: "10.0.0-marshall"
+title: "Release 10.0.0: Marshall, commons and config"
+---
+
+# Release 10.0.0: Marshall, commons and config
+
+This page is part of the [Release 10.0.0](/docs/release-notes/10.0.0) release notes. The overview page lists the major changes, the breaking changes and the API changes for the whole release.
+
+## juneau-marshall / juneau-commons
+
+### View-based property projection (`@MarshalledProp(view=...)`)
+
+Juneau now supports **view-based projection**: declare named views on bean properties and select an active
+view per serialize/parse call so only the in-view properties are emitted or consumed. This is the Juneau
+equivalent of Jackson's `@JsonView` / `MapperFeature.DEFAULT_VIEW_INCLUSION`.
+
+Tag a property with one or more string view names via the new `view` member on `@MarshalledProp`:
+
+```java
+public class Person {
+ public String id; // untagged — visible in every view
+
+ @MarshalledProp(view = "summary")
+ public String name;
+
+ @MarshalledProp(view = "detail")
+ public String description;
+}
+```
+
+Activate a view on any serializer or parser builder, or override it per call on a session:
+
+```java
+// Context-level default:
+var s = Json5Serializer.DEFAULT.copy().activeView("summary").build();
+s.write(person); // → {id:'1',name:'Alice'}
+
+// Per-call session override:
+s.createSession().activeView("detail").build().write(person); // → {description:'A person',id:'1'}
+```
+
+Key behaviours:
+- **Default-view policy:** untagged properties are included in all views by default (mirrors Jackson's
+ `DEFAULT_VIEW_INCLUSION=true`). Call `disableDefaultViewInclusion()` to reverse.
+- **Union semantics:** `@MarshalledProp(view = {"A","B"})` makes a property visible when `A` *or* `B` is active.
+- **Flat string names:** no marker-class hierarchy required.
+- **Parse-side:** out-of-view input properties are routed through the existing unknown/ignored-property
+ mechanism (silently ignored by default; `ignoreUnknownBeanProperties(false)` makes them throw).
+- **Mix-in support:** view membership can be applied to unmodifiable classes via `@MarshalledPropApply` /
+ `applyAnnotations()`, just like any other `@MarshalledProp` attribute.
+- **Cross-format:** projection is model-level and behaves identically across JSON, XML, HTML, MsgPack, and
+ all other Juneau formats.
+
+See the [View-Based Projection](/docs/topics/ViewProjection) topic page for full documentation and a
+Jackson migration table.
+
+### Hibernate lazy-proxy unwrap + JPA cookbook
+
+Marshalling now deproxifies objects whose runtime class `ClassUtils.getProxyFor` recognizes,
+including Hibernate 6/7 lazy association proxies (`$HibernateProxy$` in the class name).
+Serializers use the unwrapped class (immediate superclass for subclass proxies; first
+interface for JDK proxies) so handler fields and proxy `_type` names are not emitted.
+This is a core `getClassMetaForObject` change across all formats — not a
+`juneau-marshall-hibernate` module, and not a parse-into-`@Entity` feature.
+
+See [JPA and Hibernate](/docs/topics/JpaAndHibernate).
+
+## juneau-config / juneau-commons
+
+### Config profiles + relaxed binding (cloud-native environment config)
+
+Juneau 10.0 adds two Spring-Boot-style ergonomics to the config + settings system, both additive and off-by-default
+so existing configs are unaffected:
+
+- **Config profiles** — a `<name>-<profile>` overlay (e.g. `my-stage.cfg`, or `my-stage.yml` for YAML bases) is merged
+ over the base config when a profile is active. Profile entries win over base, base-only keys survive, and for multiple
+ active profiles the **last one wins**. Activated via `Config.Builder.profiles(...)` or the `juneau.profiles.active`
+ key (system property / env var / config). Resolution happens at the store layer (`ProfileConfigStore` + `ProfileMerge`),
+ so variable resolution, format handling, and **live reload** (a change to the base *or* any active profile file
+ re-fires change listeners) all behave exactly as for a single-file config. When the Spring Boot bridge is present,
+ `juneau.profiles.active` **piggybacks on `Environment.getActiveProfiles()`** for a single source of truth.
+- **Relaxed binding** — a single logical config key resolves from any common environment spelling, so k8s-style
+ env vars map cleanly: `my.prop` / `myProp` / `MySection/myKey` are satisfied by `MY_PROP` / `MY_SECTION_MY_KEY`. The
+ default `Settings` env source is wrapped with `RelaxedPropertySource` (verbatim-first, so exact-match is preserved);
+ it can wrap any other `PropertySource` too.
+
+See the new [Config Profiles & Relaxed Binding](/docs/topics/ConfigProfilesAndRelaxedBinding) topic page for the
+activation conventions, overlay/merge precedence, and the candidate-generation rules.
+
+## juneau-marshall
+
+### Token-Streaming and Record-Streaming API
+
+Juneau 10.0 introduces a public low-level streaming surface that complements the POJO databind path with cursor-based push and pull primitives, layered:
+
+- **<a href="/docs/topics/RecordStreaming">RecordReader</a> / <a href="/docs/topics/RecordStreaming">RecordWriter</a>** — whole-value record cursor (`canRead()` / `read(Class)` / `write(Object)`). Format-neutral, works on every Juneau format.
+- **<a href="/docs/topics/TokenStreaming">TokenReader</a> / <a href="/docs/topics/TokenStreaming">TokenWriter</a>** — fine-grained structural cursor (`next()`, `startObject()`, `fieldName(...)`, etc.). For formats whose wire encoding is already token-shaped: JSON, JSON5, JSONL, JSON5L, CBOR, MsgPack.
+
+`TokenReader extends RecordReader` and `TokenWriter extends RecordWriter`, so token-capable formats inherit record-stream semantics for free; record-only formats expose only the record surface so the type system enforces "no nested structure visible at the cursor."
+
+Capability is expressed through **role interfaces** in `org.apache.juneau.marshall.stream` and detected with `instanceof` (there is no capability enum):
+
+- **`RecordReadable` / `RecordWritable`** — `readRecords(input)` / `writeRecords(output)`; implemented by every streaming format.
+- **`TokenReadable` / `TokenWritable`** — `readTokens(input)` / `writeTokens(output)`; implemented by json, json5, jsonl, json5l, cbor, msgpack. (These extend the record roles.)
+- **`ArrayRecordReadable` / `ArrayRecordWritable`** — `readArrayRecords(input)` / `writeArrayRecords(output)`; implemented by json, json5, cbor, msgpack, bson, parquet, yaml, xml, html.
+
+A cursor-quality signal (`isRecordStreaming()` / `isArrayRecordStreaming()`) distinguishes a true O(1) streaming cursor from a buffered one. Writer-only formats (jsonschema, jcs, soap-xml) implement only the `*Writable` roles.
+
+Naturally-multi-record formats (jsonl, json5l, csv, sse) expose `readRecords`/`writeRecords` as a true multi-record cursor: N read calls yield N records; N write calls produce wire output equivalent to `serialize(List.of(records))`.
+
+Opt-in <a href="/docs/topics/ArrayRecordStreaming">readArrayRecords / writeArrayRecords</a> for streaming the elements of a top-level wire array on JSON / JSON5 / CBOR (actually streaming, O(1) memory in array length), plus MsgPack / BSON / Parquet / YAML / XML / HTML (buffered). MsgPack offers a count-overload `writeArrayRecords(output, expectedCount)` that writes the length-prefix header up front and serializes each element directly to the output stream — no per-element buffering. Mism [...]
+
+XML and HTML expose `readArrayRecords(input, rootElementName)` for caller-specified array root elements.
+
+#### REST integration
+
+`TokenReader`, `RecordReader`, `TokenWriter`, `RecordWriter` and concrete subtypes (e.g. `JsonlTokenReader`) are first-class `@RestOp`-method parameter types. Two-layer resolution: declared parameter type filters eligible parsers/serializers; HTTP `Content-Type` / `Accept` negotiates within that filter. Mismatch returns HTTP 415. See the new <a href="/docs/topics/RestStreamingIntegration">REST Streaming Integration</a> topic page.
+
+##### Documentation
+
+A full topic-page family lives under "2.50. Token / Record Streaming" in the Marshalling section: <a href="/docs/topics/TokenRecordStreaming">Basics</a>, <a href="/docs/topics/RecordStreaming">Record Streaming</a>, <a href="/docs/topics/TokenStreaming">Token Streaming</a>, <a href="/docs/topics/ArrayRecordStreaming">Array-Record Streaming</a>, and <a href="/docs/topics/RestStreamingIntegration">REST Streaming Integration</a>.
+
+The streaming surface is **purely structural** — object swaps and `@Schema` annotations apply only on the POJO databind path, never at the token layer. Both surfaces are additive; the existing POJO ↔ document API remains the primary, recommended path.
+
+### JSON5L line-delimited format
+
+Juneau 10.0 adds **JSON5L** — a new line-delimited format combining the relaxed [JSON5](/docs/topics/Json5) dialect with [JSONL](/docs/topics/JsonlSupport)'s one-document-per-line framing. It ships as a new `org.apache.juneau.marshall.json5l` package (`Json5lSerializer` / `Json5lParser` / sessions / `Json5lTokenReader`), a `Json5l` marshaller, and a `@Json5lConfig` annotation.
+
+- **Strict by default, sugar opt-in** — the serializer emits strict RFC-8259 JSON per line by default (byte-identical to `JsonlSerializer`), keeping output maximally portable. `Json5lSerializer.create().json5Sugar()` switches the per-line output to JSON5 sugar (single-quoted strings, unquoted field names where safe).
+- **Full JSON5 parse leniency** — the parser accepts the full JSON5 dialect on each line (comments, unquoted/single-quoted keys, trailing commas, relaxed numbers). Because JSON5 is a strict superset of JSON, it also reads plain JSONL input unchanged. Blank and comment-only lines are skipped; inline comments are tolerated; `/* ... */` block comments must open and close on the same line.
+- **Token-streaming and array-record streaming parity** with JSONL (O(1) memory line cursor).
+- **First-class REST media type** `application/json5l` (`text/json5l`, plus reduced-q-value cross-acceptance of the JSONL family), registered in `BasicUniversalConfig` and the `RestClient.universal()` set.
+- **Media-type aliases** — `application/jsonlines` (the type suggested by the JSON Lines spec) is accepted as an alias of `application/jsonl`, and `application/json5lines` as an alias of `application/json5l`, for both `Accept` and `Content-Type` negotiation. Produced responses keep the canonical `application/jsonl` / `application/json5l` Content-Type. `ContentType.APPLICATION_JSONLINES` and `ContentType.APPLICATION_JSON5LINES` constants are added.
+
+See the new <a href="/docs/topics/Json5l">JSON5L Basics</a> topic page.
+
+### Inclusion knob: `nonDefault()` on `Serializer.Builder`
+
+Juneau 10.0 adds a new serializer-side inclusion knob to suppress properties whose value equals the type's
+default — the Juneau equivalent of Jackson's `@JsonInclude(NON_DEFAULT)`. It lives alongside the existing
+`keepNullProperties` / `trimEmptyCollections` / `trimEmptyMaps` / `trimStrings` family and applies uniformly
+across every format (no per-format opt-in).
+
+```java
+public class Person {
+ public String name = "Anonymous";
+ public int age = 0;
+}
+
+var s = JsonSerializer.create().nonDefault().build();
+s.write(new Person()); // → {}
+```
+
+Default values are resolved as:
+- For primitives / boxed wrappers — the Java type default (`0`, `false`, `null`, …).
+- For bean properties — the value held on a freshly-constructed instance via the bean's no-arg constructor.
+ The reference instance is built **once** and cached on `ClassMeta`. If the bean cannot be instantiated, the
+ knob silently skips that bean rather than aborting the serialization.
+
+Equality uses value-based numeric comparison (so `1` ≡ `1.0` ≡ `1L`); everything else uses `Objects.equals`.
+
+The matching annotation setting is `@SerializerConfig(nonDefault = "true")`.
+
+Precedence (first "omit" wins): `keepNullProperties` (handles `null` and `Optional.empty()`) →
+`trimStrings` → `trimEmptyCollections` / `trimEmptyMaps` → `nonDefault`.
+
+### Parser-side null coercion: `Nulls` policy
+
+A new `org.apache.juneau.marshall.Nulls` enum + matching `nulls` setting on `Parser.Builder` /
+`@ParserConfig` / `@MarshalledProp` controls what happens when a `null` reaches a bean property during
+parsing — the Juneau equivalent of Jackson's `@JsonSetter(nulls = …)`. Four modes:
+
+- `LEAVE` — pass `null` through to the setter (current behavior, default).
+- `EMPTY` — substitute `""`, an empty mutable `Collection` / `Map`, or `Optional.empty()`.
+- `DEFAULT` — substitute the bean's reference-instance value for that property.
+- `SKIP` — do not invoke the setter at all; any pre-existing field initializer is preserved.
+
+Per-property setting wins over session, which wins over context default:
+
+```java
+public class Bean {
+ @MarshalledProp(nulls = Nulls.EMPTY)
+ public String s;
+
+ @MarshalledProp(nulls = Nulls.SKIP)
+ public String t = "initial";
+
+ public String u; // falls through to context default
+}
+
+var p = JsonParser.create().nulls(Nulls.LEAVE).build();
+var b = p.read("{\"s\":null,\"t\":null,\"u\":null}", Bean.class);
+// b.s == "" b.t == "initial" b.u == null
+```
+
+See the new [Null & Inclusion Policies](/docs/topics/NullAndInclusionPolicies) topic page for the full
+contract, including a Jackson migration table.
+
+### First-class JDK datatype coverage (`OptionalInt` / `OptionalLong` / `OptionalDouble` / `BitSet`)
+
+Juneau 10.0 extends its direct, in-box JDK datatype coverage with first-class handling for the primitive
+`Optional` variants — `OptionalInt`, `OptionalLong`, `OptionalDouble` — and for `BitSet`.
+
+The primitive `Optional` variants are handled directly on the serializer/parser path, mirroring the
+existing `Optional<T>` plumbing rather than going through a swap. They share the same wire contract as
+`Optional<T>`: empty maps to `null` (omitted by default, emitted as `null` when `keepNullProperties()` is
+set), present emits the unwrapped primitive, and parse-side `null`/absent both resolve back to the matching
+`empty()` sentinel — never a bare `null` inside the `Optional`.
+
+`BitSet` is likewise a first-class type with its own `BitSetFormat` enum — no swap required. The new
+`bitSetFormat` knob selects the textual wire form (`INDICES`, the default comma-delimited set-bit-index
+list; `BITS`, a little-endian bit string; or `HEX`, a hex-encoded byte array) and is configurable at all
+four precedence levels (`@MarshalledProp`, `@Marshalled`, `@MarshalledConfig`, and the `bitSetFormat(...)`
+builder setter), exactly like the other `*Format` enums.
+
+A new [Supported JDK Datatypes](/docs/topics/SupportedJdkDatatypes) topic page enumerates the full coverage
+matrix and contrasts Juneau's first-class-in-box approach with Jackson's per-module `jackson-datatype-*`
+family.
+
+### `MarshalledNode` typed tree façade + RFC 6901 JSON-Pointer addressing
+
+Juneau 10.0 adds a typed tree façade and an RFC 6901 JSON-Pointer surface over the existing `MarshalledMap`/`MarshalledList` collections model (in `org.apache.juneau.marshall.collections`):
+
+- **`MarshalledNode`** — a public, **live** typed-tree façade (not a parallel Jackson-style node hierarchy) wrapping a backing `Map`/`List`/scalar/`null`. Provides node-type introspection (`isObject`/`isArray`/`isValue`/`isNull`), typed accessors (`asString`/`asInt`/`asLong`/`asDouble`/`asBoolean`/`as(Class)`/`value`), navigation (`get(String)`/`get(int)`/`size`), fluent in-place builders (`put`/`add`), a deep-copy `copy()` snapshot, and the RFC 6901 pointer methods `at`/`find`/`set`/`re [...]
+- **`JsonPointer`** — a public RFC 6901 JSON-Pointer helper: `JsonPointer.of(String)`, `eval(root)` (read, null-miss), `set(root, value)` (auto-vivifying write; `-` appends), `remove(root)`, and static `encodeToken`/`decodeToken` (`~0`→`~`, `~1`→`/`). The empty string `""` addresses the whole document; a non-empty pointer must begin with `/`.
+- **Convenience** — `MarshalledMap.at(String)` and `MarshalledList.at(String)` resolve a pointer and return a `MarshalledNode`.
+
+```java
+JsonMap doc = Json.to("{foo:{bar:['x','y']}}", JsonMap.class);
+
+String y = doc.at("/foo/bar/1").asString(); // "y"
+MarshalledNode.of(doc).set("/foo/bar/-", "z"); // appends -> ['x','y','z']
+```
+
+Both `MarshalledNode` and `JsonPointer` are marked **Beta — API subject to change**. See the new [Tree Model & RFC 6901 JSON-Pointer](/docs/topics/MarshalledNodeAndJsonPointer) topic page.
+
+### Protobuf binary wire-format codec
+
+Juneau 10.0 adds a native, bean-driven **Protocol Buffers binary** serializer and parser in the new
+`org.apache.juneau.marshall.protobuf` package — distinct from the existing text-format `marshall.prototext`
+codec (`text/protobuf`). It emits the compact, non-self-describing protobuf binary wire format used by
+`protoc`-generated code, with **no `.proto` schema file or code generation required**: the
+field-number/type schema is derived directly from Juneau bean metadata.
+
+```java
+public class Person {
+ public String name;
+ public int age;
+ public Person() {}
+ public Person(String name, int age) { this.name = name; this.age = age; }
+}
+
+byte[] protobuf = Protobuf.of(new Person("Alice", 30));
+Person parsed = Protobuf.to(protobuf, Person.class); // target type required on read
+```
+
+It is **interop-capable but bean-driven**: lossless out of the box for Juneau-to-Juneau messaging, and
+upgradable field-by-field to true external `protoc` interop via the new `@Protobuf(fieldNumber=…, type=…)`
+annotation.
+
+- **Media types:** produces `application/protobuf`; accepts `application/protobuf` and
+ `application/x-protobuf` (no collision with text `text/protobuf`).
+- **Field numbers:** explicit `@Protobuf(fieldNumber=N)` wins; otherwise auto-assigned sequentially from 1
+ ordered alphabetically by property name, skipping the reserved 19000–19999 band.
+- **Scalar mapping:** sensible Java→proto defaults (`int`→int32, `long`→int64, `String`→string,
+ `enum`→int32 ordinal, `byte[]`→bytes, dates/`BigInteger`→lossless string), each overridable via
+ `@Protobuf(type=…)` (zigzag `sint*`, `fixed*`/`sfixed*`, `uint*`, enum-by-name).
+- **Containers:** packed repeated scalars, repeated tagged strings/bytes/messages, length-delimited nested
+ messages, and `Map<K,V>` as proto `map<>` entry sub-messages. Parsing accepts both packed and unpacked
+ repeated forms.
+- **Deferred (assess-only):** `oneof`, full proto2 presence, unknown-field retention, and well-known types
+ (`Timestamp`/`Duration`/`Any`/wrappers).
+
+See the new [Protobuf Binary Format Basics](/docs/topics/Protobuf) topic page for the full
+scalar/container mapping tables, field-number rules, and round-trip guarantees.
+
+### Parquet external-read interop + binary-native logical types
+
+Juneau 10.0 extends the Parquet codec to read spec-compliant files produced by external writers
+(parquet-mr / Spark / Hive), not just files Juneau itself emits, and to understand the binary-native
+logical types those tools use.
+
+- **External-read fidelity** — the reader now handles **multi-page** column chunks, **multi-row-group**
+ files, and **dictionary-encoded** pages (`DICTIONARY_PAGE` + `PLAIN_DICTIONARY`/`RLE_DICTIONARY`, the
+ parquet-mr default). Previously only the single-page / single-row-group / PLAIN shape Juneau wrote was
+ readable.
+- **Snappy decompression** — a built-in, dependency-free Snappy **block** decoder reads Snappy-compressed
+ pages (the default Spark/parquet-mr compression). Decode-only; write stays `UNCOMPRESSED`/`GZIP`. LZ4 /
+ Zstd / Brotli / LZO remain hard errors (no silent fallback).
+- **Binary-native logical types** — INT96 timestamps, DECIMAL (INT32/INT64-backed, scale-aware),
+ DATE, TIME(micros), and TIMESTAMP(micros) are always decoded on read. On write they are opt-in via
+ `ParquetSerializer.create().nativeLogicalTypes(true)`; the default keeps the existing portable
+ string / `TIMESTAMP_MILLIS` normalization, so default output is byte-for-byte unchanged.
+- **Write knobs** — `pageSize` and `rowGroupSize` are now honored on write, splitting output into
+ multiple pages / row groups.
+
+See the [Parquet Basics](/docs/topics/Parquet) topic page for details.
+
+### Stream-based static marshaller shortcuts
+
+Every marshaller facade now provides **stream-based static shortcuts** alongside the existing `String` / `byte[]`
+shortcuts, so you can parse from and serialize to streams without going through a `DEFAULT` instance:
+
+- **Character facades** (`Json`, `Xml`, `Html`, `Yaml`, `Ini`, `Hjson`, …) add `to(Reader, Class<T>)`,
+ `to(Reader, Type, Type...)`, and `of(Object, Writer)`.
+- **Binary facades** (`MsgPack`, `Cbor`, `Bson`, `Protobuf`, `Parquet`) add `to(InputStream, Class<T>)`,
+ `to(InputStream, Type, Type...)`, and `of(Object, OutputStream)`.
+
+```java
+MyPojo a = Json.to(reader, MyPojo.class);
+Json.of(myPojo, writer);
+
+MyPojo b = MsgPack.to(inputStream, MyPojo.class);
+MsgPack.of(myPojo, outputStream);
+```
+
+Unlike the instance `read(...)` / `write(...)` methods (which throw a checked `IOException`), these static
+shortcuts **wrap any underlying `IOException` into an unchecked `ParseException` (read) or `SerializeException`
+(serialize)**, so they compose cleanly in lambdas and streams. Consistent with the 10.0.0 stream-only I/O
+narrowing, there are intentionally **no `File` overloads** — open a `Reader`/`Writer`/`InputStream`/`OutputStream`
+yourself (e.g. via `Files.newBufferedReader(...)`).
+
+See the [Marshallers](/docs/topics/Marshallers) topic page for details.
+
+### Variant marshaller classes (`Json5R` / `IniR` / `HjsonC`)
+
+Three commonly-used non-default marshaller configurations are now first-class **variant facade classes**, each a
+subclass of its base format facade with its own `DEFAULT` instance and the full static shortcut surface (including
+the new stream-based shortcuts above) bound to that instance:
+
+- `Json5R` — readable (indented) JSON5, backed by `Json5Serializer.DEFAULT_READABLE`.
+- `IniR` — readable (spaced) INI, backed by `IniSerializer.DEFAULT_READABLE`.
+- `HjsonC` — compact (single-line) Hjson, backed by `HjsonSerializer.DEFAULT_COMPACT`.
+
+```java
+String pretty = Json5R.of(myPojo); // readable JSON5
+String compact = HjsonC.of(myPojo); // compact Hjson
+```
+
+These replace the former `Json5.DEFAULT_READABLE`, `Ini.DEFAULT_READABLE`, and `Hjson.DEFAULT_COMPACT` instance
+constants (see Breaking Changes below). The [Marshallers](/docs/topics/Marshallers), [JSON5](/docs/topics/Json5),
+[Hjson](/docs/topics/Hjson), and [Ini](/docs/topics/Ini) topic pages were updated accordingly.
+
+### Additional variant marshaller classes for full format symmetry
+
+Every remaining non-default `*Serializer.DEFAULT_*` / `*Parser.DEFAULT_*` configuration across all marshaller
+formats now has its own first-class **variant facade class**, extending the `Json5R`/`IniR`/`HjsonC` pattern
+above to full symmetry. This is a purely **additive** change — no existing class or constant was removed or
+modified. 27 new variant classes were added:
+
+- **Readable variants:** `JsonR`, `TomlR`, `YamlR`, `PrototextR`, `UonR`, `UrlEncodingR`, `XmlSqR`, `XmlNsSqR`, `HtmlSqR`.
+- **UON:** `UonE` (percent-encoding).
+- **URL-encoding:** `UrlEncodingPlain` (unencoded value parts), `UrlEncodingExpanded` (repeated `key=value` for arrays/collections).
+- **HOCON:** `HoconBraces` (root `{ }` braces), `HoconC` (compact).
+- **XML:** `XmlSq` (single-quoted attrs), `XmlNs` (namespace-aware), `XmlNsSq` (namespace + single-quoted).
+- **HTML:** `HtmlSq` (single-quoted attrs), `HtmlSimpleSq` (single-quoted + JSON type-hint tags disabled).
+- **Binary formats (`Bson`/`Cbor`/`MsgPack`):** `*SpacedHex` and `*Base64` (alternate `byte[]` wire representations), plus `CborNative`/`MsgPackNative` (native-mode parsing — exposes low-level CBOR tag / MessagePack extension-type info; pairs the plain serializer with the native-mode parser).
+
+```java
+String readable = JsonR.of(myPojo); // readable (indented) JSON
+byte[] cbor = CborSpacedHex.of(myPojo); // spaced-hex byte[] binary format
+```
+
+See the [Marshallers](/docs/topics/Marshallers) topic page for the full variant-class table.
+
+### MCP (Model Context Protocol) support (new modules)
+
+Juneau's first [Model Context Protocol](https://modelcontextprotocol.io/) support lands in 10.0.0 across four new modules. (An earlier MCP write-up appeared in the draft, unreleased 9.5 notes describing a single-revision `juneau-bean-mcp` / `juneau-rest-server-mcp` pair; MCP itself had not shipped in any released Juneau version at that point, so that content has been withdrawn from `9.5.0` and replaced by this re-layered design, which lands for the first time here.) The implementation is [...]
+
+- **`juneau-bean-jsonrpc`** — revision-neutral JSON-RPC 2.0 envelope beans.
+- **`juneau-rest-server-mcp`** — revision-neutral REST-server core (tool/prompt/resource registry, dispatch contract, pagination, the two HTTP entry points), with zero compile-time knowledge of any MCP protocol revision.
+- **`juneau-bean-mcp-v20250618`** — MCP revision `2025-06-18` wire beans (renamed from the withdrawn draft's `juneau-bean-mcp`).
+- **`juneau-rest-server-mcp-v20250618`** — the `2025-06-18` REST-server adapter that binds the neutral core to those wire beans; this is the module application code depends on to actually expose an MCP endpoint today.
+
+### MCP client modules (`juneau-rest-client-mcp*`)
+
+10.0.0 adds Juneau's first first-party MCP client stack:
+
+- **`juneau-rest-client-mcp`** — revision-neutral client core on `juneau-rest-client` (`AbstractMcpClient`) with raw JSON-RPC send/receive, auth-token seam (`McpAuthInterceptor`), response-cache seam (`McpResponseCache` + `InMemoryMcpResponseCache`), and an opaque server->client duplex dispatch seam (`McpServerRequestHandler`).
+- **`juneau-rest-client-mcp-v20250618`** — typed `McpClient` facade for MCP `2025-06-18`.
+- **`juneau-rest-client-mcp-v20260728`** — typed `McpClient` facade for MCP `2026-07-28`, including v2 `_meta`/header plumbing and cache-hint-aware client behavior.
+
+The duplex path builds directly on the new general-purpose SSE APIs in `juneau-rest-client` (`RestRequest.openEventStream()`, `ResponseBody.asEventStream()`) and keeps server->client payloads raw/opaque by design; typed sampling/elicitation modeling remains owned by later MCP server slices.
+
+**Read-only-method stale-connection retry.** Every MCP call is transported as an HTTP `POST`, which a transport can never safely replay on a stale pooled-connection failure (the `POST` may already have executed server-side). `AbstractMcpClient.send()` therefore adds its own **once-only** retry keyed on **JSON-RPC method idempotency** rather than HTTP method: a pre-response stale-connection failure on a side-effect-free method (`initialize`, `server/discover`, `ping`, `tools/list`, `promp [...]
+
+### `juneau-bean-jsonrpc` (new module)
+
+A new bean module, `juneau-bean-jsonrpc`, models the revision-neutral JSON-RPC 2.0 envelope as Juneau `@Marshalled` POJOs. It carries no MCP-specific knowledge — it depends on `juneau-marshall` only — and is the shared framing layer beneath protocol-specific bean modules such as `juneau-bean-mcp-v20250618`.
+
+### Coverage
+
+- **`JsonRpcRequest`** — `jsonrpc`, `id`, `method`, `params`.
+- **`JsonRpcResponse`** — `jsonrpc`, `id`, `result`, `error`. Also carries three `public static` helpers used by dispatch implementations: `ok(id, result)` and `errorResponse(id, code, message[, data])` (both response factories), plus `notification(id)` (a `boolean` predicate testing whether an id represents a JSON-RPC notification, i.e. `id == null`).
+- **`JsonRpcError`** — `code`, `message`, `data`.
+- **`McpException`** — a `RuntimeException` carrying JSON-RPC error fields (`code`, `data`) for handler-side propagation, with `toJsonRpcError()` converting it to a `JsonRpcError`.
+
+### Example
+
+```java
+import org.apache.juneau.bean.jsonrpc.*;
+import org.apache.juneau.json.*;
+
+JsonRpcRequest req = new JsonRpcRequest()
+ .setJsonrpc("2.0")
+ .setId(1)
+ .setMethod("tools/list");
+
+String wire = JsonSerializer.DEFAULT.serialize(req);
+JsonRpcRequest back = JsonParser.DEFAULT.parse(wire, JsonRpcRequest.class);
+
+JsonRpcResponse ok = JsonRpcResponse.ok(1, "pong");
+JsonRpcResponse err = JsonRpcResponse.errorResponse(1, -32601, "Method not found");
+```
+
+### `juneau-rest-server-mcp` (re-layered into a revision-neutral core)
+
+`juneau-rest-server-mcp` has been re-layered from a single-revision implementation into a **revision-neutral core** with zero compile-time knowledge of any MCP protocol revision — enforced by a `maven-enforcer` banned-dependency rule that fails the build if this module ever depends on `juneau-bean-mcp-*` or `juneau-rest-server-mcp-*`. A protocol revision is supplied by an `McpRevision` implementation living in its own adapter module (see `juneau-rest-server-mcp-v20250618` below); a consu [...]
+
+### New Classes
+
+- **`McpRevision`** — the 3-method SPI a protocol revision implements: `protocolVersion()`, `dispatch(McpExchange, McpServerConfig, BeanStore)`, and `errorCode(McpErrorKind)`.
+- **`McpExchange`** — the inbound JSON-RPC envelope plus request-header access, with no servlet or HTTP types attached.
+- **`McpErrorKind`** — revision-neutral classification of a dispatch failure (`INVALID_REQUEST`, `UNKNOWN_METHOD`, `TOOL_NOT_FOUND`, `PROMPT_NOT_FOUND`, `RESOURCE_NOT_FOUND`, `INVALID_PARAMS`, `INTERNAL_ERROR`, `PARSE_ERROR`); each revision maps kinds to its own JSON-RPC error codes via `McpRevision.errorCode(McpErrorKind)`.
+- **`McpServerConfig`** — re-shaped aggregate registry of tools, prompts, resources, `name`/`version`, `instructions`, and pagination strategy (`cursor`). No longer holds `protocolVersion` (now owned by `McpRevision.protocolVersion()`) or `capabilities` (now owned by a revision-specific hook — see the `2025-06-18` adapter below); `setProtocolVersion(String)` has no replacement.
+- **`McpRestServlet`** — abstract `BasicRestServlet` subclass exposing `POST /`. Subclasses implement `createMcpConfig()`; a new abstract `revision()` method supplies the bound protocol revision.
+- **`McpEndpoint`** — mixin interface exposing `POST /mcp` on any `@Rest` resource, re-shaped the same way (`getMcpConfig()` plus an abstract `revision()`).
+- **Neutral model** — `McpToolSpec`/`McpToolOutcome`, `McpPromptSpec`/`McpPromptArgument`/`McpPromptMessage`/`McpPromptOutcome`, `McpResourceSpec`/`McpResourceOutcome`, `McpContentBlock`, `McpResourceContents`, `McpRole`, and the unconstrained JSON-object carrier `McpSchema`. This is what a handler is written against, so a handler compiles against exactly one protocol revision's worth of assumptions: none.
+- **`McpToolHandler`**, **`McpPromptHandler`**, **`McpResourceHandler`** — raw `@FunctionalInterface` handlers, now typed against the neutral spec/outcome types above (previously typed against wire beans). Each keeps its default-throwing `descriptor()`, so a lambda implementing only the call/get/read method still compiles.
+- **`McpCursor`**, **`McpPage<T>`** — pagination strategy seam, unchanged: `McpCursor.SINGLE_PAGE` (default, returns everything in one page) and `McpCursor.fixedSize(n)` (opaque integer-offset paging).
+- **`McpParamUtils`** — shared `params`-coercion plumbing (`asMap`/`strParam`/`mapParam`) used by revision `dispatch()` implementations.
+
+**Removed:** `McpDispatcher` and the static `Mcp` façade are gone with no drop-in replacement class — their method-table logic now lives in each revision's own `McpRevision.dispatch()` implementation (see `org.apache.juneau.rest.server.mcp.v20250618.McpRevision` below), and their internal plumbing helpers were redistributed onto `JsonRpcResponse`, `McpCursor`, and the new `McpParamUtils`.
+
+See [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp) for the full topic.
+
+### `juneau-bean-mcp-v20250618` (renamed from `juneau-bean-mcp`)
+
+The bean module has been renamed from `juneau-bean-mcp` to `juneau-bean-mcp-v20250618` (package `org.apache.juneau.bean.mcp.v20250618`) as part of the re-layering above — the name change makes room for a future sibling module under the same `org.apache.juneau.bean.mcp` namespace should a later MCP protocol revision need one. The module still models the MCP `2025-06-18` wire format as Juneau `@Marshalled` POJOs that round-trip cleanly through any Juneau serializer/parser (JSON, JSON5, Mes [...]
+
+### Coverage
+
+- **Initialization** — `InitializeRequest`, `InitializeResult`, `Implementation`, `ClientCapabilities`, `ServerCapabilities`, and the per-feature capability beans (`ToolCapability`, `PromptCapability`, `ResourceCapability`, `LoggingCapability`, `RootsCapability`).
+- **Tools** — `Tool`, `CallToolRequest`, `CallToolResult`, `ListToolsResult`, plus the `JsonSchema` argument-schema bean. `Tool.outputSchema` and `CallToolResult.structuredContent` — part of MCP structured tool output since `2025-06-18` but previously missing from this module — are now present, closing a prior gap in this revision's own bean coverage; see below.
+- **Prompts** — `Prompt`, `PromptArgument`, `GetPromptRequest`, `GetPromptResult`, `ListPromptsResult`, `PromptMessage`, `Role`.
+- **Resources** — `Resource`, `ReadResourceRequest`, `ReadResourceResult`, `ListResourcesResult`, plus polymorphic `ResourceContents` (`TextResourceContents`, `BlobResourceContents`).
+- **Polymorphic content blocks** — `Content` interface with `TextContent`, `ImageContent`, `EmbeddedResourceContent` discriminated by a `type` property.
+- **Constants** — `McpMethods` (JSON-RPC method names) and `McpProtocol` (version constants, including the `JSON_RPC_2_0` literal).
+
+### Polymorphic Serialization
+
+`Content` and `ResourceContents` use `@Marshalled(typePropertyName="type", dictionary={...})` to discriminate at the wire level. Configure your serializer with `addBeanTypes()` to emit the discriminator on output:
+
+```java
+JsonSerializer ser = JsonSerializer.create()
+ .addBeanTypes()
+ .typePropertyName(Content.class, "type")
+ .typePropertyName(ResourceContents.class, "type")
+ .build();
+```
+
+### Example
+
+```java
+import org.apache.juneau.bean.jsonrpc.*;
+import org.apache.juneau.bean.mcp.v20250618.*;
+import org.apache.juneau.json.*;
+
+JsonRpcRequest req = new JsonRpcRequest()
+ .setJsonrpc(McpProtocol.JSON_RPC_2_0)
+ .setId(1)
+ .setMethod(McpMethods.TOOLS_LIST);
+
+String wire = JsonSerializer.DEFAULT.serialize(req);
+JsonRpcRequest back = JsonParser.DEFAULT.parse(wire, JsonRpcRequest.class);
+```
+
+See [juneau-bean-mcp-v20250618](/docs/topics/JuneauBeanMcp) for the full topic.
+
+### `juneau-rest-server-mcp-v20250618` (new module)
+
+A new adapter module, `juneau-rest-server-mcp-v20250618` (package `org.apache.juneau.rest.server.mcp.v20250618`), binds the revision-neutral `juneau-rest-server-mcp` core to the `2025-06-18` wire beans in `juneau-bean-mcp-v20250618`.
+
+### New Classes
+
+- **`org.apache.juneau.rest.server.mcp.v20250618.McpRevision`** — the `McpRevision` implementation for protocol revision `2025-06-18`. Replaces the old `McpDispatcher`/`Mcp` façade: owns the JSON-RPC method table (`initialize`, `ping`, `tools/list|call`, `prompts/list|get`, `resources/list|read`) and the error-code table. The error-code table preserves a known-wrong mapping carried over unmodified from the pre-re-layering dispatcher for three of the four "not found" kinds — unknown-metho [...]
+- **`org.apache.juneau.rest.server.mcp.v20250618.McpRestServlet`** — concrete abstract servlet for this revision; subclass it (instead of the core `McpRestServlet`) and implement `createMcpConfig()`. Exposes a `protected ServerCapabilities capabilities()` hook (returns `null` by default) for explicitly overriding the `initialize` capabilities advertisement instead of relying on auto-derivation from the registered tool/prompt/resource lists.
+- **`org.apache.juneau.rest.server.mcp.v20250618.McpEndpoint`** — mixin interface for this revision, at parity with `org.apache.juneau.rest.server.mcp.v20250618.McpRestServlet`: implement `getMcpConfig()`, and optionally override its `default ServerCapabilities capabilities()` hook.
+- **`McpTypedPromptHandler<A>`**, **`McpTypedHandlers`** (prompt-only) — the typed *prompt* sugar layer stays here, since it's genuinely revision-specific by construction: it binds arguments into this revision's wire-bean argument types and adapts the result to the neutral `McpPromptHandler` raw interface the core registry holds.
+
+**Typed *tool* sugar is revision-neutral, not revision-specific.** `McpTypedToolHandler<A,R>` and its adaptation into a raw `McpToolHandler` now live in the core (`org.apache.juneau.rest.server.mcp`), not this adapter — one typed tool handler serves either dated adapter unchanged, deriving `inputSchema`/`outputSchema` from its declared argument/result `Type`s and producing `structuredContent` automatically. This corrects an earlier plan-time assumption that all typed sugar was dated by c [...]
+
+**Gap closed: structured tool output, present since `2025-06-18`, is now fully supported end to end.** `Tool.outputSchema` and `CallToolResult.structuredContent` were part of the MCP `2025-06-18` specification from the start (constrained to object-shaped schemas and a JSON-object `structuredContent` value) but were missing from this module's `Tool`/`CallToolResult` beans and unreachable from the neutral core. 10.0.0 adds both fields to the `2025-06-18` wire beans, adds matching `outputSc [...]
+
+**New behavior:** on a given `McpServerConfig`'s first dispatch, this revision validates that every registered tool's `McpSchema` uses only the 6 JSON Schema keywords its wire `JsonSchema` bean can represent (`type`, `properties`, `required`, `additionalProperties`, `items`, `$defs`) — throwing `IllegalArgumentException` naming the offending tool and keyword if not, rather than silently dropping an unsupported keyword on the wire. This same first-dispatch check now also covers `outputSch [...]
+
+### Drop-in servlet
+
+```java
+@Rest(path="/mcp")
+public class MyMcpServlet extends McpRestServlet {
+ @Override
+ protected McpServerConfig createMcpConfig() {
+ return new McpServerConfig().setName("my-server").setVersion("1.0.0").addTool(new MyEchoTool());
+ }
+}
+```
+
+### Mixin on an existing resource
+
+```java
+@Rest(path="/api")
+public class MyResource extends BasicRestServlet implements McpEndpoint {
+ @Override
+ public McpServerConfig getMcpConfig() {
+ return new McpServerConfig().addTool(new MyEchoTool());
+ }
+}
+```
+
+### Typed handlers
+
+Typed *tools* use the revision-neutral `McpTypedToolHandler<A,R>` (core), so the same handler works unchanged against this adapter or the `2026-07-28` adapter:
+
+```java
+public class WeatherArgs { String city; String unit; /* getters/setters */ }
+public class WeatherResult { String summary; /* getters/setters */ }
+
+McpTypedToolHandler<WeatherArgs, WeatherResult> typed = new McpTypedToolHandler<>() {
+ @Override public McpToolSpec descriptor() { return new McpToolSpec().setName("weather"); }
+ @Override public Type argumentType() { return WeatherArgs.class; }
+ @Override public Type resultType() { return WeatherResult.class; }
+ @Override public WeatherResult call(WeatherArgs a, BeanStore ctx) { return new WeatherResult(); }
+};
+
+config.addTool(typed);
+```
+
+`inputSchema`/`outputSchema` are derived from `argumentType()`/`resultType()`; the returned `R` is canonicalized once into `structuredContent` and mirrored as compact JSON text by default (override `mirrorStructuredContentAsText()` to opt out). Typed *prompts* still use the dated `McpTypedPromptHandler<A>` (see "New Classes" above) and adapt via `McpTypedHandlers.adaptPrompt(...)`.
+
+### Pagination
+
+```java
+config.setCursor(McpCursor.fixedSize(50));
+```
+
+`McpCursor` lives in the neutral core (above) and is unchanged: it receives the full descriptor list and the inbound cursor token, and returns an `McpPage<T>` containing the slice plus an opaque `nextCursor`. Custom strategies plug in via the `McpCursor` functional interface.
+
+### Capability override
+
+```java
+@Rest(path="/mcp")
+public class MyMcpServlet extends McpRestServlet {
+ @Override
+ protected McpServerConfig createMcpConfig() {
+ return new McpServerConfig().addTool(new MyEchoTool());
+ }
+
+ @Override
+ protected ServerCapabilities capabilities() {
+ return new ServerCapabilities().setLogging(new LoggingCapability());
+ }
+}
+```
+
+Returning `null` (the default) leaves capabilities auto-derived from the registered tool/prompt/resource lists. Returning a non-`null` value bypasses auto-derivation entirely — the way to advertise `resources.subscribe`, `logging`, `listChanged`, or `experimental`, none of which are derivable from a handler registry. `McpEndpoint` exposes the equivalent hook as a `default` interface method for the mixin path.
+
+### Notifications
+
+Requests without an `id` are treated as JSON-RPC notifications: handlers run, exceptions are swallowed, and `dispatch()` returns `null`. The REST servlet/mixin writes an empty body so transports can map this to `204 No Content`.
+
+See [juneau-rest-server-mcp-v20250618](/docs/topics/JuneauRestServerMcp) for the full topic.
+
+### MCP `2026-07-28` — cache hints (SEP-2549) + `resources/templates/list`
+
+The `2026-07-28` bean and REST-server adapter modules (`juneau-bean-mcp-v20260728`, `juneau-rest-server-mcp-v20260728`) add [SEP-2549](https://modelcontextprotocol.io/) cache hints and a resource-template listing endpoint. Both are `2026-07-28`-only — the neutral `juneau-rest-server-mcp` core and the `2025-06-18` adapter are untouched.
+
+- **Cache hints** — `tools/list`, `prompts/list`, `resources/list`, `resources/templates/list`, and `resources/read` results can each carry a nullable, top-level `ttlMs` (`Integer`) and `cacheScope` (`McpCacheScope`: `public`/`private`) pair, via the new `CacheableResult<T>` CRTP base that `ListToolsResult`, `ListPromptsResult`, `ListResourcesResult`, `ListResourceTemplatesResult`, and `ReadResourceResult` all extend. Fields are omitted from the wire when unset — there is no forced defau [...]
+- **`resources/templates/list`** — a new v2-only endpoint (`McpMethods.RESOURCES_TEMPLATES_LIST`) listing `ResourceTemplate` descriptors (`uriTemplate`, `name`, `title`, `description`, `mimeType`) registered via the neutral `McpServerConfig.addResourceTemplate(McpResourceTemplateSpec)`. Registering at least one resource template auto-derives the `resources` capability, same as resources. The pre-built neutral `McpResourceTemplateSpec` descriptor and this endpoint are designed for reuse b [...]
+- **Static, adapter-owned configuration only** — a server configures cache hints once, at construction time, via the new `McpCacheConfig`/`McpCacheHint` types, mutated in place through the `cache(Consumer<McpCacheConfig>)` configure-block on the `McpOptions` aggregate (see the config-consolidation entry further down this page). Precedence is atomic per list method (method-specific hint, else a shared default, else no cache fields) and, for `resources/read`, adds an exact-URI override map [...]
+
+See [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#cache-hints-mcp-2026-07-28-sep-2549) and [juneau-bean-mcp-v20260728](/docs/topics/JuneauBeanMcp#mcp-2026-07-28-cache-hints-resource-templates) for the full topics.
+
+### MCP — cross-revision resource-template reads and completions (`completion/complete`)
+
+Building on the `resources/templates/list` endpoint above, 10.0.0 makes registered resource templates **executable** through ordinary `resources/read`, and makes prompt arguments and resource-template variables **completable** through the new `completion/complete` method — on both `2025-06-18` and `2026-07-28`. This closes `2025-06-18`'s prior resource-template gap (it previously had no `ResourceTemplate`/`ListResourceTemplatesResult` beans, no `resources/templates/list`, and no way to s [...]
+
+- **`McpResourceTemplateHandler` (new, revision-neutral core)** — pairs the existing `McpResourceTemplateSpec` descriptor with a variable-aware `read(String uri, Map<String,String> variables, BeanStore ctx)` and an optional per-variable `completer(String variableName)`. `McpServerConfig`'s primary resource-template registration is now handler-based (`getResourceTemplates()`/`setResourceTemplates(List<McpResourceTemplateHandler>)`/`addResourceTemplate(McpResourceTemplateHandler...)`); the [...]
+- **A dependency-free RFC 6570 reverse matcher (new, `McpUriTemplateMatcher`, revision-neutral core)** — reliably reverse-matches one narrow, invertible subset of RFC 6570 (simple/reserved/fragment scalars, path/label segments, ordered query expressions) and extracts decoded, immutable, insertion-ordered variables. This does **not** cover every legal RFC 6570 form and Juneau does **not** perform any URI-template expansion (client-side or otherwise): exploded/prefix/matrix/multi-variable/ [...]
+- **Neutral completion domain and SPI (new)** — `McpCompleter` (`@FunctionalInterface`), `McpCompletionRef`, `McpCompletionRequest`, and `McpCompletionResult` model a `completion/complete` request/result independent of either dated wire format. `McpPromptArgument` gains a nullable, non-wire `completer` property (its existing `name`/`description`/`required` fields are unchanged and remain the sole wire-mapped fields). Unknown/uncompletable completion targets return a successful empty comp [...]
+- **`2025-06-18` backfill** — adds `ResourceTemplate` and `ListResourceTemplatesResult` beans (the same five/two-field shape `2026-07-28` already had, minus cache fields), `McpMethods.RESOURCES_TEMPLATES_LIST`, and `resources/templates/list` dispatch, closing this revision's prior gap. Missing-resource reads (exact or template) continue to report this revision's existing known-wrong `-32601`.
+- **`2026-07-28`** — reuses its existing C2 `ResourceTemplate`/`ListResourceTemplatesResult`/mapping/pagination/cache-hint machinery unchanged; template-backed reads apply the same cache-hint precedence as exact-resource reads, keyed by the original concrete request URI. Missing-resource reads report `RESOURCE_NOT_FOUND` (`-32602`).
+- **New completion wire beans (both revisions, independent copies)** — `CompleteRequest`, `CompletionReference` (closed polymorphic base with `PromptReference`/`ResourceTemplateReference`, discriminated `ref/prompt`/`ref/resource`), `CompletionArgument`, `CompletionContext`, `CompleteResult`, `Completion`, and an empty `CompletionCapability` marker under a new nullable `ServerCapabilities.completions`, auto-derived only when at least one completer is registered. `McpMethods.COMPLETION_CO [...]
+- **Included refactor: `McpJsonValueSafety` relocated to `juneau-commons`.** The MCP core's bounded-depth/node/deadline JSON-value walker (used for structured tool-output safety) moves to `juneau-commons` as `org.apache.juneau.commons.utils.JsonValueSafety`, with an identical API and behavior — a rename-only move (drops the `Mcp` prefix, since the class was already protocol-neutral) bundled into this same change because it touches the same neutral core and both dated adapters. `juneau-co [...]
+- **Scope fence, explicitly out of this change** — resource subscriptions (`resources/subscribe`/`resources/unsubscribe` on `2025-06-18`, the newer `subscriptions/listen` streaming request on `2026-07-28`) remain unimplemented and are tracked separately; client-side RFC 6570 expansion and a general-purpose URI-template API are out of scope; and Juneau does not rank, filter, deduplicate, or paginate completion values beyond the protocol's single-response 100-value cap.
+
+See [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#resource-templates-reads-and-completions) and [juneau-bean-mcp-v20250618](/docs/topics/JuneauBeanMcp#completions-completioncomplete-new-in-1000) for the full topics.
+
+### MCP `2026-07-28` — `_meta` wire compliance + trace-context propagation + Spring Boot convenience class
+
+`2026-07-28`-only. Brings `juneau-bean-mcp-v20260728`'s per-request/per-result metadata into exact alignment with the MCP `2026-07-28` schema, adds distributed trace-context propagation through that same `_meta` carrier, and adds a Spring Boot convenience base class. `2025-06-18` is untouched by all three changes.
+
+- **`_meta` wire-compliance reshape.** The neutral JSON-RPC envelope beans (`JsonRpcRequest`/`JsonRpcResponse`) carry no `_meta` property; it now lives under `params._meta` (new `RequestMeta`, via a new `RequestParams<T>` CRTP base on `CallToolRequest`/`GetPromptRequest`/`ReadResourceRequest`/`CompleteRequest`, plus `RequestParamsOnly` for the metadata-only methods) with the exact `io.modelcontextprotocol/`-prefixed negotiation keys the schema mandates, and under `result._meta` (new `Res [...]
+- **Trace-context propagation.** `params._meta` may additionally carry bare (unprefixed) W3C `traceparent`/`tracestate`/`baggage` keys. When an active `TracerHook` is registered, an explicit `params._meta` value wins over the equivalent HTTP header (independently per field); the active trace context is echoed back into `result._meta` on success; and the OpenTelemetry bridge (when present) enriches the span with pinned, experimental `gen_ai.*`/`mcp.*` attributes and maps JSON-RPC errors ( [...]
+- **`SpringMcpRestServlet` convenience class (both revisions).** A new `SpringMcpRestServlet` (`org.apache.juneau.rest.server.mcp.v20250618.SpringMcpRestServlet` and `.v20260728.SpringMcpRestServlet`) combines `BasicSpringRestServlet` with the revision's `McpEndpoint` mixin — the simplest way to expose MCP from a Spring Boot resource with full Spring dependency-injection support in tool/prompt/resource handlers; implement only `getMcpConfig()`. **Don't** subclass the plain `McpRestServle [...]
+
+See [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#trace-context-propagation-mcp-2026-07-28) (Spring Boot + trace-context sections), [juneau-bean-mcp-v20260728](/docs/topics/JuneauBeanMcp#mcp-2026-07-28-metadata-and-trace-context-_meta) (bean catalog), and [Observability](/docs/topics/RestServerObservability#mcp-trace-context-propagation-2026-07-28) (framework-level seam + OTel bridge) for the full topics.
+
+### MCP `2026-07-28` — `McpOptions` config consolidation
+
+`2026-07-28`-only. The six previously-separate per-binding behavior-config hooks the v2 adapter accumulated across the MRTR, cache-hints, and subscriptions work above (`capabilities`, `instructions`, `cache`, `mrtr`, `subscriptions`, `subscriptionBroker` — spread across mismatched `xxxConfig()` single-method and `createXxx()`/`getXxx()` pair idioms, with divergent defaults between the mixin and servlet paths) are consolidated into a single aggregate, **before this never-shipped API's fir [...]
+
+- **New `McpOptions`** (`org.apache.juneau.rest.server.mcp.v20260728`) — a mutable-setup fluent bean, same idiom as the neutral `McpServerConfig` and the nested `McpCacheConfig`/`McpMrtrConfig`/`McpSubscriptionsConfig` types it wraps. Plain `getXxx()`/`setXxx(...)` for `capabilities`/`instructions`/`subscriptionBroker`, plus `Consumer`-style configure-blocks (`cache(Consumer<McpCacheConfig>)`, `mrtr(Consumer<McpMrtrConfig>)`, `subscriptions(Consumer<McpSubscriptionsConfig>)`) that mutate [...]
+- **One override replaces six.** `McpRestServlet` gains `createMcpOptions()` (mirroring `createMcpConfig()`); `McpEndpoint` gains `getMcpOptions()` (mirroring `getMcpConfig()`). The prior six hooks (`capabilities()`, `instructions()`, `cacheConfig()`/`createCacheConfig()`, `mrtrConfig()`/`createMrtrConfig()`, `subscriptionsConfig()`/`createSubscriptionsConfig()`, the v2 `subscriptionBroker()` override) and the wiring seam `subscriptionsConfigBean()` are gone from the user-visible surface.
+- **Uniform per-binding memoization.** `McpOptions` is invoked once per binding and published as a single internal `@Bean`, exactly like tools/prompts — and this internal publication is authoritative, taking precedence over any Spring `@Bean McpOptions` in the application context (Spring users inject their configuration *into* the `createMcpOptions()`/`getMcpOptions()` override instead of publishing a bare `@Bean McpOptions`). This also **unifies a prior default-sharing inconsistency**: [...]
+- **Subscription broker folded in.** `McpOptions.getSubscriptionBroker()` defaults to `null` ("framework-derived"): the binding lazily derives and memoizes a `BasicMcpSubscriptionBroker` sized from `subscriptions`' `queueSize`. Call `setSubscriptionBroker(...)` to supply a custom broker instead.
+- **Client-side elicitation naming split, for symmetry.** `org.apache.juneau.rest.client.mcp.v20260728.ElicitationAccess` is replaced by an `ElicitationRequests` (reading server→client requests: `isInputRequired`/`requests`/`requestState`) + `ElicitationResponses` (encoding client→server answers: `toInputResponse`/`toInputResponses`) pair, matching the naming already used by the server-side helpers of the same names.
+- **Neutral core untouched.** `juneau-rest-server-mcp`'s revision-neutral `subscriptionBroker()`/`getSubscriptionBroker()` seam is unchanged; only the v2 adapter delegates through `McpOptions`.
+
+Because `McpOptions` and the v2 adapter are never-shipped 10.0.0 APIs, this is a clean replace with no deprecation shims.
+
+See [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#configuring-the-endpoint-mcpoptions-mcp-2026-07-28) and [juneau-rest-client-mcp](/docs/topics/JuneauRestClientMcp#elicitation-mcp-2026-07-28-sep-2322) for the full topics.
+
+### MCP `2026-07-28` — MRTR `KeyProvider` SPI for shared/rotating sealing keys
+
+`2026-07-28`-only. Introduces a key-provider abstraction beneath the MRTR `AeadRequestStateCodec` (shipped unreleased, above) so operators can supply a stable shared sealing key — fixing horizontally-scaled RESUME — and roll keys via a `keyId`, while keeping today's per-process ephemeral key as the zero-config default.
+
+- **New `KeyProvider` SPI** (`org.apache.juneau.rest.server.mcp.v20260728`) — `currentKey(): KeyedSecret` (the key to seal new tokens with, plus its `keyId`) and `resolveKey(keyId): Optional<SecretKey>` (never throws; empty on an unknown/retired `keyId`). `KeyedSecret` is a small `keyId`/`SecretKey` record with non-blank/length-bounded `keyId` validation.
+- **Two built-ins.** `EphemeralKeyProvider` preserves today's exact zero-config behavior (a random AES-256 key **and** a random `keyId` per instance, still not restart-durable or cross-instance-shareable). `StaticKeyProvider` is an immutable, statically-configured keyring for the shared/rotating case — a `StaticKeyProvider.of(keyId, key)` one-liner, or a builder (`create().addKey(...).current(...).build()`) supporting overlapping keys during a rollover — plus an `aesKey(byte[] | String)` [...]
+- **`AeadRequestStateCodec` now takes a `KeyProvider`.** The no-arg constructor is unchanged behaviorally (`this(new EphemeralKeyProvider())`); a new `AeadRequestStateCodec(KeyProvider)` constructor is the extension point. The sealed-token wire format grows from two to four dot-joined segments — `version . b64url(keyId) . b64url(nonce) . b64url(ciphertext+tag)` — with `keyId` authenticated (folded into the AEAD's AAD) but not encrypted (it must be readable before decryption selects the k [...]
+- **`McpMrtrConfig.setKeyProvider(KeyProvider)` (new convenience).** Sugar for `setCodec(new AeadRequestStateCodec(value))`, reachable through the `McpOptions` aggregate via `new McpOptions().mrtr(m -> m.setKeyProvider(kp))`. Last-wins against `setCodec(...)`; no getter for the provider (`getCodec()` remains the sole accessor).
+- **Error mapping unchanged.** The never-throw `unseal` contract and the existing `-32602`/`-32022`/`-32023` error codes are untouched — an unknown/retired `keyId` flows through the same `Optional.empty()` path as any other unseal failure, surfacing as the standard `-32602` "Invalid or tampered requestState".
+- **Scope fence, explicitly out of this change** — the OAuth/OIDC authorization SEPs and the MRTR hardenings (principal-in-AAD, replay cache, argument-hash binding) are tracked separately and untouched here; the pre-existing `-32602`/`-32002` missing-resource v1 error-code mismatch is now resolved — see the resource-not-found error-code correction below.
+
+See [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#key-management-keyprovider) and the [MRTR key management recipes](/docs/topics/JuneauMcpRecipes#mrtr-key-management-v2-only) for the full topics.
+
+### MCP `2026-07-28` — MRTR replay protection + argument-hash sealing
+
+`2026-07-28`-only. Adds two optional hardenings on top of the MRTR pause/resume foundation above: a replay cache to reject reuse of a consumed `requestState` token, and an always-on hash check binding a resume's `arguments` back to what the original, paused call authorized.
+
+- **Replay protection, opt-in.** By default a `requestState` token is unchanged — a multi-use bearer credential, replayable any number of times within its TTL. A new `ReplayCache` SPI (`org.apache.juneau.rest.server.mcp.v20260728`) — a single atomic `checkAndRecord(String jti, long expiresAtMs): boolean` — narrows that default to single-use once wired via `McpMrtrConfig.setReplayCache(ReplayCache)`; unset (the default) means no replay check at all. The built-in `InMemoryReplayCache` is a [...]
+- **Argument-hash sealing, always-on.** The original `tools/call`/`prompts/get` `arguments` are now canonicalized (RFC 8785 JSON Canonicalization Scheme) and SHA-256-hashed at PAUSE time, then re-verified against the RESUME request's own `arguments` on every round — no configuration, no opt-out. A faithful client that re-sends identical arguments (the documented, expected case) sees no change in behavior; a resume with different arguments is now hard-rejected with the new `CODE_REQUEST_S [...]
+- **Bounded-JSON safety now covers `arguments` too.** Because the always-on hash above canonicalizes the full `arguments` value, Juneau's existing bounded-JSON safety checks (max depth, max node count) now also apply to tool/prompt `arguments` on every PAUSE and RESUME — including schemaless `tools/call` and `prompts/get`, which previously had no input-shape guard at all — so pathologically large or deep `arguments` are rejected with `-32602` (invalid params) before the handler ever runs.
+- **`McpRequestState` gains two additive fields** (`jti`, `argumentsHash`) alongside its existing `continuation`/`method`/`round`/`expiresAtMs`. Both ride inside the AEAD-sealed plaintext, so neither is forgeable without breaking the existing integrity check; no change to `RequestStateCodec`/`AeadRequestStateCodec` or the sealed-token wire format.
+
+See [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#replay-protection-replaycache-opt-in) for the full topic.
+
+### MCP `2026-07-28` — schema-safety compute budget measured as thread CPU time
+
+`2026-07-28`-only. The bounded, no-fetch `tools/call` input-schema validation guard enforces a compute budget so a pathological schema (for example a catastrophically-backtracking `pattern`) can't hang the request thread — tripping `-32602` ("Tool input schema validation exceeded 100 ms") instead. That budget is now charged against the **validating thread's actual CPU time** rather than wall-clock time: OS scheduler preemption and thread-pool queueing no longer count against it, only rea [...]
+
+### MCP `2026-07-28` — OAuth 2.1 authorization: client acquisition + server resource server
+
+`2026-07-28`-only, and **off by default** — an endpoint that doesn't opt in behaves exactly as before. 10.0.0 lands the OAuth 2.1 / MCP authorization baseline in three slices: client-side token acquisition (F1), a server-side resource-server (F2), and dynamic client registration + authorization hardening (F3). Together they let a Juneau MCP client authenticate to, and a Juneau MCP endpoint protect, a `2026-07-28` MCP resource end to end, covering the MCP authorization SEPs at a baseline [...]
+
+- **New client module `juneau-rest-client-mcp-auth`** (package `org.apache.juneau.rest.client.mcp.auth`, on the Nimbus OAuth 2.0 SDK) — feeds the existing `McpAuthInterceptor` token seam (a `Supplier<String>`):
+ - **`McpTokenProvider`** — a thread-safe headless token manager in three modes: pre-provisioned static (`ofStaticToken`), client-credentials (`clientCredentials()`, RFC 6749 §4.4), and refresh-token (`refreshToken(...)`, RFC 6749 §6 / SEP-2207 with rotation capture via `currentRefreshToken()` and terminal-`invalid_grant` latching). The dynamic modes require `resource(URI)` (RFC 8707), carried on every token request; `interceptor()` wires it straight into a client builder.
+ - **`McpAuthorizationCodeAcquirer`** — interactive authorization-code + PKCE (`S256`) for CLI/native use: opens a `LoopbackRedirectReceiver`, launches the authorization URL (pluggable `browserLauncher`), validates the callback (single-use `state` CSRF, SEP-2468 `iss` — `expectedIssuer(URI)` required unless `skipIssuerValidation(true)`), and exchanges the code. `offlineAccess(true)` requests a refresh token.
+ - **`McpProtectedResourceMetadataClient`** — RFC 9728 PRM discovery with an `expectedResource(URI)` §3.3 identity check and an `https`-by-default scheme requirement (loopback exempt), plus `discoverAuthorizationServer(...)`; `WwwAuthenticateChallenge` parses the `401`/`403` `Bearer` challenge to recover the `resource_metadata` pointer.
+ - **`OidcDiscoveryClient`** — RFC 8414 Authorization Server Metadata with OIDC Discovery fallback (both required by the spec), each performing the issuer-identity check.
+- **New server RS baseline on `juneau-rest-server-mcp-v20260728`** — `McpResourceServerConfig`, folded into `McpOptions` as the `resourceServer(Consumer<McpResourceServerConfig>)` nested config. When enabled it requires a valid RFC 6750 bearer token on the MCP `POST` endpoint (validation mode chosen by the supplied `TokenValidator`: `OAuthIntrospectionValidator` for opaque tokens, `JwtTokenValidator` for JWTs), serves an RFC 9728 PRM document from `.well-known/oauth-protected-resource`, [...]
+- **Origin-root mount constraint.** Because RFC 9728 requires the PRM document at an origin-root well-known location — the exact URL advertised in the `resource_metadata` challenge — RS auth requires the MCP endpoint to be mounted at the origin root (no context path, no non-root servlet path, no `@Mixin(path=...)` re-mount prefix). A non-root or re-mounted mount **fails fast with a `500`** carrying an `origin-root` diagnostic (on both the `POST` gate and the well-known route) rather than [...]
+
+**F3 — dynamic client registration + authorization hardening.** The third slice completes the client story with RFC 7591 / OIDC Dynamic Client Registration, issuer-keyed credential persistence (SEP-2352), and scoped step-up authorization (SEP-2350, both halves), all in `juneau-rest-client-mcp-auth` except the server step-up gate:
+
+- **Dynamic Client Registration (SEP-837 / RFC 7591 / OIDC).** `McpDynamicClientRegistrar` POSTs client metadata to an AS `registration_endpoint` and returns an immutable, secret-redacting `McpClientRegistration`. `McpApplicationType` is a Juneau-owned `NATIVE`/`WEB` enum (native is the SEP-837 SHOULD for CLI/desktop/`localhost` clients) so the public API does not leak the Nimbus `provided` type. Loopback redirects are built with `LoopbackRedirectUris`: `portAgnostic(path)` is the RFC 82 [...]
+- **Issuer-keyed credential binding (SEP-2352).** Persisted credentials **MUST** be keyed by the AS `issuer` and never reused across authorization servers. `McpClientRegistrationStore` is the persistence SPI; `InMemoryMcpClientRegistrationStore` is the thread-safe, secret-redacting default. `McpClientRegistrationManager` orchestrates mechanism selection, issuer-keying, and migration: on-demand (no store), store-hit reuse, or re-registration when discovery indicates a different issuer — s [...]
+- **Scoped step-up authorization (SEP-2350, both halves).** Server side, `McpResourceServerConfig.addOperationScope(operation, scopes…)` declares per-operation required scopes; the RS answers an under-scoped call with `403 insufficient_scope` + a `WWW-Authenticate` `scope=` hint, and the baseline `401` now also carries the `scope` hint when a baseline required-scope set is configured. Both the baseline and per-operation gates are **hierarchy-aware** (a broader granted scope satisfies a n [...]
+
+See the [OAuth 2.1 recipe](/docs/topics/JuneauMcpRecipes#securing-an-mcp-client-and-server-with-oauth-21-v2-only) (end-to-end client + server), the [DCR + step-up recipes](/docs/topics/JuneauMcpRecipes#dynamic-client-registration-and-step-up-v2-only), the [juneau-rest-client-mcp auth reference](/docs/topics/JuneauRestClientMcp#oauth-21-authorization-juneau-rest-client-mcp-auth), and the [juneau-rest-server-mcp resource-server reference](/docs/topics/JuneauRestServerMcp#oauth-21-resource- [...]
+
+### MCP `2026-07-28` — first-party runnable example module (`juneau-examples-mcp`)
+
+A new example module, `juneau-examples-mcp` (under `juneau-examples/`, alongside `juneau-examples-core`), ships the single highest-value adoption artifact for the now-complete MCP framework above: a **runnable, copy-me reference implementation** of both the MCP server and client sides, built against the `2026-07-28` revision. One cohesive in-memory "notes" service (`NoteStore`, a `title -> body` map) is exercised end to end by every headline surface:
+
+- **tool** — `publishNote(title, body)`, which stores a note and pushes a `resources/updated` change to subscribers.
+- **tool + elicitation/MRTR** — `deleteNote(title)`, which pauses with `input_required` to confirm before resuming to delete.
+- **prompt + completion** — `summarize(title)`, whose `title` argument offers `completion/complete` suggestions from existing note titles.
+- **resource** — the fixed `note:///index` resource, listing all note titles.
+- **resource template + completion** — `note:///{title}`, reading one note by title, with the same title-completion behavior on its `{title}` variable.
+- **subscription (SEP-2575)** — `subscriptions/listen` held-open SSE, delivering the `resources/updated` change `publishNote` fires.
+
+`ExampleMcpServer` (a v2 `McpRestServlet`) registers every surface above; `ExampleServer` is an embedded-Jetty launcher (`Microservice` + `JettyConfiguration`, `main(...)` plus a `start(port)` handle); `ExampleClient` is a guided, numbered `McpClient` walkthrough printing each step's request/response top-to-bottom like a tutorial; and a Spring Boot variant (`SpringExampleMcpServer`/`SpringExampleApplication`) uses `SpringMcpRestServlet` to resolve a Spring-managed `GreetingService` throu [...]
+
+See the [MCP Recipes](/docs/topics/JuneauMcpRecipes#full-example-juneau-examples-mcp), [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp), and [juneau-rest-client-mcp](/docs/topics/JuneauRestClientMcp) topics for pointers to the module, and the module's own `README.md` for run instructions.
+
+### MCP `2026-07-28` — OAuth 2.1-secured variant of `juneau-examples-mcp` (`secured/` package)
+
+The example module above gets a second, fully **offline-runnable** variant demonstrating the OAuth 2.1 authorization baseline end to end: `org.apache.juneau.examples.mcp.secured` wraps the identical notes service in a live resource-server gate, with a demo authorization server standing in for a real IdP so the whole walkthrough needs no external network access.
+
+- **`SecuredExampleMcpServer extends ExampleMcpServer`** — the entire security story is one `createMcpOptions()` override layering `McpResourceServerConfig` on top of the inherited notes surface: RS auth enabled, `setResource(...)` (the RFC 9728 resource identity / RFC 8707 default audience), a `JwtTokenValidator` checking `iss`/`aud`/`exp`/`nbf` against the offline AS, `addAuthorizationServer(...)`, a baseline `addRequiredScope("mcp.read")`, and SEP-2350 per-operation step-up (`addOpera [...]
+- **`OfflineAuthorizationServer`** — a clearly-marked DEMO-ONLY stand-in AS: a fresh self-generated RSA signing key per run, an offline `JWKSource` handed straight to the validator (no JWKS HTTP fetch), a real RFC 6749 §4.4 client-credentials `/token` endpoint gated by a fixed scope allowlist (`mcp.read`/`mcp.write`, else `invalid_scope`), and an RFC 8414 `/.well-known/oauth-authorization-server` metadata document (issuer = its own loopback base URL, plus `token_endpoint`) enabling genui [...]
+- **`SecuredExampleServer`/`SecuredExampleClient`** — a launcher plus a real, discovery-driven end-to-end client walkthrough: an unauthenticated call rejected with `401` + `WWW-Authenticate: Bearer`, RFC 9728 PRM discovery (`authorization_servers`, `scopes_supported`), RFC 8414 AS-metadata discovery of the `token_endpoint`, a client-credentials token acquisition via `juneau-rest-client-mcp-auth`'s `McpTokenProvider`, a successful `mcp.read` read, the same token rejected with `403 insuffi [...]
+
+See the [OAuth 2.1 recipe](/docs/topics/JuneauMcpRecipes#securing-an-mcp-client-and-server-with-oauth-21-v2-only) for the full walkthrough, and the module's own `README.md` for run instructions.
+
+### MCP `2026-07-28` — client/server adoption convenience methods
+
+Four small, additive convenience methods land alongside the example module above, each replacing a small piece of repetitive code the example (and real-world adopters) would otherwise repeat at every call site. None deprecates or replaces the lower-level API it sits on top of.
+
+- **`CallToolResult.firstText()`** (`juneau-bean-mcp-v20260728`) — returns the text of the first `TextContent` block found while scanning the result's `content` list in order, or `null` if the list is empty/unset or contains no `TextContent` block. Kills the `((TextContent) result.getContent().get(i)).getText()` cast-and-scan a caller who only wants the tool's text result would otherwise repeat everywhere.
+- **`McpClient.callToolText(String name, Map<String,Object> arguments)`** (`juneau-rest-client-mcp-v20260728`) — convenience for `callTool(...)` followed by `CallToolResult.firstText()`; null-safe if `callTool(...)` itself returns a `null` result.
+- **`ServerDiscoverResult.getServerInfo()`** (`juneau-bean-mcp-v20260728`) — null-safe shortcut for `getMeta() == null ? null : getMeta().getServerInfo()`, so a caller doesn't have to null-check the intermediate `_meta` envelope just to reach the server's `Implementation` identity. `@BeanIgnore`d so it does not add a second, top-level `serverInfo` member to the `server/discover` wire format.
+- **`McpMrtrResumeContext.continuationAsString()`** (`juneau-rest-server-mcp-v20260728`) — shorthand for `continuationAs(String.class)`, for the common case of a plain `String` continuation.
+- **`ElicitationResponses.getBoolean(ctx, id, field)` / `getString(ctx, id, field)`** (`juneau-rest-server-mcp-v20260728`) — decline/omission-safe typed reads of a single field from an elicitation answer: a missing id, a missing field, or a non-`ACCEPT` action (declined/cancelled) all resolve to a safe default (`false` for `getBoolean`, `null` for `getString`) instead of requiring the caller to null-check `get(...)` and its content map by hand.
+
+See the [confirm-delete recipe](/docs/topics/JuneauMcpRecipes#elicitation--multi-round-trip-requests-v2-only) and the [client recipe](/docs/topics/JuneauMcpRecipes#client-call-read-and-handle-errors) in MCP Recipes, the [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#elicitation-mcp-2026-07-28-sep-2322) elicitation reference, and the [juneau-rest-client-mcp](/docs/topics/JuneauRestClientMcp) client reference for these methods in context.
+
+### MCP `2025-06-18` — resource-not-found error code corrected to `-32002`
+
+`2025-06-18`-only bug fix. A `resources/read` for a URI the server does not serve now returns the JSON-RPC error code **`-32002`** ("Resource not found") — the `2025-06-18` spec's dedicated missing-resource code — instead of the previous generic **`-32601`** ("method not found"). This was one of the four "not found" kinds that the re-layered `2025-06-18` `McpRevision` initially collapsed onto `-32601` (see the `juneau-rest-server-mcp-v20250618` module notes above); resource-not-found is [...]
+
+See [juneau-rest-server-mcp](/docs/topics/JuneauRestServerMcp#resource-templates-reads-and-completions) for the resolution-order and error-code details.
+
+### OIDC Relying Party — injectable `LoginStateStore` SPI for clustered login
+
+`juneau-rest-server-auth-oidc-rp` gains a pluggable **`LoginStateStore`** SPI so an OIDC login flow can survive a redirect/callback landing on different nodes — i.e. clustered login **without** sticky sessions. Previously the transient `state` → `(nonce, code_verifier, redirectTarget)` map was per-JVM only.
+
+- **New `LoginStateStore` interface** — `store(state, PendingLogin)` + atomic single-use `consume(state)`, with a framework-owned `MAX_TTL` ceiling (30 min). `PendingLogin` (moved onto `LoginStateStore`) now carries a framework-minted `expiresAt` and a `toString()` that redacts the secret-bearing `nonce` / `code_verifier`.
+- **`Builder.loginStateStore(...)`** — inject a shared (Redis / JDBC) store for clustered deployments; the `stateNonceTtl` cap `(0, MAX_TTL]` is enforced on the custom-store path too, not just the default implementation.
+- **Consume-time fail-closed re-checks** — after `consume(state)` and **before** the token exchange, the framework re-validates `redirectTarget` via `safeRelativePath` (a writable shared store cannot smuggle in an off-site redirect) and re-checks `expiresAt` against its own clock (a `null`-timestamp or over-TTL record is rejected as expired rather than trusted). The store is treated as an untrusted blob store, not an oracle.
+- **Default implementation renamed `EphemeralStore` → `InMemoryLoginStateStore`.** As this module is new in 10.0.0, the rename, the `PendingLogin` move onto `LoginStateStore`, and the new `store(String, PendingLogin)` signature are source-level deltas within the unreleased line — no migration for released users.
+
+See [OIDC Relying Party Login](/docs/topics/OidcRelyingParty#login-state-store-clustering) for the clustering guidance and store contract.
+
+## juneau-marshall
+
+### Duration/Period wire format controls
+- Added new enum strategies:
+ - `org.apache.juneau.DurationFormat` (`ISO_8601`, `ISO_8601_WITH_DAYS`, `NANOS`, `MILLIS`, `SECONDS`, `HOCON`)
+ - `org.apache.juneau.PeriodFormat` (`ISO_8601`, `DAYS`)
+- Added global marshalling-context settings:
+ - `MarshallingContext.Builder.durationFormat(...)` / `periodFormat(...)`
+- Added annotation-level overrides:
+ - `@Marshalled(durationFormat=..., periodFormat=...)`
+ - `@MarshalledProp(durationFormat=..., periodFormat=...)`
+ - `@MarshalledConfig(durationFormat=..., periodFormat=...)`
+- Updated serializer sessions across JSON/HJSON/HOCON/UON/XML/HTML/Markdown/TOML/INI/CSV/PROTO plus BSON/CBOR/MessagePack so configured duration/period formats are honored consistently. Binary serializers emit native numeric wire types for numeric duration formats.
+- Parsers now sniff duration/period wire shapes more broadly and support mixed input styles, with parser-side format settings acting as hints for ambiguous numeric values.
+
+**Migration note (default behavior change):**
+
+- Default `Duration` serialization now uses `ISO_8601_WITH_DAYS` (for example `Duration.ofHours(48)` now writes as `'P2D'` instead of `'PT48H'`).
+- To restore prior wire output style, configure serializers explicitly:
+
+ ```java
+ JsonSerializer s = JsonSerializer.create()
+ .marshallingContext(MarshallingContext.create().durationFormat(org.apache.juneau.DurationFormat.ISO_8601))
+ .build();
+ ```
+
+### Date/time + Locale wire format controls
+This is a **major-release breaking change**. The legacy `Temporal*Swap` inner-class family and named root-level temporal/locale/timezone swaps are removed without deprecation shims; configure formats via `MarshallingContext` or the new annotations instead.
+
+- Added new enum strategies under `org.apache.juneau`:
+ - `CalendarFormat` (default `ISO_OFFSET_DATE_TIME`; ISO variants, `RFC_1123_DATE_TIME`, `MILLIS`, plus an opt-in `XML_FORMAT` for `Calendar`/`GregorianCalendar`).
+ - `DateFormat` (default `ISO_LOCAL_DATE_TIME`; ISO variants, `RFC_1123_DATE_TIME`, `MILLIS`).
+ - `TemporalFormat` (default `DEFAULT` = per-subtype default; ISO variants, `RFC_1123_DATE_TIME`, `ISO_YEAR`, `ISO_YEAR_MONTH`, `MILLIS`).
+ - `TimeZoneFormat` (default `ID`; shared by `TimeZone` and `ZoneId`; `OFFSET`, `NAME_LONG`, `NAME_SHORT`).
+ - `LocaleFormat` (default `BCP_47`; also `UNDERSCORE`).
+- Added global marshalling-context settings:
+ - `MarshallingContext.Builder.calendarFormat(...)` / `dateFormat(...)` / `temporalFormat(...)` / `timeZoneFormat(...)` / `localeFormat(...)`.
+ - Mirrored on `MarshallingContextable.Builder` so every serializer/parser builder inherits the fluent setters with concrete return types.
+- Added annotation-level overrides on the three Marshalled annotations:
+ - `@Marshalled(calendarFormat=..., dateFormat=..., temporalFormat=..., timeZoneFormat=..., localeFormat=...)`
+ - `@MarshalledProp(calendarFormat=..., dateFormat=..., temporalFormat=..., timeZoneFormat=..., localeFormat=...)`
+ - `@MarshalledConfig(calendarFormat=..., dateFormat=..., temporalFormat=..., timeZoneFormat=..., localeFormat=...)`
+- Implemented uniform precedence resolution in `MarshalledPropertyPostProcessor`: `@MarshalledProp` > `@Marshalled` > `MarshallingContext` setting > enum default. Resolution installs `StringSwap` instances built directly from the enum's `format`/`parse` methods on the matching property type (`Calendar`/`GregorianCalendar`, `Date`, `TemporalAccessor` subtypes, `TimeZone`/`ZoneId`, `Locale`). `XMLGregorianCalendar` is hard-wired to XML lexical format regardless of any configured `CalendarF [...]
+- Updated serializer and parser sessions across JSON/HJSON/HOCON/UON/XML/HTML/Markdown/TOML/INI/CSV/PROTO plus BSON/CBOR/MessagePack and Parquet so configured calendar/date/temporal formats are honored at the root level. `Iso8601Utils` gained format-aware overloads that route through the session's resolved format.
+- Downgraded `TimeZoneSwap`, `ZoneIdSwap`, and `LocaleSwap` to thin delegators that read the resolved `TimeZoneFormat` / `LocaleFormat` from the active session, so root-level (de)serialization of `TimeZone`/`ZoneId`/`Locale` follows the same context configuration as bean properties.
+
+**Removed (hard break, no `@Deprecated`):**
+
+- `org.apache.juneau.marshall.swaps.TemporalCalendarSwap` and all 17 inner-class variants (`IsoOffsetDateTime`, `IsoInstant`, `IsoLocalDateTime`, etc.).
+- `org.apache.juneau.marshall.swaps.TemporalDateSwap` and all 17 inner-class variants.
+- `org.apache.juneau.marshall.swaps.TemporalSwap` and all 18 inner-class variants.
+- `org.apache.juneau.marshall.swaps.XMLGregorianCalendarSwap` (its always-XML behavior is now built into the post-processor for `XMLGregorianCalendar` properties; the `XML_FORMAT` `CalendarFormat` constant covers the opt-in case for `Calendar`).
+
+**Notable behavior — `MILLIS` for `TemporalFormat`:**
+
+- `Instant`, `OffsetDateTime`, `ZonedDateTime` use `toInstant().toEpochMilli()`.
+- `LocalDateTime` uses `ldt.toInstant(ZoneOffset.UTC).toEpochMilli()`.
+- `LocalDate` uses `ld.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli()`.
+- `YearMonth` and `Year` use the first day at midnight UTC.
+- `LocalTime` and `MonthDay` fall back to the per-subtype `DEFAULT` ISO string form.
+- This asymmetry is documented in the `MILLIS` constant's Javadoc.
+
+**Migration:**
+
+- Replace any `@Swap(TemporalCalendarSwap.IsoXxx.class)` with `@MarshalledProp(calendarFormat=CalendarFormat.ISO_XXX)`.
+- Replace `@Swap(TemporalDateSwap.IsoXxx.class)` with `@MarshalledProp(dateFormat=DateFormat.ISO_XXX)`.
+- Replace `@Swap(TemporalSwap.IsoXxx.class)` with `@MarshalledProp(temporalFormat=TemporalFormat.ISO_XXX)`.
+- For context-level configuration, replace `swaps(TemporalXxxSwap.IsoYyy.class)` on serializer/parser builders with `xxxFormat(XxxFormat.ISO_YYY)`.
+
+### Binary + Enum wire format controls
+This is a **major-release breaking change**. The legacy global `ByteArraySwap` family, the
+`OutputStreamSerializer.Builder` / `InputStreamParser.Builder` `binaryFormat(...)` setters, and the
+`useEnumNames(boolean)` setter are removed without deprecation shims; configure these via
+`MarshallingContext` or the new annotations instead.
+
+- Extended `org.apache.juneau.BinaryFormat` with two new constants:
+ - `NOT_SET` — sentinel meaning "no value configured" (falls through to the next-higher precedence level; mirrors the convention used by every other `<Type>Format` enum).
+ - `BASE64_URL` — RFC 4648 §5 URL-safe Base64 encoding (`-` / `_` instead of `+` / `/`, no padding on the wire but accepted on input).
+- `BinaryFormat` now exposes `format(byte[])` and `parse(String)` methods so a single enum drives both serialize and parse paths. The `parse(String)` method is intentionally format-agnostic — it sniffs the wire shape (spaced hex, URL-safe Base64, hex, standard Base64) and decodes accordingly so parsers accept any of the formats produced by `format(byte[])` regardless of the configured constant.
+- Added new `org.apache.juneau.EnumFormat` with the constants `NOT_SET`, `TO_STRING` (default; calls `Enum.toString()`), `NAME` (calls `Enum.name()`), `LOWER_HYPHEN` (e.g. `MY_VALUE` → `my-value`), `UPPER_HYPHEN`, `LOWER_UNDERSCORE`, `LOWER`, `UPPER`, and `ORDINAL` (numeric on the wire). `format(Enum)` and `parse(String, Class)` cover both directions; the parser is lenient (case-insensitive, hyphen/underscore-insensitive, accepts ordinal as integer) so any wire shape produced by any cons [...]
+- Added global marshalling-context settings on `MarshallingContext.Builder`:
+ - `binaryFormat(BinaryFormat)` (default `NOT_SET`).
+ - `enumFormat(EnumFormat)` (default `TO_STRING`).
+ - Mirrored on `MarshallingContextable.Builder` so every serializer / parser builder inherits the fluent setters with concrete return types.
+- Added annotation-level overrides on the three Marshalled annotations:
+ - `@Marshalled(binaryFormat=..., enumFormat=...)`
+ - `@MarshalledProp(binaryFormat=..., enumFormat=...)`
+ - `@MarshalledConfig(binaryFormat=..., enumFormat=...)`
+- Implemented uniform precedence resolution in `MarshalledPropertyPostProcessor`: `@MarshalledProp` > `@Marshalled` > `MarshallingContext` setting > enum default. Resolution installs the appropriate session-aware `ObjectSwap` on each `byte[]` / enum bean property. For `EnumFormat.ORDINAL` the swap produces a `Number` so binary serializers emit a native int.
+- Downgraded the global `byte[]` default swap (`org.apache.juneau.marshall.swaps.BinarySwap`) to a thin session-aware delegate that reads the resolved `BinaryFormat` from the active `MarshallingContext` at swap time. Skips binary serializers (BSON / CBOR / MsgPack / Proto / Parquet emit native bytes), CSV (defers to `csv.CsvByteArrayCellFormat`), and OpenAPI (uses its schema-directed BYTE / BINARY / BINARY_SPACED encoding). When the resolved format is `NOT_SET`, the swap is a no-op so `b [...]
+- `OutputStreamSerializerSession#serializeToString(Object)` now always emits `BinaryFormat.HEX` regardless of the configured `binaryFormat`, providing a stable copy-pasteable hex dump for debug / display irrespective of the surrounding context's wire-format setting.
+
+**Removed (hard break, no `@Deprecated`):**
+
+- `org.apache.juneau.marshall.swaps.ByteArraySwap` and the `Base64`, `Hex`, `SpacedHex` inner-class variants. Replace `@Swap(ByteArraySwap.Base64.class)` etc. with `@MarshalledProp(binaryFormat=BASE64)` etc.
+- `OutputStreamSerializer.Builder.binaryFormat(BinaryFormat)` and `InputStreamParser.Builder.binaryFormat(BinaryFormat)`. The setting is now on `MarshallingContext.Builder` (and inherited by every serializer / parser builder via `MarshallingContextable.Builder`) so the same wire-format setting drives both binary and textual sessions.
+- `BeanContext.Builder.useEnumNames(boolean)` and the `useEnumNames` field on `MarshallingContext`. Replace `useEnumNames()` with `enumFormat(EnumFormat.NAME)`; replace `@Bean(useEnumNames=true)` with `@Marshalled(enumFormat=EnumFormat.NAME)`.
+
+**Renamed:**
+
+- `org.apache.juneau.marshall.csv.ByteArrayFormat` → `org.apache.juneau.marshall.csv.CsvByteArrayCellFormat`. The new name disambiguates the CSV-only cell-encoding role from the new top-level `BinaryFormat`.
+
+**Migration:**
+
+- Replace `@Swap(ByteArraySwap.Base64.class)` with `@MarshalledProp(binaryFormat=BinaryFormat.BASE64)`.
+- Replace `@Swap(ByteArraySwap.Hex.class)` with `@MarshalledProp(binaryFormat=BinaryFormat.HEX)`.
+- Replace `@Swap(ByteArraySwap.SpacedHex.class)` with `@MarshalledProp(binaryFormat=BinaryFormat.SPACED_HEX)`.
+- Replace `OutputStreamSerializer.Builder#binaryFormat(...)` / `InputStreamParser.Builder#binaryFormat(...)` calls with the inherited `binaryFormat(...)` from `MarshallingContextable.Builder` (no source change needed for builder chains; the inherited setter has the same signature).
+- Replace `useEnumNames()` / `useEnumNames(true)` with `enumFormat(EnumFormat.NAME)`.
+
+### UUID + BigInteger/BigDecimal wire format controls
+- Added new enum strategies under `org.apache.juneau`:
+ - `UuidFormat` (default `STANDARD`; also `NO_DASHES` for the 32-hex-character compact form, and `URN` for the RFC 4122 `urn:uuid:` namespace).
+ - `BigNumberFormat` (default `NUMBER`; also `STRING` for always-quoted output, and `AUTO` which emits a bare numeric token when the value is JS-safe (`|v| ≤ 2^53−1`) and a quoted string otherwise to avoid silent precision loss in JavaScript clients).
+- Both enums expose static `format(...)` and `parse(...)` helpers; parsers are intentionally lenient and accept every textual shape produced by any constant regardless of the configured value (the setting is informational only on parse).
+- Binary serializers (BSON / CBOR / MsgPack) continue to emit native UUID bytes and native big-number types regardless of the textual format setting; the new enums apply to textual wires only.
+- Added global marshalling-context settings on `MarshallingContext.Builder`:
+ - `uuidFormat(UuidFormat)` (default `STANDARD`).
+ - `bigNumberFormat(BigNumberFormat)` (default `NUMBER`).
+ - Mirrored on `MarshallingContextable.Builder` so every serializer / parser builder inherits the fluent setters with concrete return types.
+- Added annotation-level overrides on the three Marshalled annotations:
+ - `@Marshalled(uuidFormat=..., bigNumberFormat=...)`
+ - `@MarshalledProp(uuidFormat=..., bigNumberFormat=...)`
+ - `@MarshalledConfig(uuidFormat=..., bigNumberFormat=...)`
+- Extended uniform precedence resolution in `MarshalledPropertyPostProcessor`: `@MarshalledProp` > `@Marshalled` > `MarshallingContext` setting > enum default. Resolution installs an `ObjectSwap<T, Object>` on `UUID` / `BigInteger` / `BigDecimal` properties whose `swap(...)` returns either a `Number` (bare numeric token) or a `String` (quoted), so binary serializers continue to see native types and text serializers branch on the formatted value. For `BigNumberFormat.AUTO` the swap dynami [...]
+
+This completes the umbrella wire-format-control plan (Phases 1, 2, and 3 are all delivered in 10.0.0).
+
+### Boolean / Float / Currency / Class wire format controls
+Round-2 follow-up extending the `MarshallingContext` format-control surface to four additional types. Same template (precedence chain, lenient parsers, binary-native fall-through) as the UUID / BigNumber phases.
+
+- Added four new enums under `org.apache.juneau`:
+ - `BooleanFormat` (default `TRUE_FALSE`; also `ZERO_ONE` for numeric `0`/`1` tokens, `YES_NO` / `Y_N` / `ON_OFF` for textual forms). `ZERO_ONE` emits a bare numeric token (not a quoted string) on JSON.
+ - `FloatFormat` (`NaN_AS_NULL` (default per spec, but install-skipped on primitive `float`/`double` — see note below), `NaN_AS_STRING` for quoted `"NaN"` / `"Infinity"` / `"-Infinity"`, `NaN_AS_NUMBER` for the bare token (non-strict JSON), `NaN_AS_ERROR` for fail-fast).
+ - `CurrencyFormat` (default `ISO_CODE` — locale-independent, round-trip-safe; also `SYMBOL` and `DISPLAY_NAME` which are locale-sensitive and best-effort on parse).
+ - `ClassFormat` (default `FQCN` — `Class.getCanonicalName()` with `getName()` fallback for local/anonymous classes; also `BINARY_NAME` for `Class.getName()` with `$` separators, and `SIMPLE_NAME` for `Class.getSimpleName()` which is serialize-only — parsing `SIMPLE_NAME` throws `UnsupportedOperationException`).
+- Each enum exposes static `format(...)` / `parse(...)` helpers; parsers are intentionally lenient and accept every textual shape produced by any constant regardless of the configured value (the setting is informational only on parse).
+- Added global marshalling-context settings on `MarshallingContext.Builder`:
+ - `booleanFormat(BooleanFormat)` (default `TRUE_FALSE`).
+ - `floatFormat(FloatFormat)` (default `NaN_AS_NULL`).
+ - `currencyFormat(CurrencyFormat)` (default `ISO_CODE`).
+ - `classFormat(ClassFormat)` (default `FQCN`).
+ - Mirrored on `MarshallingContextable.Builder` and overridden on `Serializer.Builder` / `Parser.Builder` so every serializer / parser builder inherits the fluent setters with concrete return types.
+- Added annotation-level overrides on the three Marshalled annotations:
+ - `@Marshalled(booleanFormat=..., floatFormat=..., currencyFormat=..., classFormat=...)`
+ - `@MarshalledProp(booleanFormat=..., floatFormat=..., currencyFormat=..., classFormat=...)`
+ - `@MarshalledConfig(booleanFormat=..., floatFormat=..., currencyFormat=..., classFormat=...)`
+- Extended uniform precedence resolution in `MarshalledPropertyPostProcessor`: `@MarshalledProp` > `@Marshalled` > `MarshallingContext` setting > enum default. Resolution installs an `ObjectSwap` on `Boolean` / `Float` / `Double` / `Currency` properties as appropriate. Binary serializers continue to see native primitives via the swap's `swap(...)` short-circuit on `OutputStreamSerializerSession`.
+
+**Breaking changes:**
+
+- **`ClassSwap` deleted outright.** The legacy `org.apache.juneau.marshall.swaps.ClassSwap` is removed in 9.6. Its responsibilities split into two replacements:
+ - For bean properties, an `ObjectSwap<Class, String>` is installed by `MarshalledPropertyPostProcessor` when `@MarshalledProp(classFormat=...)` or `@Marshalled(classFormat=...)` is configured.
+ - For standalone `Class<?>` values (root-level, map keys, list elements), a new `org.apache.juneau.marshall.swaps.ClassFormatSwap` is registered in `DefaultSwaps` and reads `MarshallingContext.classFormat` at swap-time.
+ - Default behavior (`FQCN` ≈ `Class.getCanonicalName()`) is functionally compatible with the old `ClassSwap.getName()` for top-level classes. **Differences:** `FQCN` returns `int[]` for `int[].class` (vs. `[I` from `Class.getName()`); use `BINARY_NAME` if you need the JVM binary form. Callers that explicitly wanted `getSimpleName()` must move to `@MarshalledProp(classFormat=ClassFormat.SIMPLE_NAME)`.
+ - Replace `@Swap(impl=ClassSwap.class)` with `@MarshalledProp(classFormat=ClassFormat.FQCN)` (or simply remove it — the new default matches old behavior for top-level classes).
+
+**Notable caveats:**
+
+- **`FloatFormat.NaN_AS_NUMBER`** emits the bare `NaN` / `Infinity` / `-Infinity` token, which is **not** strict JSON per RFC 8259. Use only with lenient consumers (Juneau's own JSON parser, V8, several Python libraries). Strict consumers reject these tokens.
+- **`FloatFormat` on primitive `float` / `double` fields:** Juneau's bean machinery applies a null-to-primitive-default convention (e.g. `m.put("d1", null)` → `d1 = 0.0`) that conflicts with swap-driven `NaN_AS_NULL` semantics. To preserve this contract, the post-processor installs the float swap only on **boxed** `Float` / `Double` properties for context-level format settings. Explicit `@MarshalledProp(floatFormat=...)` or `@Marshalled(floatFormat=...)` overrides install for the explici [...]
+- **`CurrencyFormat.SYMBOL` / `DISPLAY_NAME` round-trip is best-effort and locale-dependent on parse.** ISO_CODE is the only constant with strict round-trip guarantees — `$` can resolve to USD / CAD / AUD / etc.; the parser prefers the locale's default currency on ambiguity and throws `IllegalArgumentException` when no match is found.
+- **`ClassFormat.SIMPLE_NAME` is serialize-only** — `parse(...)` throws `UnsupportedOperationException`. Use `FQCN` (default) or `BINARY_NAME` for round-trippable wires.
+
+### Format-control round-trip hardening
+Closes out the format-control extension work with a cross-serializer/parser round-trip test matrix over all 16 `*Format` enums and 8 additional `TemporalFormat` subtypes. The matrix surfaced and closed 18 production bugs across the format-control dispatch layer; new public helpers and capabilities were added to support the fixes.
+
+**Highlights:**
+
+- **`TemporalFormat` carryover** — all 10 supported `TemporalAccessor` subtypes (`Instant`, `LocalDateTime`, `LocalDate`, `OffsetDateTime`, `ZonedDateTime`, `Year`, `YearMonth`, `LocalTime`, `OffsetTime`, `MonthDay`) now round-trip across every `TemporalFormat` value. Date-bearing-only / time-bearing-only formats are handled lossily via the existing `DefaultingTemporalAccessor` mechanism (year defaults to 1970, time defaults to 00:00:00, etc.).
+- **`MonthDay` support** — `java.time.MonthDay` is a `TemporalAccessor` but **not** a `Temporal`, so it was previously falling through to standard bean serialization (and failing on parse for lack of a public no-arg constructor). Now fully round-trippable via a new sibling `TemporalAccessor`-keyed swap factory; configured `TemporalFormat` values are silently ignored for `MonthDay` properties (the only stable wire shape is the native `--MM-DD` `toString()` / `parse()` form, since no `Date [...]
+- **`hasNativeBytes()` capability check** — new public methods on `OutputStreamSerializerSession` and `InputStreamParserSession` (default `true`) consulted by `BinarySwap.match` and `MarshalledPropertyPostProcessor.binarySwap`. Overridden to `false` on `ParquetSerializerSession` / `ParquetParserSession` / `RdfStreamSerializerSession` / `RdfStreamParserSession`. **Wire-format change**: at any non-`NOT_SET` `BinaryFormat`, Parquet and the two binary RDF serializers (RDF/Thrift, RDF/Proto) [...]
+- **18 production bugs closed** across the format-control dispatch layer. Aggregated by theme:
+ - **Format-hint correctness across binary serializers** — MsgPack `INT8` / `INT16` / NEGFIXINT sign-extension on parse (negative `Period` / `BigInteger` values no longer come back as unsigned magnitudes); Proto text-format `Float` / `Double` decimal-literal sign loss and `0`-prefix tokenization for `0.x` / `0eN` / `0f` shapes; Proto `nan` / `inf` identifier-vs-token disambiguation.
+ - **JSON-family precision tier (Bug #5)** — `StringUtils.parseNumber` no longer auto-classifies bare decimals as `Float` when targeting `Number` / `Object` / `Double`. JSON / JSON5 / JSONL / XML / HTML / UON / UrlEncoding / JCS parsers now return `Double` for `parse("3.14", Object.class)` and preserve full double precision in `Double` bean properties. *Caller-visible if you were instance-checking the parsed `Number` for `Float`* — see migration guide.
+ - **Bean-property `Map<Enum, V>` key coercion (Bug #7b)** — Hjson / Hocon / Proto / Bson parsers now thread the bean property's `ClassMeta<K>` for the map key into the key-coercion step, so typed `Map<MyEnum, V>` properties round-trip cleanly at every `EnumFormat` value. Proto-specific ordinal-keyed maps (`EnumFormat.ORDINAL`) now emit bare integer field tags (`0: "first"`) instead of quoted-string keys, which previously triggered Proto's adjacent-string-literal concatenation on parse.
+ - **Parquet schema fidelity (Bugs #7a, #11)** — Parquet now correctly round-trips `UUID` (via `LOGICAL_TYPE_UUID`), `Class<?>` (via the default-swap dispatch), `Enum` at `EnumFormat.ORDINAL` (via `INT32` column), and `byte[]` (via the `TYPE_BYTE_ARRAY` schema element without `convertedType`).
+ - **Parser dispatch for collection-element / top-level `byte[]` (Bug #12)** — Toml / Proto / Hjson / Hocon parsers now consult `BinarySwap.unswap` for `byte[]` collection elements and top-level values at non-`NOT_SET` formats, so wire forms like `"0001ff807f102030"` (HEX) decode to the correct 8-byte array instead of a 16-byte ASCII char array.
+ - **`BinaryFormat.BASE64_URL` hint (Bug #9)** — `BinaryFormat.parse(String)` now honors the `BASE64_URL` constant directly (routing to `Base64.getUrlDecoder()` which accepts missing padding) before falling through to the format-agnostic wire-shape sniff. Non-3-aligned URL-safe payloads without `-` / `_` chars no longer fail with "Invalid BASE64 string length".
+ - **Hocon parser-fragility (Bugs #10, #15)** — `HoconWriter.QUOTE_VALUE_CHARS` extended to cover `=` (BASE64 padding), `+` (`+NNNN` timezone offsets), and `$` (nested-class `BINARY_NAME`); `HoconTokenizer` recursion guard now throws a clean `IOException` instead of `StackOverflowError`; `HoconParserSession.parseArray` / `parseObject` array-flatten bug fixed (nested arrays no longer collapse via spurious array-concatenation).
+ - **`HoconTokenizer` `=+` lookahead bug** — `HoconTokenizer.readTokenImmediate` incorrectly guarded the `EQUALS` emission with `peekChar() != '+'`, causing any `=+` character sequence in the input to throw `IOException("Unexpected character '='")` instead of emitting `EQUALS`. Since the `+=` (PLUS_EQUALS) operator is already handled by an independent branch that reads `+` first, the lookahead on the `=` branch was both unnecessary and wrong. The guard has been removed; `=` is now uncon [...]
+ - **`ClassFormat.FQCN` nested-types / arrays parser-fragility (Bug #16)** — `ClassFormat.parse` now resolves nested types (`java.util.Map.Entry` → `java.util.Map$Entry`), array suffixes (`int[]`, `java.lang.String[][]`), and leaf primitives (`Class.forName("int")` previously rejected; now resolves through a primitive-name table).
+ - **`Currency` default-swap registration (Bug #6)** — `Currency` added to `DefaultSwaps` (sibling of the existing `Locale` / `TimeZone` / `ZoneId` entries) so `Currency` bean properties round-trip at the default `CurrencyFormat.ISO_CODE` / `NOT_SET` levels.
+ - **UrlEncoding-expanded empty `byte[]` (Bug #13)** — empty `byte[]` properties now round-trip as `byte[0]` instead of `null` through the expanded-params UrlEncoding flavor.
+ - **`MonthDay` round-trip support (Bug #18)** and **`OffsetTime × MILLIS` swap asymmetry (Bug #17)** — see "API changes" below.
+
+**API changes:**
+
+- **`TemporalFormat.format(...)` and `parse(...)` signatures widened** from `Temporal` to `TemporalAccessor`:
+ - `format(Temporal, ZoneId)` → `format(TemporalAccessor, ZoneId)`
+ - `parse(String, Class<? extends Temporal>, ZoneId)` → `parse(String, Class<? extends TemporalAccessor>, ZoneId)`
+
+ Source-compatible for callers passing `Temporal` instances (every existing `Temporal` is a `TemporalAccessor`). Required so `MonthDay` (which is a `TemporalAccessor` but not a `Temporal`) reaches the dispatch path. Flag in migration guide.
+- **New `TemporalFormat.isMillisNumeric(Class<? extends TemporalAccessor>)`** static helper — returns `true` iff `format(value, MILLIS)` emits a `Long`-parseable numeric string for the subtype. Returns `false` for `LocalTime`, `OffsetTime`, and `MonthDay` (the three subtypes whose `MILLIS` branch falls back to the per-subtype `DEFAULT` ISO string form). Single source of truth for the `MILLIS` wire-type decision; consumed by both `temporalSwap` and the new `temporalAccessorSwap` factory.
+- **New `MarshallingContext` capability methods** on the binary session base classes (default `true`; see "Highlights" above for the wire-format implications):
+ - `OutputStreamSerializerSession.hasNativeBytes()`
+ - `InputStreamParserSession.hasNativeBytes()`
+- **New `MarshalledPropertyPostProcessor` internals** — sibling `temporalAccessorSwap` swap factory keyed at `TemporalAccessor.class` (vs `temporalSwap`'s `Temporal.class`), and a new `isTemporalAccessorType(Class<?>)` predicate (negative-space complement: `TemporalAccessor.class.isAssignableFrom(c) && ! Temporal.class.isAssignableFrom(c)`). Written as the negative-space complement so a future JDK addition of another non-`Temporal` `TemporalAccessor` is picked up automatically. Wired sym [...]
+- **`OffsetTime × TemporalFormat.MILLIS`** now falls back to `ISO_OFFSET_TIME` (sibling of the existing `LocalTime` and `MonthDay` carve-outs) — `OffsetTime` has no canonical epoch-millis interpretation. The carve-out is reflected in the updated `MILLIS` constant Javadoc and consumed via `isMillisNumeric`.
+
+**Performance:**
+
+- `IniParserSession` and `MarkdownParserSession` now memoize their internal `Json5Parser` instance per session instead of constructing a fresh `Json5Parser.DEFAULT` per parse call. Behavior-preserving; eliminates repeated builder-and-instance construction in tight-loop parsing of INI / Markdown content.
+
+**Build-config change (developer-only):**
+
+- New `.mvn/maven.config` containing `--also-make`. Affects developers running `mvn -pl <module> …` from the workspace root: upstream modules are now automatically added to the active reactor instead of being resolved from `~/.m2/repository`. Inert without `-pl`, so root-level builds (`mvn clean install`, `./scripts/test.py`, `./scripts/push.py`) and CI are unaffected. Fixes a long-standing developer-loop footgun where in-tree edits to upstream modules wouldn't be picked up by `mvn -pl j [...]
+
+### Typed `JsonSchema` bean generation bridge
+- Added `JsonSchemaBeanGenerator` in `juneau-bean-jsonschema` to generate typed `JsonSchema` beans from Java types via `JsonSchemaGenerator`.
+- Added static convenience factories on `JsonSchema`: `JsonSchema.of(Type)` and `JsonSchema.of(Class<?>)`.
+- Added `JsonSchema` bean support for `format`, `$comment`, and `deprecated` so bean output preserves generator metadata.
+- Added tests covering `JsonSchemaBeanGenerator` output parity with `JsonSchemaGenerator` map output and round-trip conversion.
+
+### Schema validation mode for parsers and serializers
+- Added an opt-in `validateSchema` flag on `MarshallingContext` that validates bean-property values against the constraints declared by `@Schema` annotations during both parsing (value set on the bean) and serialization (value read from the bean).
+- Backed by a new `JsonSchemaValidator` in `juneau-bean-jsonschema` that implements JSON Schema Draft 2020-12 semantics on top of the typed `JsonSchema` bean.
+- Added new commons-side SPI (`PropertyValidator` / `PropertyValidatorFactory` / `PropertyValidators`) so marshall and `juneau-bean-jsonschema` stay decoupled. The factory is discovered via `ServiceLoader`; when `juneau-bean-jsonschema` is absent, the flag becomes a silent no-op.
+- Added fluent enablers: `MarshallingContext.Builder.validateSchema()` / `validateSchema(boolean)`, propagated through `MarshallingContextable.Builder` and every parser/serializer/REST-client builder subclass so chains like `JsonParser.create().validateSchema().build()` keep their concrete return type.
+- Added `@BeanConfig(validateSchema="true")` so the flag can be configured by annotation (with `VarResolver.DEFAULT` interpolation).
+- Validation failures throw `SchemaValidationException`; parsers surface them as `ParseException` and serializers as `SerializeException`.
+- Supported keywords (v1): `type`, `enum`, `const`, numeric (`minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf`), string (`minLength`, `maxLength`, `pattern`), array (`minItems`, `maxItems`, `uniqueItems`, `items`), object (`minProperties`, `maxProperties`, `required`, `properties`).
+- Example:
+ ```java
+ public class MyBean {
+ @Schema(minLength=2, maxLength=10) public String name;
+ @Schema(minimum="0", maximum="150") public int age;
+ }
+ ReaderParser p = JsonParser.create().validateSchema().build();
+ p.parse("{\"name\":\"a\"}", MyBean.class); // throws ParseException - too short
+ ```
+
+### AI-friendly `summary` field on `@Schema` and related annotations
+- Added a `summary` field (alias `su`) to `@Schema`, designed to hold a short, single-line description suitable for AI / LLM consumption, compact docs, and tooltips. Complements (and does not replace) the existing multi-line `description` field.
+- `summary` flows through `SchemaAnnotation.asMap()` and `JsonSchemaGenerator` into the generated JSON Schema output under the key `"summary"`.
+- Added a paired `summary` field on companion annotations that already expose `description`: `@Marshalled`, `@BeanProp`, `@Response`, `@Header`, `@Query`, `@Path`, `@FormData`, `@Content`.
+- Added `JsonSchema.getSummary()` / `setSummary(String)` (and fluent overrides on `JsonSchemaProperty` and `JsonSchemaRef`), so the typed bean and the `JsonSchemaBeanGenerator` bridge surface the new field automatically.
+- Added Swagger / OpenAPI propagation: `summary` from `@Schema` is now copied into the generated Swagger Schema Object alongside `description`.
+- Example:
+ ```java fragment
+ @Schema(
+ summary="A pet available for adoption",
+ description={
+ "Represents a pet in the store's inventory.",
+ "Includes details such as name, species, breed, age, and adoption status."
+ }
+ )
+ public class Pet { ... }
+ ```
+
+### Neutral `MarshalledMap` / `MarshalledList` collections + `Json5Map` / `Json5List`
+A major refactor of the generic-model collection hierarchy. `JsonMap` / `JsonList` are no longer the
+single one-size-fits-all generic-model types: they are now strict-JSON-flavored subclasses of a new
+neutral, marshaller-agnostic base, with JSON5-flavored siblings in `org.apache.juneau.marshall.json5`.
+
+**Behavioral break.** `JsonMap.toString()` and `JsonList.toString()` now return strict
+[RFC 8259](https://www.rfc-editor.org/rfc/rfc8259) JSON (was JSON5), and their `(CharSequence)` /
+`(Reader)` constructors and `ofString(...)` factories (renamed from `ofJson(...)`) now default to
+`JsonParser.DEFAULT` (was `Json5Parser.DEFAULT`). See the
+[v10.0 Migration Guide](/docs/topics/V10MigrationGuide) for the full breakdown and the
+recommended fix paths.
+
+#### New neutral base in `org.apache.juneau.collections`
+
+- **`MarshalledMap`** (extends `LinkedHashMap<String,Object>`) and **`MarshalledList`** (extends
+ `LinkedList<Object>`) — neutral base classes with no language coupling. Their default `toString()`
+ is the inherited `LinkedHashMap` / `LinkedList` form.
+- Carry the full marshaller-agnostic surface: typed accessors (`getInt`, `getLong`, `getBoolean`,
+ `getString`, `getStringArray`, `getWithDefault`, `findKeyIgnoreCase`, `find`, etc.), fluent setters
+ (`append`, `appendIf`, `appendIfAbsent`, `appendReverse`, etc.), bean integration (`cast`,
+ `setBeanSession`, `getMarshallingSession`), `PathTraversal`-driven path navigation (`getAt`, `putAt`,
+ `postAt`, `deleteAt`), copy-with-filter helpers (`include`, `exclude`, `keepAll`, `removeAll`),
+ inner-map plumbing (`inner`), nested-collection accessors (`getMap`, `getList`), and the standard
+ `EMPTY_MAP` / `EMPTY_LIST` / `unmodifiable()` / `modifiable()` family.
+
+##### `JsonMap` / `JsonList` retargeted to strict JSON (breaking)
+
+- `toString()` now returns `Json.of(this)` — strict RFC 8259 JSON (was `Json5.of(this)`).
+- `(CharSequence)` / `(Reader)` constructors and the renamed `ofString(...)` static factories
+ (previously `ofJson(...)`) now default to `JsonParser.DEFAULT` (was `Json5Parser.DEFAULT`).
+- `ofText(...)` remains available as a deprecated forwarding alias for compatibility with existing callers.
+ Bare-constructor calls that feed JSON5 input (unquoted keys, single-quoted strings) into
+ `new JsonMap(...)` will now throw `ParseException`.
+- The JSON-specific helpers `toJson()` / `toJson5()` / `toJsonl()` / `toJcs()` / `toHjson()` /
+ `toReadableJson5()` stay on `JsonMap` / `JsonList`. **`toJson()` is now the synonym for
+ `toString()`**, not `toJson5()`.
+
+##### New `Json5Map` / `Json5List` in `org.apache.juneau.marshall.json5`
+
+- `Json5Map` (extends `MarshalledMap`) and `Json5List` (extends `MarshalledList`) carry the JSON5
+ flavor that `JsonMap` / `JsonList` used to carry: `toString()` returns `Json5.of(this)`,
+ `(CharSequence)` / `(Reader)` constructors default to `Json5Parser.DEFAULT`, single-arg `ofString(...)`
+ static factories (same name as the neutral base), JSON5-flavored `putJson5(...)`.
+- These are the drop-in replacement for callers that today rely on `JsonMap.toString()` producing
+ JSON5 or on `new JsonMap("{unquoted:'json5'}")` parsing JSON5.
+
+##### Parser sessions now produce flavored maps / lists (breaking)
+
+`MarshallingSession` now exposes two `protected` factory hooks — `newGenericMap()` and
+`newGenericList()` — that control the runtime type of maps / lists created during parsing into an
+unbound `Object` / `Map<String,Object>` / `Collection<Object>` target. The base session returns the
+neutral `MarshalledMap` / `MarshalledList`; per-language parser sessions override to return their
+flavored type:
+
+| Parser | `parse(text, Object.class)` runtime type |
+|---|---|
+| `JsonParser.DEFAULT` | `JsonMap` / `JsonList` (unchanged) |
+| `Json5Parser.DEFAULT` | `Json5Map` / `Json5List` (**new** — used to be `JsonMap` / `JsonList`) |
+| Any other parser (`XmlParser`, `YamlParser`, `UonParser`, `UrlEncodingParser`, `MsgPackParser`, `CborParser`, `BsonParser`, `HtmlParser`, `HoconParser`, `JsonlParser`, `MarkdownParser`, `HjsonParser`, `CsvParser`, `RdfXmlParser`, etc.) | `MarshalledMap` / `MarshalledList` (**new** — used to be `JsonMap` / `JsonList`) |
+
+Code that casts the result to `JsonMap` / `JsonList` (or uses `instanceof JsonMap` / `instanceof
+JsonList`) will throw `ClassCastException` against any non-`JsonParser` parser. Recommended fixes:
+
+1. **Cast to the matching flavored type** (`Json5Map` / `Json5List`), or
+2. **Cast to the neutral base** (`MarshalledMap` / `MarshalledList`) when flavor doesn't matter
+ at the call site, or
+3. **Pass an explicit target class** to force the old runtime type:
+ `parser.parse(text, JsonMap.class)`. The hook is only consulted for `Object` / `Map<String,Object>` /
+ `Collection<Object>` targets; explicit targets always win.
+
+Nested maps / lists inside a parsed structure are also produced through the hook, so the flavor
+propagates uniformly through the parsed tree.
+
+##### `ResolvingJsonMap` renamed to `ResolvingMarshalledMap`
+
+The SVL-resolving map (which moved into `org.apache.juneau.collections` as part of an earlier change)
+has been **renamed** to `ResolvingMarshalledMap` and re-parented from `JsonMap` to the new neutral
+`MarshalledMap` base. SVL resolution is language-agnostic, so the strict-JSON parent was no longer
+the right shape. Hard rename, no deprecation shim. See the SVL "Package Moves" table further down
+in this file and the [v10.0 Migration Guide](/docs/topics/V10MigrationGuide) for the import update.
+
+##### Migration
+
+The behavioral break around `JsonMap.toString()` / `JsonList.toString()`, the bare-constructor
+default parser change, and the parser-produced runtime-type change are all called out in detail in
+the [v10.0 Migration Guide](/docs/topics/V10MigrationGuide). The short version:
+
+| Old behavior | New behavior | Recommended migration |
+|---|---|---|
+| `JsonMap.toString()` → JSON5 | strict JSON | Use `Json5Map` (or `Json5.of(map)`) for JSON5 output. |
+| `new JsonMap("{unquoted:'json5'}")` parses JSON5 | parses strict JSON (throws) | Use `new Json5Map(...)` / `Json5Map.ofString(...)`. |
+| `JsonMap.ofJson(text)` / `JsonList.ofJson(text)` / `Json5Map.ofJson5(text)` / `Json5List.ofJson5(text)` | `JsonMap.ofString(text)` / `JsonList.ofString(text)` / `Json5Map.ofString(text)` / `Json5List.ofString(text)` | Single-arg parse factories are now named `ofString(...)` for consistency with string-based parse APIs across map/list types. `ofText(...)` is retained as a deprecated compatibility alias. No behavioral change — the subclass single-arg form still uses the language-specific [...]
+| `Json5Parser.DEFAULT.parse(s, Object.class)` → `JsonMap` | → `Json5Map` | Cast to `Json5Map`, or to neutral `MarshalledMap`, or pass `parser.parse(s, JsonMap.class)`. |
+| Other parsers' `parse(s, Object.class)` → `JsonMap` | → `MarshalledMap` | Same fix options. |
+| `import org.apache.juneau.collections.ResolvingJsonMap;` | `import org.apache.juneau.collections.ResolvingMarshalledMap;` | Hard rename; update imports and any `new ResolvingJsonMap(...)` constructor calls. |
+
+##### Future work (Phase D — not delivered in 10.0)
+
+Per-language flavored `XMap` / `XList` pairs for the remaining marshallers (`XmlMap` / `XmlList`,
+`YamlMap` / `YamlList`, `UonMap` / `UonList`, `HoconMap` / `HoconList`, `BsonMap` / `BsonList`,
+`MsgPackMap` / `MsgPackList`, `CborMap` / `CborList`, etc.) are planned as a follow-on. Until they
+land, parser sessions other than `JsonParserSession` / `Json5ParserSession` produce the neutral
+`MarshalledMap` / `MarshalledList`.
+
+### MessagePack media type normalized to `application/msgpack`
+
+- `MsgPackSerializer` and `MsgPackParser` now use the RFC 6838-conforming `application/msgpack` as the
+ canonical media type. The previous invalid `octal/msgpack` value (`octal` is not a registered MIME
+ top-level type) has been replaced.
+- The serializer emits `Content-Type: application/msgpack` only, and `MsgPackSerializer.DEFAULT
+ .getResponseContentType()` (and `getPrimaryMediaType()`) now return `application/msgpack`.
+- For backward compatibility, both the serializer's `Accept` media-type list and the parser's
+ `consumes` list retain `octal/msgpack` as an alias, so existing wire payloads and clients that
+ still send `Content-Type: octal/msgpack` or `Accept: octal/msgpack` continue to be matched and
+ decoded.
+- Updated the `RestClient.msgPack()` / `RestRequest.msgPack()` Javadoc to document the new media-type
+ value sent on the `Accept` and `Content-Type` request headers.
+
+### juneau-marshall bug fixes
+
+- **Parquet serializer compression codec now fails fast (breaking).** An unknown
+ `@ParquetConfig(compressionCodec)` value (e.g. a typo) or a codec the serializer cannot write
+ (`SNAPPY`, which is decode-only) now throws an `IllegalArgumentException` naming the bad value and the
+ valid write codecs (`UNCOMPRESSED`, `GZIP`) instead of silently falling back to `UNCOMPRESSED`.
+ `ParquetSerializer.Builder.compressionCodec(CompressionCodec.SNAPPY)` throws likewise. The parser still
+ reads SNAPPY-compressed files.
+- **Removed the unused `@Parquet.parquetType()` and `@Parquet.logicalType()` attributes (breaking).** Neither was read by the
+ serializer or parser, so setting them never had any effect. `@Parquet` no longer declares any attributes; remove them
+ from any source that sets them (otherwise it will no longer compile).
+- **`@Inject`-method and constructor `@Value Optional<T>` parameters** now wrap resolved values
+ correctly. Previously, declaring a parameter as `@Value("${maybe}") Optional<String>`
+ caused `IllegalArgumentException` at reflective invoke because `ParameterInfo.resolveValue`
+ returned the unwrapped scalar. `ParameterInfo.resolveValue` now mirrors `FieldInfo.inject`:
+ when the declared parameter type is `Optional<T>`, a missing or empty-string resolution becomes
+ `Optional.empty()` and a non-empty resolved value becomes `Optional.of(value)`. The plain-scalar
+ workaround (declare as `T` + null-check) is no longer needed for Optional-typed parameters.
+
+- **`ClassFormatSwap.unswap(...)` now consults the session classloader** before falling back to the
+ thread-context classloader. Sessions that explicitly set a classloader via
+ `MarshallingContext.Builder.classLoader(ClassLoader)` — such as OSGi bundle classloaders, webapp
+ classloaders, and plugin systems — now correctly resolve `Class<?>` values from that classloader
+ during parsing. The same fix was applied to the inline per-property swap installed by
+ `MarshalledPropertyPostProcessor`. The thread-context classloader is still used as a fallback when
+ no session classloader is configured, preserving historical behavior for existing callers.
+
+- **`HoconTokenizer` peek/skip invariant hardened**. `HoconTokenizer.skipWhitespaceAndComments()`
+ is now a no-op while a token has already been peeked (`peek()` / `peekNoSkip()` has cached a token).
+ Once a token is cached the underlying reader sits *after* that token, so skipping whitespace would
+ consume characters that semantically follow the cached token — most dangerously a newline that is the
+ element separator of an enclosing array/object. `HoconParserSession.parseArray` / `parseObject`
+ previously protected against this with targeted peek-before-skip guards; the invariant is now centralized
+ in the tokenizer, and those guards have been removed. No behavior change for existing inputs (newline-
+ separated nested arrays/objects still stay nested; same-line array/object concatenation still works); the
+ change forecloses a class of latent bugs where a future caller of `skipWhitespaceAndComments` could
+ silently reintroduce the over-read.
+
+- **Abstract `Collection`-typed bean properties with no setter and no `@BeanProp(type=...)` now populate
+ correctly**. A field such as `Set<MyEnum> tags;` (or `List` / `SortedSet` / `Deque` / `Queue`,
+ generic or raw) with only a public field and no concrete type hint previously could not be parsed from a
+ JSON array — there was no concrete-collection materialization branch (the sibling abstract-`Map` case was
+ already handled). `BeanPropertyMeta.setPropertyValue(...)` now materializes a concrete collection using a
+ best-effort-then-fallback strategy: it first tries to instantiate the field's declared type directly (so a
+ concrete `AbstractSet` / `AbstractList` subclass with an accessible no-arg constructor is honored as
+ itself), and only falls back to a shape-based default when the declared type is genuinely not instantiable
+ — `SortedSet`/`NavigableSet` → `TreeSet`, `Set` → `LinkedHashSet` (insertion order preserved),
+ `Deque`/`Queue` → `ArrayDeque`, `List`/raw `Collection` → `ArrayList`. Element values are coerced to the
+ declared element type. An explicit `@BeanProp(type=...)` override still wins, but it is **no longer
+ required** as a workaround for these abstract-collection fields.
+
+- **JSON/JSON5/UON parsers no longer drop U+007F (DEL) characters in strings that also contain escape sequences.** A string value containing a literal DEL character alongside at least one escape sequence previously lost the DEL on parse; the character is now preserved.
+- **TOML non-scalar lists now round-trip.** `TomlSerializer` previously wrote `List<Integer>` as `nums = ["1", "2"]`,
+ and lists of beans, maps or lists via `toString()` (e.g. `["Q$In@74e52303"]`), none of which `TomlParser` could read back.
+ Scalar lists are now native arrays (`nums = [1, 2]`), lists of lists are nested arrays (`grid = [[1], [2, 3]]`), lists/arrays
+ of beans or maps are arrays of tables (`[[ins]]`), maps inside mixed lists are inline tables, and empty lists are `[]`.
+ `TomlParser` also now resolves dotted table headers that pass through an array of tables (e.g. `[[a]]` then `[a.b]`) to the
+ most recent element, per the TOML spec.
+
+## juneau-commons
+
+### `@Value` annotation + `${xxx}` shortcut
+- Added `org.apache.juneau.commons.inject.Value` annotation for declarative injection of configuration
+ values into `BeanInstantiator`-created beans (fields, constructor parameters, setter parameters,
+ arbitrary method parameters). Resolves expressions through `VarResolver.DEFAULT` and coerces
+ the resolved string to the target type via `Settings.toType(...)`.
+- Extended `JsrSupport` with FQN-based detection of the new Juneau `@Value` (`JUNEAU_VALUE`) and
+ Spring's `@Value` (`SPRING_VALUE = "org.springframework.beans.factory.annotation.Value"`), keeping
+ `juneau-commons` compile-time clean of Spring. Either annotation is picked up by the same
+ injection path.
+- Made `@Value` and `@Inject` mutually exclusive on the same site — applying both throws
+ `BeanCreationException` with a clear "site already has @Value / @Inject" message.
+- Added the `${xxx}` shortcut in `VarResolverSession` — the tokenizer rewrites `${...}` to `$P{...}`
+ before resolution, so it inherits the full `PropertyVar` source stack (system properties, env vars,
+ registered `Config`s, Spring `Environment`). The first top-level `:` in the expression is
+ translated to `,` so Spring-style `${key:default}` defaulting works without further changes.
+- Made `Settings.toType(...)` public and extended it to also recognise `static T parse(CharSequence)`
+ methods (in addition to `parse(String)`), so `java.time` types like `Instant` coerce cleanly.
+- Added `Settings.removeSource(PropertySource)` for explicit deregistration — required so per-context
+ bridges (microservice, `RestContext`, Spring `Environment`) can clean up at shutdown without
+ cross-test bleed.
+- `Optional<T>` field/parameter injection collapses `null` and empty-string results to
+ `Optional.empty()` — matching the "missing-key" intuition that consumers expect.
+
+### SVL overhaul: `#{...}` scripting + `VarTemplate` compilation
+This is a **major-release breaking change** in the SVL surface. The 11 single-purpose
+transformation `Var` classes are removed without `@Deprecated` shims; their behaviour is
+recovered through the new `#{name(args)}` function-call syntax.
+
+#### New `#{...}` function-call syntax
+
+- Introduced the `#{name(arg1, arg2, ...)}` script-call form inside any SVL string.
+ Tokenized by the same recursive-descent compiler that handles `${...}` and `$X{...}`, so the
+ three forms nest naturally (`#{upper(${user.name})}`, `${prefix:#{lower(${value})}}`, …).
+- Added the `org.apache.juneau.commons.svl.VarFunction` SPI plus `TypedFunction` reflection
+ helper. `TypedFunction` derives arity and per-arg types from the declared `invoke(...)` method
+ signature(s) and routes string args through `ArgCoercer` for coercion to `int` / `long` /
+ `double` / `boolean` / `String` / `String[]` / `Object`.
+- Boolean truthiness is centralised in `ArgCoercer`: `"true"`, `"yes"`, `"on"`, `"1"` are truthy;
+ everything else (including `null` and empty string) is falsy. `#{if(...)}`, `#{not(...)}`,
+ `#{switch(...)}`, etc. all share the same rule.
+- `#{switch(...)}` matches each case pattern as a glob (`*` = any sequence, `?` = single char),
+ preserving the legacy `$SW{...}` Var's matching semantics.
+- `#{len(s, delimiter)}` adds a two-argument overload that counts parts after split-by-delimiter,
+ preserving the legacy `$LN{s, delim}` Var's two-arg behaviour. The single-arg form still
+ returns character length.
+
+##### Built-in function catalog (~68 functions, 10 categories)
+
+| Category | Examples |
+|---|---|
+| String | `upper`, `lower`, `len`, `substring`, `trim`, `split`, `join`, `format`, `concat`, `pathToken` |
+| Type conversion | `toInt`, `toLong`, `toDouble`, `toBoolean`, `toString` |
+| Arithmetic | `add`, `sub`, `mul`, `div`, `mod`, `min`, `max`, `abs`, `round`, `floor`, `ceil` |
+| Boolean | `not`, `and`, `or`, `xor`, `eq`, `ne`, `lt`, `gt`, `le`, `ge` |
+| Conditional | `if`, `switch`, `coalesce`, `notEmpty`, `case` |
+| Regex | `matches`, `replace`, `extract` |
+| Encoding | `urlEncode`, `urlDecode`, `base64Encode`, `base64Decode`, `htmlEscape`, `htmlUnescape` |
+| Date/time | `now`, `today`, `formatDate`, `parseDate` |
+| Random/UUID | `uuid`, `randomInt`, `randomLong`, `randomDouble`, `randomString`, `randomChar` |
+| JSON navigation | `jsonPath`, `get`, `keys`, `values`, `size` |
+
+See <a href="/site/apidocs/org/apache/juneau/commons/svl/functions/package-summary.html" target="_blank">org.apache.juneau.commons.svl.functions</a>
+for the per-class function lists with full signatures and arg style notes.
+
+##### Function discovery
+
+Three discovery channels, evaluated in order:
+
+1. **Explicit registration** — `VarResolver.create().functions(MyFn.class).build()`.
+2. **`ServiceLoader` discovery** — declare your implementation in
+ `META-INF/services/org.apache.juneau.commons.svl.VarFunction`. `VarResolver.create().defaultFunctions().build()`
+ and `VarResolver.DEFAULT` both load these automatically.
+3. **`BeanStore` lookup** — built-in functions are constructed through the
+ `VarResolver.Builder`'s `BeanStore` so they can themselves take constructor `@Inject`/`@Value`
+ dependencies (rarely needed but available).
+
+Unknown function names fail lazily at resolve time (`No such function 'foo'`) rather than at
+compile time, matching the existing `Var` registry behaviour.
+
+##### Hard-break removal of 11 `Var` classes
+
+The following 11 transformation `Var` classes are **removed without `@Deprecated` shims**:
+
+| Removed `Var` | Removed prefix | Replacement |
+|---|---|---|
+| `IfVar` | `$IF{...}` | `#{if(cond, then, else)}` |
+| `SwitchVar` | `$SW{...}` | `#{switch(value, pat1:result1, ..., *:default)}` (glob semantics preserved) |
+| `CoalesceVar` | `$CO{...}` | `#{coalesce(a, b, c, ...)}` |
+| `NotEmptyVar` | `$NE{...}` | `#{notEmpty(arg)}` |
+| `PatternMatchVar` | `$PM{...}` | `#{matches(value, pattern)}` |
+| `PatternReplaceVar` | `$PR{...}` | `#{replace(value, pattern, replacement)}` |
+| `PatternExtractVar` | `$PE{...}` | `#{extract(value, pattern, groupIndex)}` |
+| `UpperCaseVar` | `$UC{...}` | `#{upper(arg)}` |
+| `LowerCaseVar` | `$LC{...}` | `#{lower(arg)}` |
+| `LenVar` | `$LN{...}` | `#{len(arg)}` (single-arg) or `#{len(arg, delimiter)}` (two-arg parity) |
+| `SubstringVar` | `$ST{...}` | `#{substring(arg, start)}` or `#{substring(arg, start, end)}` |
+
+`VarList.addDefault()` no longer registers the removed Vars; the
+`org.apache.juneau.commons.svl.vars` package no longer ships their classes. Any user code or
+template that still references the removed prefixes fails at resolve time with
+`No such var '<prefix>'`.
+
+The source-data Vars (`$E{}`, `$S{}`, `$MF{}`, `$A{}`, `$P{}` and the shortcut `${}`) are
+unchanged.
+
+##### New `VarTemplate` compiled-template API
+- Added `VarResolver.compile(String)` → `VarTemplate` — tokenizes a template once and returns a
+ reusable, threadsafe compiled form. Subsequent `template.resolve(session)` calls skip
+ tokenization + var-registry lookup, walking the cached segment array directly.
+- Added `VarResolver.resolveSupplier(String)` → `Supplier<String>` — opens a fresh session on
+ every `.get()` call. Safe to share across threads. Recommended default.
+- Added `VarResolverSession.compile(String)` and `VarResolverSession.resolveSupplier(String)` —
+ symmetric session-bound variants. The session-bound Supplier inherits the session's
+ threadsafety contract (i.e. not threadsafe by default).
+- Added `VarTemplate.isLiteral()` — `true` when the template resolves to a fixed string (empty
+ template, no variables, or all variables fold to literals via stable-value folding). Callers
+ can fast-path purely-literal inputs.
+- Refactored `VarResolverSession.resolve(String)` to delegate to
+ `compile(input).resolve(session)` so there is a single tokenizer + segment-resolution path in
+ the codebase. No behaviour change for existing callers.
+
+##### `@Value Supplier<String>` field-type autodetect
+
+- `BeanInstantiator` now inspects the declared field/parameter type at injection sites carrying
+ `@Value`. A `Supplier<String>` field receives a re-evaluating, threadsafe Supplier (delegates
+ to `VarTemplate.asSupplierWithFreshSessions(...)`); a bare `String` field gets the one-shot
+ resolved value as before. **No `@Value(supplier=true)` flag is required — the field type IS
+ the opt-in signal.**
+- Literal-only expressions (`@Value("plain") Supplier<String>`) take a constant-folding fast
+ path that returns the same captured `String` reference on every `.get()` call, avoiding the
+ per-call session allocation entirely.
+- Worked patterns for live UUID factories, idempotency keys, jittered delays, and hot-reload
+ config knobs are in the [`@Value` topic page](/docs/topics/ValueAnnotation#supplierstring-field-type--re-evaluating-reads-1000).
+
+##### Compile-time stable-value folding
+
+- New `default boolean Var.isStable() { return false; }` SPI hook. Opt-in per `Var`
+ implementation; conservative `false` default protects third-party `Var`s from accidental
+ incorrect folding.
+- Four built-in `Var`s opt in: `EnvVariablesVar`, `SystemPropertiesVar`, `ManifestFileVar`,
+ `ArgsVar`. Compiled templates eagerly resolve these at compile time and replace the segment
+ with a `LiteralSegment` — runtime dispatch is eliminated.
+- `SystemPropertiesVar`'s opt-in carries a documented caveat: `System.setProperty(...)` calls
+ *after* compile do not propagate to a folded template. In practice this is the desired
+ behaviour for the common "read once at startup" use case.
+- `ConfigVar`, `PropertyVar`, `DotenvVar`, `EnvFileVar` stay non-stable. Their backing source
+ can mutate at runtime (config reload, file watch, runtime `Settings.set`), so folding would
+ produce stale reads.
+
+##### Internal perf
+
+- `BeanInstantiator`'s `@Value` resolution path caches a `VarTemplate` per distinct expression
+ in `ValueResolver.TEMPLATE_CACHE`. Repeated bean construction reads the cached form rather
+ than re-tokenizing.
+- `RestOpContext.pathMatchers` Memoizer now goes through `vr.compile(p).resolve(session)`
+ explicitly so the framework hot loop exercises the compiled-template seam. If we ever switch
+ to per-request dynamic path resolution, only the `.resolve(session)` step moves to the
+ request handler — the compile is already done at context-build time.
+- Micro-benchmark (`juneau-utest/.../VarResolver_Benchmark_Test`): compile-once + resolve-N is
+ ~1.7× faster than compile-per-call for plain `${...}` templates and ~1.4× for
+ `#{...}` script templates on a representative machine. The headline `≥ 2× / ≥ 5×` targets
+ from the plan apply to comparisons against the pre-10.0 legacy ad-hoc dispatcher, which is no
+ longer in the tree — the unified compiled-form path is now the baseline.
+
+##### Migration
+
+For any SVL template that uses one of the 11 removed `Var` prefixes, rewrite to `#{...}`. Cookbook:
+
+| Before | After |
+|---|---|
+| `$UC{$S{user.name}}` | `#{upper(${user.name})}` |
+| `$IF{$S{prod},prod.key,dev.key}` | `#{if(${prod}, prod.key, dev.key)}` |
+| `$SW{$S{os},*win*:Windows,*:Unix}` | `#{switch(${os}, *win*:Windows, *:Unix)}` |
+| `$PR{$S{name},[aeiou],*}` | `#{replace(${name}, [aeiou], *)}` |
+| `$LN{a,b,c,,}` | `#{len(a,b,c, ,)}` |
+| `$ST{hello,2,4}` | `#{substring(hello, 2, 4)}` |
+
+The full `${...}` shortcut, `$S{}`/`$E{}`/`$MF{}`/`$A{}`/`$P{}` source vars, and `$C{}` config
+var keep their existing syntax — no migration required for those.
+
+### juneau-commons breaking changes
+
+- **`org.apache.juneau.commons.lang.Value` renamed to `Holder`.** The mutable reference-wrapper utility class has been renamed to `Holder<T>` to resolve an import-name collision with the `@Value` injection annotation. Update import statements: `import org.apache.juneau.commons.lang.Value` → `import org.apache.juneau.commons.lang.Holder`. No behavioral change.
+
+- **`XValue` primitive-holder companions renamed to `XHolder`.** The nine typed primitive-holder subclasses have been renamed for consistency with the `Value` → `Holder` rename: `BooleanValue` → `BooleanHolder`, `ByteValue` → `ByteHolder`, `CharValue` → `CharHolder`, `DoubleValue` → `DoubleHolder`, `FloatValue` → `FloatHolder`, `IntegerValue` → `IntegerHolder`, `LongValue` → `LongHolder`, `ShortValue` → `ShortHolder`, `StringValue` → `StringHolder`. Update import statements and type refe [...]
+
+## juneau-config
+
+### Classpath-default `Config` bridge to `Settings`
+- Added `org.apache.juneau.config.ConfigPropertySourceProvider`, a `META-INF/services`-discovered
+ `PropertySourceProvider` that bridges `Config.getSystemDefault()` (i.e. the classpath
+ `juneau.cfg` / `default.cfg` / `application.cfg` / etc. discovered by
+ `Config.getCandidateSystemDefaultConfigNames()`) into the process-wide `Settings` singleton.
+- Resolution is **lazy** — `Config.getSystemDefault()` is invoked on the first `Settings.get(name)`
+ lookup rather than at SPI registration time. This avoids a static-initialization cycle
+ (`Settings.<clinit>` → `useServiceLoader()` → `ConfigPropertySourceProvider.create()` →
+ `Config.<clinit>` → `Cache.create()` → `Utils.env(...)` → `Settings.<clinit>`) and a runtime
+ reentrance cycle (`Config.findSystemDefault()` looking up `"juneau.configFile"` through every
+ registered source, including this one). A thread-local guard short-circuits the reentrant
+ lookup; any throwable while `Config` is mid-initialisation is treated as "not yet available"
+ without memoising the failure.
+- The provider is **silent** when no classpath `Config` exists — every lookup returns
+ `missing()`, no log noise, no exception.
+- The provider's `order()` is `100`, deliberately low — so any programmatic
+ `Settings.get().addSource(...)` registered later (microservice, `RestContext`, Spring) shadows
+ the classpath default at lookup time.
+
+### YAML config format support
+- Added optional YAML config support to `juneau-config` alongside the existing INI/CFG format.
+- Added format selection via config name extension (`.yml` / `.yaml`) and explicit builder methods (`.yaml()` / `.ini()`).
+- Added path-style section support (e.g. `[foo/bar/baz]`) to align INI sections with nested YAML mappings.
+- Updated default `FileStore` extension search order to include `cfg,yml,yaml` (with `cfg` remaining first for backward compatibility).
+
+### Unified Property Source Hierarchy
+- Added a new property-source SPI naming model: `PropertySource` / `PropertyStore` and functional variants.
+- Added `PropertySourceProvider` (ServiceLoader SPI) so ambient sources can be auto-registered onto `Settings`.
+- Added built-in property sources for args, manifest entries, system properties, environment variables, and `.env`.
+- Added runtime source wiring via `Settings.addSource(PropertySource)` to allow explicit opt-in sources.
+- Added `PropertyVar` (`$P{key[,default]}`) as the SVL unified property-stack resolver.
+- Existing `$S` / `$E` / `$A` / `$MF` vars remain supported as source-specific resolvers, now aligned with the unified settings model.
+- **Breaking:** `SettingSource`, `SettingStore`, `FunctionalSource`, and `FunctionalStore` have been removed outright (no deprecation shims). Migrate to `PropertySource`, `PropertyStore`, `FunctionalPropertySource`, and `FunctionalPropertyStore` respectively.
+
+### Bean→Marshalled Rename
+A comprehensive rename of annotation and engine types to clarify the distinction between *Java-bean-structure* types (which keep `BeanXxx` names) and *marshalling-process* types (which are now `MarshallingXxx`).
+
+#### Annotation Renames (breaking)
+
+| Old | New |
+|-----|-----|
+| `@Bean` (marshall) | `@Marshalled` |
+| `@BeanApply` | `@MarshalledApply` |
+| `@Beanp` | `@MarshalledProp` |
+| `@BeanpApply` | `@MarshalledPropApply` |
+| `@Beanc` | `@MarshalledCtor` |
+| `@BeancApply` | `@MarshalledCtorApply` |
+| `@BeanIgnore` | `@MarshalledIgnore` |
+| `@BeanIgnoreApply` | `@MarshalledIgnoreApply` |
+| `@BeanConfig` | `@MarshalledConfig` |
+
+##### Engine Type Renames (breaking)
+
+| Old | New |
+|-----|-----|
+| `BeanContext` | `MarshallingContext` |
+| `BeanContextable` | `MarshallingContextable` |
+| `BeanSession` | `MarshallingSession` |
+| `BeanTraverseContext` | `MarshallingTraverseContext` |
+| `BeanTraverseSession` | `MarshallingTraverseSession` |
+| `BeanRecursionException` | `MarshallingRecursionException` |
+| `BeanStringSwap` | `MarshallingStringSwap` |
+| `BeanContextConverter` | `MarshallingContextConverter` |
+| `BeanDefMapper` | `MarshallingDefMapper` |
+| `BeanContext.DEFAULT` | `MarshallingContext.DEFAULT` |
+
+##### Unchanged Types (clarification)
+
+The following `BeanXxx` types model the *Java bean structure* and are deliberately unchanged:
+`BeanMap`, `BeanMeta`, `BeanPropertyMeta`, `BeanPropertyValue`, `BeanPropertyConsumer`, `BeanRegistry`, `BeanDictionaryMap`, `BeanDictionaryList`, `BeanProxyInvocationHandler`, `BeanDiff`.
+
+##### Package Moves (Phase 5a of bean-layer split)
+
+As part of the bean-layer split (separating bean-modeling from marshalling), the `BeanInterceptor` type was moved into the new `org.apache.juneau.commons.bean` package in `juneau-commons`:
+
+| Old | New |
+|-----|-----|
+| `org.apache.juneau.marshall.swap.MarshallingInterceptor` | `org.apache.juneau.commons.bean.BeanInterceptor` |
+
+This is both a rename (back to the pre-10.0 `BeanInterceptor` name) and a package move. Source code referencing the type by simple name through wildcard imports may need to update its import from `org.apache.juneau.marshall.swap.*` to `org.apache.juneau.commons.bean.*` (or add an explicit import).
+
+##### New Type: `BeanConfigContext` (Phase 5b of bean-layer split)
+
+A new `org.apache.juneau.commons.bean.BeanConfigContext` POJO carries the runtime bean-modeling settings — visibility thresholds, the `beans*Require*` toggles, fluent-setter detection, property naming, the bean-type property name, the not-a-bean exclusion sets, the active `BeanStore`, the active `AnnotationProvider`, and an optional `Predicate<ClassInfo>` override for `isNotABean`. It is the runtime sibling of the `@BeanConfig` annotation introduced in Phase 3.
+
+`MarshallingContext` now exposes `getBeanConfigContext()` returning a memoized snapshot view, so consumers that only need bean-modeling settings can depend on `commons.bean` without referencing any marshalling-aware types. This is foundational infrastructure for the relocation of `BeanMap` / `BeanMeta` / `BeanPropertyMeta` and friends into `commons.bean` (completed in Phase 5c below).
+
+##### Bean-Runtime Cluster Moved to `commons.bean` (Phase 5c of bean-layer split)
+
+The seven core bean-runtime types have been physically moved out of `org.apache.juneau` (in `juneau-marshall`) into `org.apache.juneau.commons.bean` (in `juneau-commons`):
+
+| Old | New |
+|-----|-----|
+| `org.apache.juneau.BeanMap` | `org.apache.juneau.commons.bean.BeanMap` |
+| `org.apache.juneau.BeanMapEntry` | `org.apache.juneau.commons.bean.BeanMapEntry` |
+| `org.apache.juneau.BeanMeta` | `org.apache.juneau.commons.bean.BeanMeta` |
+| `org.apache.juneau.BeanPropertyMeta` (and its `Builder` inner class) | `org.apache.juneau.commons.bean.BeanPropertyMeta` |
+| `org.apache.juneau.BeanPropertyValue` | `org.apache.juneau.commons.bean.BeanPropertyValue` |
+| `org.apache.juneau.BeanPropertyConsumer` | `org.apache.juneau.commons.bean.BeanPropertyConsumer` |
+| `org.apache.juneau.BeanProxyInvocationHandler` | `org.apache.juneau.commons.bean.BeanProxyInvocationHandler` |
+
+The `@Name` annotation also moved as part of this work:
+
+| Old | New |
+|-----|-----|
+| `org.apache.juneau.annotation.Name` | `org.apache.juneau.commons.bean.Name` |
+
+After these moves, `juneau-commons` compiles standalone with no dependency on `juneau-marshall`, and the bean-modeling layer in `commons.bean` is fully self-contained. Source code referencing any of these types by simple name through wildcard imports must update from `org.apache.juneau.*` (or `org.apache.juneau.annotation.*`) to `org.apache.juneau.commons.bean.*`. Fully-qualified `@org.apache.juneau.annotation.Name(...)` references must be rewritten to `@org.apache.juneau.commons.bean.Na [...]
+
+##### Bean-Modeling Annotation Surface (Phase 5c)
+
+The bean-modeling attributes have been split out of the marshalling-side `@Marshalled` family into a parallel set of commons-side annotations. The marshalling-side annotations retain only the marshalling-only attributes; both annotations are still consulted on the marshalling-side path so a class can carry both without conflict.
+
+| Commons-side (new) | Marshalling-side sibling | Carries |
+|-----|-----|-----|
+| `@BeanType` | `@Marshalled` | Class-level bean-modeling attributes: `properties` / `p`, `excludeProperties` / `xp`, `readOnlyProperties` / `ro`, `writeOnlyProperties` / `wo`, `unsorted`, `findFluentSetters`, `propertyNamer`, `interfaceClass`, `stopClass`, `factory`. |
+| `@BeanProp` | `@MarshalledProp` | Property-level bean-modeling attributes: `name` / `value`, `ro`, `wo`, `type`, `params`, `elementType`, `factory`. |
+| `@BeanCtor` | `@MarshalledCtor` | Constructor mapping: `properties`. |
+| `@BeanIgnore` | `@MarshalledIgnore` | Bean-property / class ignore (with `ignoreAccessors` for fields). |
+| `@BeanConfig` | `@MarshalledConfig` | Class- / method-level bean-modeling config (visibility thresholds, `beans*Require*` toggles, fluent setters, property naming, not-a-bean exclusions, etc.). |
+
+`@Marshalled` retains the marshalling-only attributes: `as`, `description`, `dictionary`, `example`, `implClass`, `interceptor`, `typeName`, `typePropertyName`. `@MarshalledProp` retains its marshalling-only attributes (e.g. `dictionary`, `format`). Contributions from the bean-modeling and marshalling-side annotations are merged when building the bean filter on the marshalling-side path.
+
+##### `commons.bean` SPI Surface (Phase 5c)
+
+To plug the relocated bean-runtime types back into the marshalling layer, several new SPI types have been added to `org.apache.juneau.commons.bean`:
+
+| SPI Type | Description | Marshalling-side implementation |
+|---|---|---|
+| `BeanInfo<T>` | Bean-modeling type-info seam. Extends `ClassInfoTyped<T>`; exposes `isBean()`, `isUri()`, `getElementType()`, `getKeyType()`, `getValueType()`, `canCreateNewInstance(...)`, `getOptionalDefault()`, `getBeanConfigContext()`, `getMarshallingContext()`. | `ClassMeta<T>` extends this. |
+| `BeanSession` | Bean-modeling session SPI (`convertToType`, `convertToMemberType`, `toBeanMap`, `parseToMap(CharSequence)`, `parseToList(CharSequence)`). | `MarshallingSession` implements this. |
+| `BeanFilter` | Per-class filter surface (properties / excludeProperties / readOnlyProperties / writeOnlyProperties / typeName / interfaceClass / stopClass / propertyNamer / beanDictionary / interceptor hooks). | `MarshalledFilter` implements this. |
+| `BeanRegistryLookup` | Bean-dictionary lookup surface. | `BeanRegistry` implements this. |
+| `BeanMetaInitializer` | Marshalling-side `BeanMeta` construction hooks (filter discovery, type-name resolution, bean-registry assembly, parent-chain type-name lookup). `NOOP` default for the commons-side path. | `MarshalledBeanMetaInitializer` implements this. |
+| `BeanPropertyPostProcessor` | Marshalling-side `BeanPropertyMeta.Builder` post-processing (`@MarshalledProp` / `@Swap` / `@Uri` reads, swap-aware read/write transforms). `NOOP` default for the commons-side path. | `MarshalledPropertyPostProcessor` implements this. |
+| `BeanTypeResolver` | Marshalling-side type-resolution SPI used by `BeanPropertyMeta.Builder.validate(...)`. | `MarshallingContext` implements this. |
+
+`BeanConfigContext` carries all of these so the bean-modeling layer can drive bean-meta construction with or without a marshalling context.
+
+##### Type and Method Renames (Phase 5c, breaking)
+
+The bean-modeling SPI seam was renamed and several accessors aligned with the new SPI surface:
+
+| Old | New | Notes |
+|---|---|---|
+| `BeanTypeInfo<T>` (commons.bean) | `BeanInfo<T>` | Full type rename. No deprecation shim. |
+| `Delegate#getClassMeta()` | `Delegate#getBeanInfo()` | Returns `BeanInfo<T>`. |
+| `BeanMap#getClassMeta()` | `BeanMap#getBeanInfo()` | Returns `BeanInfo<T>`. |
+| `BeanMeta#getClassMeta()` | `BeanMeta#getBeanInfo()` | Returns `BeanInfo<T>`. |
+| `BeanPropertyMeta#getClassMeta()` | `BeanPropertyMeta#getBeanInfo()` | Returns `BeanInfo<?>`. |
+| `BeanPropertyValue#getClassMeta()` | `BeanPropertyValue#getBeanInfo()` | Returns `BeanInfo<?>`. |
+| `BeanMap#setMarshallingSession(BeanSession)` | `BeanMap#setBeanSession(BeanSession)` | No deprecated shim. Existing call sites that pass a `MarshallingSession` (which `implements BeanSession`) continue to compile after the rename. |
+
+`MarshallingContext.getClassMeta(Class)` and `MarshallingContext.getClassMeta(Type, Type...)` are intentionally unchanged — they still resolve `ClassMeta` directly on the marshalling side.
+
+The `BeanPropertyPostProcessor` SPI also tightened its signature now that `BeanPropertyMeta.Builder` lives in `commons.bean`:
+
+```java
+// Before
+void process(Object marshallingContext, Object builder);
+
+// After
+void process(Object marshallingContext, BeanPropertyMeta.Builder builder);
+```
+
+##### Removed APIs (Phase 5c)
+
+- **`BeanFilter.getMarshalledClass()`** — Dead/unused accessor on the bean-filter SPI. Removed along with the backing `marshalledClass` field on `MarshalledFilter` and the `MarshalledFilter.Builder(Class<?>)` / `MarshalledFilter.create(Class<T>)` overloads. Use `BeanFilter.getBeanClass()` (returns `ClassInfoTyped<?>`) instead.
+
+##### `ClassInfo` Additions (Phase 5c)
+
+Two pure class-check helpers were lifted up to `org.apache.juneau.commons.reflect.ClassInfo` alongside the existing `isArray()`, `isMap()`, `isBoolean()`, and `isVoid()` siblings:
+
+- **`ClassInfo#isObject()`** — Returns `true` when this class is exactly `java.lang.Object`.
+- **`ClassInfo#isOptional()`** — Returns `true` when this class is `java.util.Optional`.
+
+##### API Visibility Widening (Phase 5c)
+
+Several previously `protected` or package-private members were widened so marshalling-side callers (and `juneau-marshall-rdf` extensions) can still reach them across the new package boundary:
+
+- `BeanMeta.BeanMetaValue` record made `public` (and its `optBeanMeta()` accessor).
+- `BeanMeta` accessors `getConstructor()`, `getConstructorArgs()`, `hasConstructor()`, `newBean(Object)` widened `protected` → `public`.
+- `BeanMap` constructor `(T, BeanMeta<T>)` and `setBeanSession(BeanSession)` widened `protected` → `public`.
+- `BeanPropertyMeta.Builder` fields `innerField`, `getter`, `setter`, `rawTypeMeta`, `swap`, `readTransform`, `writeTransform`, `dictionaryClasses`, `isUri`, and `typeMeta` widened package-private → `public` (consumed by `MarshalledPropertyPostProcessor`).
+- `BeanPropertyValue.properties()` widened `protected` → `public`.
+
+##### New Feature: `@Marshalled(as=STRING)` Strategy
+
+A new `MarshalledAs` enum with values `DETECT` (default) and `STRING` has been added to `@Marshalled`:
+
+```java
+// Force a type to serialize as its toString() value, bypassing bean detection.
+@Marshalled(as=STRING)
+public class MyType {
+ @Override public String toString() { return "my-string-form"; }
+}
+```
+
+`@Marshalled(as=STRING)` replaces two earlier patterns:
+- The `BeanStringSwap` class (now `MarshallingStringSwap`; kept for custom format overrides)
+- The `@BeanIgnore`-on-class-then-`toString()` pattern
+
+##### Updated `@MarshalledIgnore` TYPE Semantics (breaking)
+
+Applying `@MarshalledIgnore` to a **class** now skips serialization entirely (outputs `null`) instead of falling through to `toString()`:
+
+```java fragment
+// Before (@BeanIgnore on class → toString())
+@BeanIgnore
+public class OldClass { @Override public String toString() { return "value"; } }
+// Serialized as: "value"
+
+// After (@MarshalledIgnore on class → null)
+@MarshalledIgnore
+public class NewClass { ... }
+// Serialized as: null
+
+// Migration: if you need toString() behavior, use @Marshalled(as=STRING) instead
+@Marshalled(as=STRING)
+public class NewClass { @Override public String toString() { return "value"; } }
+// Serialized as: "value"
+```
+
+**Migration:** If you previously used `@BeanIgnore` on a class to force `toString()` serialization, replace it with `@Marshalled(as=STRING)`. If you truly want to suppress serialization (output `null`), use `@MarshalledIgnore`.
+
+### Removed `@MarshalledProp(properties)` Attribute (breaking)
+
+The `@MarshalledProp(properties=...)` attribute has been removed. The attribute was used to limit which child properties of a nested bean/map are rendered by serializers:
+
+```java
+// Before — only "f1" was rendered on each child.
+public class MyClass {
+ @MarshalledProp(properties="f1")
+ public MyChildClass x1 = new MyChildClass();
+}
+
+// After — the attribute no longer exists; nested beans render in full.
+public class MyClass {
+ public MyChildClass x1 = new MyChildClass();
+}
+```
+
+The related internal types are gone too:
+- `BeanPropertyMeta.applyChildPropertiesFilter(...)`, `BeanPropertyMeta.getProperties()` (override-list accessor), and the underlying `properties` field/builder setter.
+- `BeanMetaFiltered` (sole purpose was wrapping a `BeanMeta` with a filtered property list).
+
+**Migration:** If you need per-property filtering for a serialization, use a custom `BeanFilter`/`@Marshalled(properties=...)` at the bean-class level instead, or configure include/exclude lists on the `MarshallingContext.Builder`. The marshalling-context-level filtering pre-dated `@MarshalledProp(properties)` and remains fully supported.
+
+### `@Beanp("*")` on non-Map fields
+
+On a field whose type is not a `Map`, `@Beanp("*")` (or `@Beanp(name="*")`) no longer tries to register a dyna property. The property name is taken from the field (via the configured `PropertyNamer`); other `@Beanp` attributes still apply. `Map` fields keep the dyna property `*` as before.
+
+### HTML documentation: bare `request:` / `servlet:` / `context:` navlinks
+
+`BasicHtmlDocTemplate` now treats bare navlink lines like `request:?Accept=...` or `request:/?Accept=...` (no `label:` prefix) as full marshall URIs. Previously the first `:` split dropped the scheme, producing relative `href` values that browsers resolved under the current path (e.g. `.../request:?Accept=...`). The same rejoin applies to `servlet:` and `context:` when the part after the colon starts with `/`, `?`, or `#`.
+
+Labeled navlinks (e.g. `JSON: request:?Accept=...`) were already correct; the bug affected **bare** entries where the text before the first `:` is only the marshall scheme name.
+
+### Native TOML Support
+
+Juneau now includes built-in TOML (Tom's Obvious Minimal Language) v1.0.0 serialization and parsing support, implemented entirely without external library dependencies. TOML is a configuration file format widely used in projects like Cargo (Rust) and Python's `pyproject.toml`.
+
+#### New Classes
+
+- **`TomlSerializer`** - Serializes POJOs to TOML format with tables, key-value pairs, and proper escaping.
+- **`TomlParser`** - Parses TOML input into POJOs using a tokenizer-based parser.
+- **`Toml`** - Convenience marshaller pairing `TomlSerializer` and `TomlParser`.
+- **`BasicTomlConfig`** - REST configuration interface for TOML-only resources.
+
+##### Features
+
+The implementation conforms to the [TOML v1.0.0 specification](https://toml.io/en/v1.0.0):
+
+```java
+// Serialize a map to TOML
+Map<String, Object> config = Map.of("name", "myapp", "port", 8080);
+String toml = Toml.of(config);
+// Produces: name = "myapp"
+// port = 8080
+
+// Parse TOML into JsonMap or beans
+JsonMap parsed = Toml.to(toml, JsonMap.class);
+MyConfig config = Toml.to(toml, MyConfig.class);
+```
+
+- **Tables** - `[section]` and nested `[parent.child]` tables
+- **Inline tables** - `{key = "value"}` for simple nested structures
+- **Arrays** - Homogeneous arrays of primitives, strings, and inline tables
+- **Strings** - Basic, literal, and multi-line variants
+- **Dates and times** - ISO 8601 (LocalDate, LocalDateTime, OffsetDateTime, etc.)
+
+##### Media Types
+
+- Produces: `application/toml`
+- Accepts: `application/toml`, `text/toml`
+
+TOML is included in `BasicUniversalConfig` by default, or can be used standalone via `BasicTomlConfig`.
+
+See [TOML Basics](/docs/topics/TomlSupport) for full documentation.
+
+### JSONL (JSON Lines) Support
+
+Juneau now includes built-in JSONL (JSON Lines / NDJSON) serialization and parsing support. JSONL is a text format where each line is a valid JSON value, widely used for LLM fine-tuning datasets, streaming AI inference, log aggregation, and bulk data pipelines.
+
+#### New Classes
+
+- **`JsonlSerializer`** - Serializes POJOs to JSONL format with one JSON value per line.
+- **`JsonlParser`** - Parses JSONL input into POJOs, reading one line at a time.
+- **`Jsonl`** - Convenience marshaller pairing `JsonlSerializer` and `JsonlParser`.
+
+##### Features
+
+- Each line is a compact JSON value; collections and arrays serialize one element per line.
+- Full round-trip parity with JSON for beans, maps, collections, scalars, and ObjectSwaps.
+- Supports streaming (Iterator, Iterable, Stream) for lazy serialization.
+- Date/time and Duration types serialized as ISO 8601 strings.
+
+```java
+// Serialize a list of beans to JSONL
+List<Person> people = List.of(new Person("Alice", 30), new Person("Bob", 25));
+String jsonl = Jsonl.of(people);
+// Produces:
+// {"name":"Alice","age":30}
+// {"name":"Bob","age":25}
+
+// Parse JSONL into a list of beans
+List<Person> parsed = Jsonl.to(jsonl, List.class, Person.class);
+```
+
+##### Media Types
+
+- Produces: `application/jsonl`
+- Accepts: `application/jsonl`, `application/x-ndjson`, `text/jsonl`
+
+JSONL is included in `BasicUniversalConfig` by default.
+
+See [JSONL Basics](/docs/topics/JsonlSupport) for full documentation.
+
+### Server-Sent Events (SSE) Support
+
+Juneau now ships with a native serializer / parser for the
+[WHATWG Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html)
+wire format (`text/event-stream`). SSE is the standard one-way push channel used by browsers
+(`EventSource`) and most LLM streaming APIs (OpenAI, Anthropic, etc.) for progress events, partial
+responses, and long-lived telemetry feeds.
+
+#### New Classes (in `org.apache.juneau.marshall.sse`)
+
+- **`SseEvent`** — bean carrying the four spec fields (`event`, `data`, `id`, `retry`). The `data`
+ payload is a **pre-serialized** string — callers serialize their typed payload through their
+ marshaller of choice (e.g. `Json.of(...)`) and assign the resulting string.
+- **`SseSerializer`** extends `WriterSerializer` — produces `text/event-stream`. Accepts a single
+ `SseEvent`, an `Iterable`, a `Stream`, or an `SseEvent[]` array. Calls `Writer.flush()` **after
+ every event** so events are pushed to the client immediately (essential for server-push and AI
+ streaming).
+- **`SseParser`** extends `ReaderParser` — eager parse of a closed `text/event-stream` into
+ `List<SseEvent>`, `SseEvent[]`, or a single `SseEvent`.
+- **`SseEventReader`** — public `Iterator<SseEvent>` + `Closeable` for line-driven streaming over a
+ caller-owned `Reader`. The intended API for long-lived SSE streams that outlive the standard
+ parser-session lifecycle.
+- **`Sse`** marshaller (in `org.apache.juneau.marshaller`) — convenience pairing of
+ `SseSerializer` and `SseParser` with `of(...)` / `to(...)` shortcuts.
+
+##### Features
+
+- Full WHATWG parse-algorithm compliance: all three line terminators (`CR`, `LF`, `CR LF`),
+ one-time UTF-8 BOM strip, `field:value` field splitting with optional-space stripping after the
+ colon, multi-line `data:` concatenation, comment-line (`: heartbeat`) skipping, and the
+ spec-defined ignore rules for `id` containing U+0000 and non-digit `retry` values.
+- Per-event `Writer.flush()` inside `SseSerializerSession.doSerialize(...)` so SSE actually works
+ as a push channel (without it, events would buffer and only land at end-of-response).
+- `SseSerializer.writeComment(Writer, String)` helper for emitting SSE heartbeat / keep-alive
+ comment lines on long-lived connections.
+
+##### Example
+
+```java
+// Serialize a list of events to SSE wire format
+List<SseEvent> events = List.of(
+ new SseEvent("progress", "step 1").setId("1"),
+ new SseEvent("progress", "step 2").setId("2")
+);
+String wire = Sse.of(events);
+// Produces:
+// event: progress
+// data: step 1
+// id: 1
+//
+// event: progress
+// data: step 2
+// id: 2
+//
+
+// Parse SSE wire format back into a list of events
+List<SseEvent> parsed = Sse.to(wire, List.class, SseEvent.class);
+```
+
+##### REST integration
+
+SSE is not added to `BasicUniversalConfig` (it's a streaming, one-way push format with different
+semantics from the regular content-negotiation marshallers). Register it explicitly per-endpoint:
+
+```java
+@Rest(serializers = SseSerializer.class)
+public class MyStreamResource {
+ @RestGet("/events")
+ public List<SseEvent> stream() {
+ return List.of(
+ new SseEvent("progress", "step 1"),
+ new SseEvent("progress", "step 2")
+ );
+ }
+}
+```
+
+##### Media Types
+
+- Produces / accepts: `text/event-stream`
+
+See [SSE Basics](/docs/topics/Sse) for full documentation.
+
+### BSON (Binary JSON) Support
+
+Juneau now includes built-in BSON (Binary JSON) serialization and parsing support, implemented without external library dependencies. BSON is the binary serialization format used by MongoDB, extending JSON with typed integers (int32/int64), native datetime, decimal128, and binary data.
+
+#### New Classes
+
+- **`BsonSerializer`** - Serializes POJOs to BSON format (little-endian, length-prefixed documents).
+- **`BsonParser`** - Parses BSON input into POJOs.
+- **`Bson`** - Convenience marshaller pairing `BsonSerializer` and `BsonParser`.
+
+##### Features
+
+- Full Java type mapping: beans/maps → documents, collections/arrays → BSON arrays, `byte[]` → binary (0x05), dates → datetime (0x09) or ISO 8601 string, `BigDecimal`/`BigInteger` → decimal128 (0x13).
+- REST integration: `RestClient.bson()`, `RestRequest.bson()`, `MockRestClient.bson()`, `MockRestRequest.bson()`.
+- Annotations: `@Bson`, `@BsonConfig` (addBeanTypes, writeDatesAsDatetime).
+- Round-trip parity with JSON for all standard data types; included in `RoundTripTest_Base`.
+
+```java
+// Serialize a bean to BSON
+Person p = new Person("Alice", 30);
+byte[] bson = Bson.of(p);
+
+// Parse BSON into a bean
+Person parsed = Bson.to(bson, Person.class);
+
+// REST client with BSON
+RestClient client = RestClient.create().uri("http://localhost:8080").bson().build();
+byte[] response = client.get("/api/person/1").bson().run().body();
+```
+
+##### Media Types
+
+- Produces: `application/bson`
+- Accepts: `application/bson`
+
+BSON is included in `BasicUniversalConfig` by default.
+
+See [BSON Basics](/docs/topics/Bson) for full documentation.
+
+### CBOR (Concise Binary Object Representation) Support
+
+Juneau now includes built-in CBOR per [RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html) serialization and parsing support, implemented without external library dependencies. CBOR is a compact binary format designed for extremely small code size and message size, widely used in IoT, constrained environments, COSE (CBOR Object Signing and Encryption), and WebAuthn.
+
+#### New Classes
+
+- **`CborSerializer`** - Serializes POJOs to CBOR format (RFC 8949, compact integer encoding, definite-length arrays/maps).
+- **`CborParser`** - Parses CBOR input into POJOs.
+- **`Cbor`** - Convenience marshaller pairing `CborSerializer` and `CborParser`.
+
+##### Features
+
+- Full Java type mapping: beans/maps → CBOR maps (major type 5), collections/arrays → CBOR arrays (major type 4), strings → text strings (major type 3), integers → compact encoding (major types 0/1), floats → IEEE 754 (0xFA/0xFB), `byte[]` → byte strings (major type 2).
+- SpacedHex and Base64 variants for text-friendly output (debugging, testing).
+- REST integration: CBOR included in `BasicUniversalConfig` and `RestClient.universal()`.
+- Annotations: `@Cbor`, `@CborConfig` (addBeanTypesCbor, useTags).
+- Round-trip parity with JSON for standard data types.
+
+```java
+// Serialize a bean to CBOR
+Person p = new Person("Alice", 30);
+byte[] cbor = Cbor.of(p);
+
+// Parse CBOR into a bean
+Person parsed = Cbor.to(cbor, Person.class);
+```
+
+##### Media Types
+
+- Produces: `application/cbor`
+- Accepts: `application/cbor`
+
+CBOR is included in `BasicUniversalConfig` by default.
+
+See [CBOR Basics](/docs/topics/Cbor) for full documentation.
+
+### HOCON Parser Enhancements
+
+The `HoconParser` now supports additional HOCON features for configuration files:
+
+- **`+=` array append** - `list += item` appends to an existing array or creates `[item]` if the key does not exist
+- **Self-referential substitution** - `path = ${path}"/bin"` appends to the previous value (e.g. `/usr` → `/usr/bin`)
+- **Value concatenation** - Adjacent values concatenate: `path = /usr "/local"` → `"/usr/local"`
+- **Array concatenation** - `[1, 2] [3, 4]` → `[1, 2, 3, 4]`
+- **Object concatenation** - `{a: 1} {b: 2}` → `{a: 1, b: 2}`
+
+See [HOCON Basics](/docs/topics/HoconSupport) for full documentation.
+
+### JCS (JSON Canonicalization Scheme) Support
+
+Juneau now includes built-in JCS (JSON Canonicalization Scheme) per [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785). JCS produces a deterministic, byte-for-byte canonical representation of JSON data, enabling reliable cryptographic operations such as hashing and digital signing.
+
+#### New Classes
+
+- **`JcsSerializer`** - Serializes POJOs to canonical JSON with sorted properties, ECMAScript number formatting, and no whitespace.
+- **`Jcs`** - Convenience marshaller pairing `JcsSerializer` and `JsonParser` (JCS output is valid JSON).
+
+##### Features
+
+- **Deterministic output** - Same input always produces identical output for hashing/signing.
+- **No whitespace** - Compact single-line output.
+- **Sorted properties** - UTF-16 code unit order, applied recursively to nested objects.
+- **ECMAScript number serialization** - Shortest round-trip representation per ECMA-262.
+- **Full round-trip** - Use `JsonParser` to parse JCS output.
+
+```java
+// Serialize to canonical JSON
+Person alice = new Person("Alice", 30);
+String canonical = Jcs.of(alice);
+// Produces: {"age":30,"name":"Alice"}
+
+// Parse canonical JSON (same as regular JSON)
+Person parsed = Jcs.to(canonical, Person.class);
+```
+
+##### Limitations
+
+- **NaN and Infinity** - Rejected; throws `SerializeException`.
+- **Lone surrogates** - Strings with unpaired UTF-16 surrogates throw `SerializeException`.
+- **Media type** - Output is `application/json`; JCS does not define a separate media type.
+
+JCS is included in `BasicUniversalConfig` by default.
+
+See [JCS Basics](/docs/topics/Jcs) for full documentation.
+
+### JsonSerializer/JsonParser and Json5Serializer/Json5Parser Separation
+
+`JsonSerializer` and `JsonParser` now strictly enforce RFC 8259 JSON (double-quoted strings only). Single-quoted JSON, comments, trailing commas, and unquoted keys are rejected by `JsonParser`.
+
+For JSON5 syntax (single quotes, comments, trailing commas, unquoted keys), use `Json5Serializer` and `Json5Parser` instead.
+
+#### Changes
+
+- **`JsonParser`** - Strict RFC 8259 only; single-quoted strings throw `ParseException`. Use `Json5Parser` for JSON5 input.
+- **`JsonSerializer`** - Produces strict JSON by default. Use `Json5Serializer` for JSON5-style output (single quotes, etc.).
+- **Removed** - `JsonSerializer.json5()` and `JsonParser.Strict`; use `Json5Serializer` and `Json5Parser` directly.
+- **`BeanContext.beanToStringSerializer`** - Now uses `Json5Serializer` for `toString()` output (more human-readable).
+- **ComboRoundTripTest** - Framework now distinguishes `json` vs `json5`; use `json()`/`json5()` builder methods on `ComboRoundTrip_Tester` accordingly.
+
+See [JSON Basics](/docs/topics/JsonSupport), [JSON Parsers](/docs/topics/JsonParsers), and [JSON 5](/docs/topics/Json5) for full documentation.
+
+### Sorted Bean Properties by Default; `@Bean(unsorted)` Opt-Out
+
+Bean properties are now serialized in **alphabetical order by default** across all serializers. Previously, the default was natural JVM order and opting in to sorted output required explicit configuration.
+
+**New API:**
+
+- **`@Bean(unsorted=true)`** — Opts a specific bean class out of the default sorted order, using natural JVM property order instead.
+- **`BeanContext.Builder.unsortedProperties()`** — Disables sorted properties globally for all beans.
+- **`BeanContext.Builder.unsortedProperties(Class<?>...on)`** — Disables sorted properties for specific bean classes only.
+- **`BeanFilter.Builder.unsortedProperties()`** — Per-bean-filter opt-out of sorted properties.
+- **`BeanFilter.isUnsortedProperties()`** — Returns `true` if this bean filter opts out of sorting.
+- **`BeanSession.isUnsortedProperties()`** — Session-level accessor for the unsorted flag.
+
+**Migration:**
+
+```java fragment
+// Before: opt in to sorted output
+@Bean(sort=true)
+public class MyBean { ... }
+
+// After: sorted is the default, opt out with unsorted=true
+@Bean(unsorted=true)
+public class MyBean { ... } // opts out of default sorted behavior
+
+// Before: global sorted flag required explicit opt-in
+JsonSerializer s = JsonSerializer.create().sortProperties().build();
+
+// After: sorted by default; use unsortedProperties() to opt out
+JsonSerializer s = JsonSerializer.create().unsortedProperties().build();
+```
+
+**Removed API:**
+
+- `@Bean(sort)` — Removed. Use `@Bean(unsorted=true)` with inverted semantics.
+- `BeanContext.Builder.sortProperties()` / `sortProperties(boolean)` — Removed. Use `unsortedProperties()`.
+- `BeanContext.Builder.sortProperties(Class<?>...on)` — Removed. Use `unsortedProperties(Class<?>...on)`.
+- `BeanContextable.Builder.sortProperties()` / `sortProperties(Class<?>...on)` — Removed. Use `unsortedProperties()` / `unsortedProperties(Class<?>...on)`.
+- `BeanFilter.Builder.sortProperties()` / `sortProperties(boolean)` — Removed. Use `unsortedProperties()`.
+- `BeanFilter.isSortProperties()` / `BeanSession.isSortProperties()` — Removed. Use `isUnsortedProperties()`.
+- `BeanMeta.isSortProperties()` — Removed. Use `isUnsortedProperties()`.
+- All serializer/parser builder `sortProperties()` / `sortProperties(Class<?>...on)` overrides — Removed.
+
+**Internal rename:**
+
+- `BeanMeta.sortProperties` field — Renamed to `unsortedProperties` with inverted semantics. The field now stores `true` when the bean opts out of default sorted order, rather than `true` when sorted is explicitly enabled.
+
+### `@BeanIgnore(ignoreAccessors=true)` on fields
+
+`@BeanIgnore` gains **`ignoreAccessors`** (default **`false`**) for use on **fields**. When **`ignoreAccessors = true`**, `BeanMeta` also excludes the matching **JavaBean accessor pair** (`getX`/`setX`, or `isX` for booleans) from bean property discovery, so the property is omitted from serialization, parsing, and `BeanMap` metadata while accessors can remain for other frameworks.
+
+The default **`false`** preserves existing behavior: `@BeanIgnore` on a field only excludes the field from field-based discovery; accessors can still expose the property (for example when `beanFieldVisibility` is `NONE` and only methods are considered).
+
+- Applies to Juneau’s default introspection and to **`BeanContext.Builder.useJavaBeanIntrospector()`** (`BeanInfo` / `PropertyDescriptor` discovery).
+- Property names follow the same rules as field discovery: `@Beanp` / `@Name` on the field, if present, otherwise the configured `PropertyNamer` on the field name.
+
+See [@BeanIgnore Annotation](/docs/topics/BeanIgnoreAnnotation).
+
+### Protobuf Text Format Support
+
+Juneau now includes built-in Protobuf Text Format serialization and parsing support, implemented without external library dependencies. This is the **human-readable** text format (not the binary wire format), suitable for configuration, debugging, and interop with tools that use the text format.
+
+#### New Classes
+
+- **`PrototextSerializer`** - Serializes POJOs to Protobuf Text Format.
+- **`PrototextParser`** - Parses Protobuf Text Format into POJOs.
+- **`Prototext`** - Convenience marshaller pairing `PrototextSerializer` and `PrototextParser`.
+- **`BasicPrototextConfig`** - REST configuration interface for Prototext-only resources.
+
+##### Features
+
+- No `.proto` schema files required—Java bean metadata provides type information.
+- Supports beans, maps, collections, scalars, repeated fields, and nested messages.
+- Date/time and Duration types serialized as ISO 8601 strings.
+- Annotations: `@Prototext` (comment), `@PrototextConfig` (useListSyntaxForBeans, useColonForMessages).
+
+```java
+// Serialize a bean to Protobuf Text Format
+Person p = new Person("Alice", 30);
+String proto = Prototext.of(p);
+// Produces: name: "Alice"
+// age: 30
+
+// Parse Protobuf Text Format into a bean
+Person parsed = Prototext.to(proto, Person.class);
+```
+
+##### Media Types
+
+- Produces: `text/protobuf`
+- Accepts: `text/protobuf`, `text/x-protobuf`
+
+Prototext is included in `BasicUniversalConfig` by default, or can be used standalone via `BasicPrototextConfig`.
+
+See [Protobuf Basics](/docs/topics/Protobuf) for full documentation.
+
+### Full CSV Parser Support
+
+The `CsvParser` is now fully implemented. Previously, parsing always threw `"Not implemented."`.
+The parser supports the same range of target types as other Juneau parsers.
+
+#### New Class
+
+- **`CsvReader`** - Low-level RFC 4180-compliant CSV reader used internally by `CsvParserSession`.
+
+##### Features
+
+The parser reads a header row to discover column names, then maps each subsequent data row to the
+target type:
+
+```java
+String csv = "name,age\nAlice,30\nBob,25\n";
+
+// Parse into a list of beans
+List<Person> people = CsvParser.DEFAULT.parse(csv, List.class, Person.class);
+
+// Parse into a list of maps
+List<Map<String,String>> rows = CsvParser.DEFAULT.parse(csv, List.class, Map.class);
+
+// Parse a single bean
+Person p = CsvParser.DEFAULT.parse("name,age\nAlice,30\n", Person.class);
+```
+
+Supported target types:
+
+| Target Type | Behavior |
+|---|---|
+| `Collection<Bean>` / `Bean[]` | Header row → property names; each data row → one bean. |
+| `Collection<Map>` / `Map[]` | Header row → map keys; each data row → one map. |
+| `Collection<SimpleType>` / `SimpleType[]` | `value` column → each row coerced to element type. |
+| Single `Bean` | Header row + one data row → one bean. |
+| Single `Map` | Header row + one data row → one map. |
+| `Object` | Returns a `JsonList` of `JsonMap` entries. |
+
+Type coercion converts string cell values to the target property type, including primitives, enums,
+date/time types, and any type supported by Juneau's `convertToType()`.
+
+The unquoted literal `null` in a cell is parsed as Java `null`.
+
+### CSV Serializer Fixes
+
+- **RFC 4180 Quote Escaping**: `CsvWriter.writeEntry()` now correctly escapes embedded double-quote
+ characters by doubling them (`"` → `""`), as required by RFC 4180. Previously, values containing
+ embedded quotes were written with unescaped quotes, producing invalid CSV.
+- **DynaBean Support**: The `CsvSerializer` now serializes DynaBeans (dynamic beans not directly
+ identified as beans by `ClassMeta.isBean()`) by attempting `toBeanMap()` on each entry.
+- **Round-Trip Tests**: `CsvSerializer` and `CsvParser` are now included in the `RoundTripTest_Base`
+ framework (as tester #18), skipping inputs that are not flat collections.
+
+See [CSV Basics](/docs/topics/CsvSupport) for full documentation.
+
+### Native Markdown Support
+
+Juneau now includes built-in Markdown serialization and parsing support, implemented entirely without external library
+dependencies. Markdown output is optimized for both human readability and LLM/AI consumption, with fragment mode
+(tables, lists) and document mode (headings + tables) for nested structures.
+
+#### New Classes
+
+- **`MarkdownSerializer`** - Serializes POJOs to Markdown fragment output (tables, bulleted lists).
+- **`MarkdownDocSerializer`** - Extends `MarkdownSerializer` for full documents with heading-based structure.
+- **`MarkdownParser`** - Parses Markdown tables and bulleted lists back into POJOs.
+- **`MarkdownDocParser`** - Parses Markdown documents with headings into nested bean structure.
+- **`Markdown`** - Convenience marshaller pairing `MarkdownSerializer` and `MarkdownParser`.
+- **`MarkdownDoc`** - Convenience marshaller pairing `MarkdownDocSerializer` and `MarkdownDocParser`.
+
+##### Features
+
+- **Fragment mode**: Beans and maps as key-value tables; uniform collections as multi-column tables; simple collections as bulleted lists.
+- **Document mode**: Nested beans under sub-headings (H2, H3, etc.); customizable title, header, and footer content.
+- **Inline complex values**: Nested beans and collections in table cells rendered as backtick-wrapped JSON5.
+
+##### Media Types
+
+- Produces: `text/markdown`
+- Accepts: `text/markdown`, `text/x-markdown`
+
+Markdown is included in `BasicUniversalConfig` and `RestClient.universal()` by default.
+
+See [Markdown Basics](/docs/topics/Markdown) for full documentation.
+
+### Native YAML Support
+
+Juneau now includes built-in YAML serialization and parsing support, implemented entirely without external library
+dependencies. Like all other Juneau serializers and parsers, the YAML implementation uses a highly-efficient
+state machine for both serialization and parsing, converting directly between POJOs and YAML without intermediate
+DOM objects.
+
+#### New Classes
+
+- **`YamlSerializer`** - Serializes POJOs to block-style YAML output.
+- **`YamlParser`** - Parses YAML input into POJOs using an indentation-aware state machine.
+- **`YamlWriter`** - Specialized writer for YAML output with key quoting and escape handling.
+- **`Yaml`** - Convenience marshaller pairing `YamlSerializer` and `YamlParser`.
+- **`@YamlConfig`** - Annotation for applying YAML configuration to REST resources.
+- **`BasicYamlConfig`** - REST configuration interface for YAML-only resources.
+
+##### Features
+
+The YAML serializer produces clean, human-readable block-style output:
+
+```java
+Person person = new Person("John Smith", 30, List.of(
+ new Address("100 Main St", "Anytown", "NY", 12345)
+));
+
+String yaml = Yaml.of(person);
+```
+
+Produces:
+
+```yaml
+name: John Smith
+age: 30
+addresses:
+ - street: 100 Main St
+ city: Anytown
+ state: NY
+ zip: 12345
+```
+
+The parser supports the full range of YAML constructs including block/flow mappings and sequences,
+quoted and plain scalars, literal and folded block scalars, comments, document markers, and all standard
+YAML scalar types (null, booleans, numbers).
+
+##### Media Types
+
+- Produces: `application/yaml`
+- Accepts: `application/yaml`, `text/yaml`
+
+##### REST Integration
+
+YAML is included in `BasicUniversalConfig` by default, or can be used standalone via `BasicYamlConfig`.
+
+See [YAML Basics](/docs/topics/YamlSupport) for full documentation.
+
+##### YAML parser buffer-underflow fix on large documents
+- Fixed a latent `java.io.IOException: Buffer underflow.` thrown by `YamlParser` when round-tripping
+ larger YAML documents whose deeply-indented lines crossed the parser's internal 1024-char read
+ buffer. The shared `org.apache.juneau.marshall.parser.ParserReader` had a hard-coded 10-char unread
+ lookback budget on the unmarked-refill path, but the YAML parser's indent handling
+ (`skipBlanksAndCountIndent` + `unreadSpaces(...)`, plus similar patterns in `parseBlockScalar`
+ and `isNullBlockValue`) routinely needs to unread an entire line's leading indent at once —
+ 12-20+ chars for deeply-nested OpenAPI 3.1 documents. When the indent read happened to cross
+ the buffer boundary, only the last 10 of those chars survived the refill and the rest of the
+ unreads underflowed.
+- The fix widens that lookback budget from 10 to 256 chars (a new
+ `ParserReader.UNMARKED_LOOKBACK_CHARS` constant). 256 covers any realistic YAML indent depth
+ (128 levels at 2-space indent) while still leaving 768 chars per refill for forward-progress
+ reads. The change has zero API surface — every parser benefits via the shared seam — and full
+ utest suite (124k+ tests) runs at the same wall-clock as before.
+- Acceptance test: the full `BasicRestServlet` OpenAPI 3.1 document (six api-docs URLs + full
+ components.schemas set) now round-trips JSON → YAML → JSON cleanly. Previously
+ `OpenApiYamlRoundTrip_Test#c01` had to scope to a single `OpenApiMixin` mount via
+ `noInherit={"mixins"}` to dodge the underflow; that workaround has been removed.
+
+### Direct Iterator/Iterable/Stream/Enumeration Serialization
+
+`Iterator`, `Iterable` (non-Collection), `Enumeration`, and `java.util.stream.Stream` types are now serialized directly as arrays by all serializers without requiring intermediate `List` materialization.
+
+Previously, `Iterator` and `Enumeration` objects were handled via `IteratorSwap` and `EnumerationSwap` which drained the entire sequence into a `LinkedList` before serialization began. `Iterable` (non-Collection) and `Stream` had no serialization support at all.
+
+**Lazy serialization** (no intermediate List):
+- JSON, XML, UON, URL Encoding, and OpenAPI serializers write elements directly to the output one at a time via `forEachStreamableEntry()`.
+
+**Materialized serialization** (collected to List when the format requires it):
+- MsgPack (protocol requires array size in header), HTML (inspects elements for table layout), and CSV (inspects first element for column headers) collect to a List internally.
+
+#### New API
+
+- **`ClassMeta`**: New methods `isIterator()`, `isIterable()`, `isStream()`, `isStreamable()` for detecting these types.
+- **`ClassMeta`**: New `Category` enum values `ITERATOR`, `ITERABLE`, `STREAM`.
+- **`SerializerSession`**: New methods `forEachStreamableEntry()` and `toListFromStreamable()`.
+
+##### Example
+
+```java
+// Iterators are now serialized directly - no swap needed
+Iterator<String> iterator = List.of("foo", "bar", "baz").iterator();
+String json = Json5Serializer.DEFAULT.serialize(iterator);
+// Produces: ['foo','bar','baz']
+
+// Streams work the same way
+Stream<Integer> stream = Stream.of(1, 2, 3);
+String json2 = Json5Serializer.DEFAULT.serialize(stream);
+// Produces: [1,2,3]
+
+// Non-Collection Iterables are also supported
+Iterable<String> iterable = () -> List.of("a", "b").iterator();
+String json3 = Json5Serializer.DEFAULT.serialize(iterable);
+// Produces: ['a','b']
+```
+
+##### Removals
+
+- `IteratorSwap` - Removed. Iterators are now handled natively.
+- `EnumerationSwap` - Removed. Enumerations are now handled natively.
+
+##### POJO Categories Update
+
+The POJO categories table has been updated. `Iterator`, `Iterable`, `Enumeration`, and `Stream` are no longer in the "swapped objects" category (group 4b). They are now in a new group (2c) alongside Collections and arrays as natively serializable sequence types.
+
+### Large-Dataset Streaming APIs
+
+New interfaces and annotations enable streaming serialization and parsing of arbitrarily large
+datasets without loading all elements into memory. Every serializer and parser format supports
+these APIs transparently.
+
+#### New Classes
+
+- **`BeanSupplier<T>`** — Serialization-side streaming interface (extends `Iterable<T>`).
+ Provides beans lazily from a cursor or query via `begin()`, `iterator()`, `onError()`, and `complete()` lifecycle methods.
+- **`BeanConsumer<T>`** — Parse-side streaming interface (extends `ThrowingConsumer<T>`).
+ Receives beans one-at-a-time from any parser for insertion or processing, with the same lifecycle methods.
+- **`BeanChannel<T>`** — Combines `BeanSupplier` and `BeanConsumer` for round-trip marshalling
+ on a single property. The same object drives both serialization and parsing.
+- **`ListBeanChannel<T>`** — Built-in in-memory `BeanChannel` backed by an `ArrayList`, useful
+ for testing and small in-memory datasets.
+- **`BeanFactory<T>`** — Universal `@FunctionalInterface` for creating instances of any type,
+ enabling integration with dependency-injection frameworks such as Spring.
+
+##### New Annotations
+
+- **`@Bean(factory=X.class)`** — Specifies the `BeanFactory` class used to instantiate a type
+ during parsing. The factory is resolved from the `BeanStore` (e.g. a Spring `ApplicationContext`).
+- **`@Beanp(factory=X.class)`** — Property-level factory for a specific bean property value.
+- **`@Beanp(elementType=Y.class)`** — Declares the element type for generic streaming properties
+ (`Stream<Y>`, `BeanSupplier<Y>`, `BeanConsumer<Y>`, `BeanChannel<Y>`) to overcome Java type
+ erasure. Also supports narrowing to concrete implementation types.
+
+##### New API
+
+- **`BeanContext.Builder.beanStore(BeanStore)`** — Wires a `BeanStore` (e.g. `SpringBeanStore`)
+ into the marshalling context so `@Bean(factory=)` factories can be resolved from a DI container.
+- **`ParserSession.parseToBeanConsumer(Object, BeanConsumer<T>, Class<T>)`** — Parses an input
+ source directly into a `BeanConsumer`, driving the full lifecycle automatically.
+
+##### Lifecycle
+
+All three interfaces share the same three-phase lifecycle: `begin()` for setup, the transfer
+method (`iterator()` or `acceptThrows()`) for element-by-element data flow, `onError()` for
+error handling, and `complete()` for cleanup. `complete()` is always called regardless of
+whether an error occurred.
+
+##### Key Behaviors
+
+- **Direction validation**: using a `BeanConsumer` during serialization or a `BeanSupplier`
+ during parsing throws an informative exception recommending `BeanChannel` for bidirectional use.
+- **Getter-only `BeanConsumer` properties**: parsers reuse a `BeanConsumer` exposed via a getter
+ (no setter required), calling `acceptThrows()` on the existing instance — matching the existing
+ behavior for mutable `Collection` properties.
+- **`Supplier<T>` unwrapping**: the standard JDK `Supplier<T>` (not `BeanSupplier`) is
+ transparently unwrapped by serializers as a single lazy value, recursively up to depth 10.
+- **`Stream<T>` auto-close**: `java.util.stream.Stream` objects used during serialization are
+ automatically closed via try-with-resources after iteration completes.
+
+##### Example
+
+```java
+// Serialize a large table from a JDBC cursor — no memory materialization.
+@Bean(factory=EmployeeSupplier.Factory.class)
+public class EmployeeSupplier implements BeanSupplier<Employee> {
+ private Connection conn;
+ private ResultSet rs;
+
+ @Override public void begin() throws Exception {
+ conn = dataSource.getConnection();
+ rs = conn.prepareStatement("SELECT * FROM employee").executeQuery();
+ }
+ @Override public Iterator<Employee> iterator() { return Employee.cursorIterator(rs); }
+ @Override public void complete() throws Exception { rs.close(); conn.close(); }
+}
+
+// Bulk-insert with batch commits — no memory materialization.
+@Bean(factory=EmployeeConsumer.Factory.class)
+public class EmployeeConsumer implements BeanConsumer<Employee> {
+ private Connection conn;
+ private PreparedStatement stmt;
+ private int count;
+
+ @Override public void begin() throws Exception {
+ conn = dataSource.getConnection();
+ conn.setAutoCommit(false);
+ stmt = conn.prepareStatement("INSERT INTO employee (name, dept) VALUES (?,?)");
+ }
+ @Override public void acceptThrows(Employee e) throws Exception {
+ stmt.setString(1, e.getName()); stmt.setString(2, e.getDepartment());
+ stmt.executeUpdate();
+ if (++count % 500 == 0) conn.commit();
+ }
+ @Override public void onError(Exception e) throws Exception { conn.rollback(); throw e; }
+ @Override public void complete() throws Exception { conn.commit(); stmt.close(); conn.close(); }
+}
+```
+
+See [Large-Dataset Streaming](/docs/topics/LargeDatasetStreaming) for full documentation.
+
+### Java Records Support
+
+Java records are now fully supported for both serialization and parsing across all serializers and parsers.
+
+Records are treated as beans whose properties are derived from the record's components. The canonical constructor is
+automatically detected for parsing, so no `@Beanc` annotation is needed.
+
+```java
+public record Person(String name, int age) {}
+
+// Serialization
+String json = Json5Serializer.DEFAULT.serialize(new Person("John", 30));
+// Produces: {age:30,name:'John'}
+
+// Parsing
+Person p = JsonParser.DEFAULT.parse("{\"name\":\"John\",\"age\":30}", Person.class);
+```
+
+#### How It Works
+
+- **Property discovery**: Record component accessor methods (e.g. `name()` for a `name` component) are recognized as
+ getters without needing to follow the traditional `getX()` naming convention.
+- **Constructor detection**: The canonical constructor is automatically identified. During parsing, property values are
+ cached and then passed to the canonical constructor to create the record instance.
+- **Private fields**: Record component fields are automatically included as bean properties despite being `private final`.
+
+##### Annotation Support
+
+The following annotations work with records:
+
+- `@Bean(properties)` - Control property ordering (all components should be included).
+- `@Beanp(name)` - Rename properties.
+- `@Beanc` - Override the constructor used for parsing (e.g. to use a non-canonical constructor with defaults).
+- `@Bean(typeName)` / `@Bean(dictionary)` - Polymorphic record types.
+
+##### Limitations
+
+Because records are immutable and all components must be provided to the canonical constructor, the following
+are not supported during parsing:
+
+- `@BeanIgnore` on individual record components.
+- `@Bean(excludeProperties)` excluding record components.
+- `@Bean(readOnlyProperties)` / `@Beanp(ro)` on record components.
+
+These annotations will still affect serialization output. Use `@Beanc` with a non-canonical constructor to provide
+defaults for omitted components.
+
+##### Changes
+
+- **`BeanMeta`**: Added record detection logic in constructor discovery, accessor method recognition, and field
+ introspection. Records are exempt from `beansRequireDefaultConstructor` and `beansRequireSomeProperties` checks.
+- **`BeanPropertyMeta`**: Constructor-arg properties are now correctly marked as writable.
+- **`@BeanIgnore`**, **`@Bean`**, **`@Beanp`**, **`@Beanc`**: Javadocs updated with records-specific notes.
+
+See [Java Records Support](/docs/topics/JavaRecordsSupport) for full documentation.
+
+### Built-in Date/Time and Duration Serialization
+
+Date, time, and duration types are now serialized and parsed natively as ISO 8601 strings by all serializers and parsers without requiring `ObjectSwap` registration.
+
+Previously, types such as `Calendar`, `Date`, `Instant`, `ZonedDateTime`, and other `java.time.temporal.Temporal` subtypes required default `ObjectSwap` instances (e.g. `TemporalCalendarSwap`, `TemporalDateSwap`) to be serialized as ISO 8601 strings. Without a swap, they would be serialized as beans or via `toString()`.
+
+Now, these types are recognized as first-class citizens by Juneau's type system and are handled directly in every serializer and parser session.
+
+#### Supported Types
+
+All `java.time.temporal.Temporal` subtypes:
+- `Instant`, `ZonedDateTime`, `OffsetDateTime`, `LocalDateTime`, `LocalDate`, `LocalTime`, `OffsetTime`, `Year`, `YearMonth`
+
+Legacy date/time types:
+- `java.util.Calendar` (and `GregorianCalendar`)
+- `java.util.Date`
+- `javax.xml.datatype.XMLGregorianCalendar`
+
+Duration type:
+- `java.time.Duration` (serialized as ISO 8601 duration strings, e.g. `"PT1H30M"`)
+
+##### How It Works
+
+- **Serialization**: All serializers check `ClassMeta.isDateOrCalendarOrTemporal()` and `ClassMeta.isDuration()` *before* bean checks in their `serializeAnything()` dispatch. Matching objects are formatted via `Iso8601Utils.format()`.
+- **Parsing**: All parsers check the same conditions in their `parseAnything()` dispatch. ISO 8601 strings are converted back to the target type via `Iso8601Utils.parse()`.
+- **Type conversions**: `BeanSession.convertToMemberType()` and `SerializerSession.toString()` also use ISO 8601 formatting for these types, ensuring consistent behavior in map keys, bean properties, and other contexts.
+
+##### New API
+
+- **`ClassMeta`**: New `Category.DURATION` enum value for `java.time.Duration` classification.
+- **`ClassMeta`**: New method `isDuration()`.
+- **`Iso8601Utils`**: New utility class centralizing ISO 8601 formatting and parsing for all date/time and duration types.
+
+##### Swap Classes as Overrides
+
+The existing swap classes (`TemporalCalendarSwap`, `TemporalDateSwap`, `CalendarSwap`, `DateSwap`, `TemporalSwap`) remain available but now serve as **override mechanisms**. If you register one of these swaps, it will take precedence over the built-in behavior, allowing you to customize the output format (e.g. RFC 1123 instead of ISO 8601).
+
+```java
+// Default behavior - no swap needed, uses ISO 8601
+String json = JsonSerializer.DEFAULT.serialize(Calendar.getInstance());
+// Produces: "2026-02-28T12:00:00Z"
+
+// Override with a swap for a different format
+JsonSerializer s = JsonSerializer.create()
+ .swaps(TemporalCalendarSwap.Rfc1123DateTime.class)
+ .build();
+String json2 = s.serialize(Calendar.getInstance());
+// Produces: "Sat, 28 Feb 2026 12:00:00 GMT"
+
+// Duration serialization - no swap needed
+String json3 = JsonSerializer.DEFAULT.serialize(Duration.ofHours(1).plusMinutes(30));
+// Produces: "PT1H30M"
+```
+
+##### POJO Categories Update
+
+The POJO categories table has been updated. `Calendar`, `Date`, `Temporal` subtypes, `XMLGregorianCalendar`, and `Duration` are no longer in the "swapped objects" category (group 4a). They are now in a new group (2d) as natively serializable date/time types.
+
+See [POJO Categories](/docs/topics/PojoCategories) for the updated table.
+
+### RDF/THRIFT and RDF/PROTO Binary Format Support
+
+The `juneau-marshall-rdf` module now includes full support for RDF/THRIFT and RDF/PROTO binary serialization formats, enabling compact binary representation of RDF graphs for efficient storage and network transfer.
+
+#### New Classes
+
+- **`RdfThriftSerializer`** - Serializes POJOs to RDF/THRIFT binary format.
+- **`RdfThriftParser`** - Parses RDF/THRIFT binary input into POJOs.
+- **`RdfThrift`** - Convenience marshaller pairing `RdfThriftSerializer` and `RdfThriftParser`.
+- **`RdfProtoSerializer`** - Serializes POJOs to RDF/PROTO binary format.
+- **`RdfProtoParser`** - Parses RDF/PROTO binary input into POJOs.
+- **`RdfProto`** - Convenience marshaller pairing `RdfProtoSerializer` and `RdfProtoParser`.
+
+##### Features
+
+Both formats use Apache Jena for underlying RDF model handling, with compact binary encoding via Apache Thrift and Protocol Buffers respectively:
+
+```java
+Map<String,String> map = Map.of("foo", "bar");
+
+// RDF/THRIFT
+byte[] thriftBytes = RdfThrift.of(map);
+Map<String,String> restored = RdfThrift.to(thriftBytes, Map.class, String.class, String.class);
+
+// RDF/PROTO
+byte[] protoBytes = RdfProto.of(map);
+Map<String,String> restored2 = RdfProto.to(protoBytes, Map.class, String.class, String.class);
+```
+
+The binary formats are semantically equivalent to RDF/XML, RDF/JSON, and other RDF serializations—beans, maps, collections, and primitives are round-tripped correctly. Date/time types are parsed using built-in ISO 8601 support.
+
+##### Round-Trip Test Coverage
+
+RDF/XML, RDF/THRIFT, and RDF/PROTO are now included in the `RoundTripTest_Base` and `ComboRoundTrip_Tester` frameworks, validating round-trip correctness across a broad set of bean types, collections, and edge cases.
+
+See [RDF Basics](/docs/topics/Rdf) for full documentation.
+
+### ClassInfoTyped
+
+`ClassInfo.of(Class)` now returns `ClassInfoTyped<T>`, a generic subclass of `ClassInfo` that preserves the type parameter at compile time. The API remains identical—existing code continues to work without changes—but callers can now benefit from stronger typing:
+
+```java
+ClassInfoTyped<String> ci = ClassInfo.of(String.class);
+// ci carries type information for String
+```
+
+### Per-Session Property Overrides (Extended)
+
+`Session.Builder.property(String, Object)` and `properties(Map)` now dispatch to typed session-level setter methods across a broad range of serializer and parser session classes, enabling per-request override of serializer/parser configuration without rebuilding the context.
+
+Both short names (e.g. `"escapeSolidus"`) and fully-qualified names (e.g. `"JsonSerializerSession.escapeSolidus"`) are accepted as keys. `BasicConverter` handles type coercion so string values (e.g. `"true"`) are automatically converted to the expected target types.
+
+```java
+// Per-request JSON settings without rebuilding the serializer
+String json = JsonSerializer.DEFAULT
+ .createSession()
+ .property("escapeSolidus", true)
+ .property("addBeanTypes", true)
+ .build()
+ .serialize(myBean);
+
+// Per-request parser settings
+MyBean bean = JsonParser.DEFAULT
+ .createSession()
+ .property("trimStrings", true)
+ .property("validateEnd", true)
+ .build()
+ .parse(json, MyBean.class);
+```
+
+#### New Session-Level Properties
+
+| Session Class | New Property Keys |
+|---|---|
+| `ParserSession` | `trimStrings` |
+| `JsonSerializerSession` | `escapeSolidus` |
+| `JsonParserSession` | `validateEnd` |
+| `UonSerializerSession` | `encoding`, `paramFormat` |
+| `UonParserSession` | `validateEnd` |
+| `UrlEncodingSerializerSession` | `expandedParams` |
+| `UrlEncodingParserSession` | `expandedParams` |
+| `XmlSerializerSession` | `addNamespaceUrisToRoot`, `autoDetectNamespaces`, `defaultNamespace`, `enableNamespaces`, `textNodeDelimiter` |
+| `XmlParserSession` | `preserveRootElement`, `validating` |
+| `HtmlSerializerSession` | `addKeyValueTableHeaders`, `detectLabelParameters`, `detectLinksInStrings`, `labelParameter`, `uriAnchorText` |
+
+These complement the properties added in the initial release of this feature:
+- `BeanTraverseSession`: `initialDepth`
+- `SerializerSession`: `keepNullProperties`, `trimStrings`, `addBeanTypes`, `addRootType`, `sortCollections`, `sortMaps`, `trimEmptyCollections`, `trimEmptyMaps`
+- `WriterSerializerSession`: `maxIndent`, `quoteChar`
+- `CsvSerializerSession`: `byteArrayFormat`, `allowNestedStructures`, `nullValue`
+
+##### Bug Fixes — Context Bypass
+
+Several session classes were overriding accessor methods to return `ctx.isAddBeanTypes()` directly, bypassing the session-level field. This meant that per-session `addBeanTypes` overrides set via `property("addBeanTypes", true)` had no effect for those serializers. The affected classes now correctly delegate to `super.isAddBeanTypes()`:
+
+- `JsonSerializerSession`, `UonSerializerSession`, `XmlSerializerSession`, `HtmlSerializerSession`, `MsgPackSerializerSession`, `YamlSerializerSession`, `CborSerializerSession`, `BsonSerializerSession`, `RdfSerializerSession`, `RdfStreamSerializerSession`
+
+Similarly, `ParserSession.isTrimStrings()` now returns the session-level field (initialized from the context) instead of delegating to `ctx.isTrimStrings()`. And `UonSerializerSession.getQuoteChar()` no longer bypasses the `WriterSerializerSession` session field.
+
+### `MarshallUtils`: Static Serialization and Parsing Helpers
+
+A new utility class, `org.apache.juneau.marshaller.MarshallUtils`, provides a single point of static
+convenience methods for every serialization format supported by Juneau. All methods are designed to
+be used with a `static import` so call sites need no explicit marshaller reference.
+
+```java
+import static org.apache.juneau.marshaller.MarshallUtils.*;
+
+// Serialize to any text format — returns String
+String a = json(myBean);
+String b = json5(myBean);
+String c = xml(myBean);
+String d = html(myBean);
+String e = yaml(myBean);
+String f = csv(myBean);
+String g = toml(myBean);
+String h = hocon(myBean);
+
+// Serialize to binary formats — returns byte[]
+byte[] m = msgPack(myBean);
+byte[] n = cbor(myBean);
+byte[] o = bson(myBean);
+byte[] p = parquet(myBean);
+
+// Parse — same method names, overloaded by input type
+MyBean b1 = json(jsonString, MyBean.class);
+MyBean b2 = xml(xmlString, MyBean.class);
+MyBean b3 = msgPack(bytes, MyBean.class);
+List<MyBean> beans = parquet(bytes, MyBean.class);
+```
+
+#### Supported Formats
+
+| Method | Format | Output |
+|---|---|---|
+| `json(o)` | Standard JSON (RFC 8259) | `String` |
+| `json5(o)` | JSON5 | `String` |
+| `jsonl(o)` | JSON Lines (NDJSON) | `String` |
+| `jcs(o)` | Canonical JSON (RFC 8785) | `String` |
+| `hjson(o)` | HJSON | `String` |
+| `xml(o)` | XML | `String` |
+| `html(o)` | HTML | `String` |
+| `uon(o)` | URL-Encoded Object Notation | `String` |
+| `urlEncoding(o)` | URL Encoding | `String` |
+| `yaml(o)` | YAML | `String` |
+| `csv(o)` | CSV | `String` |
+| `openApi(o)` | OpenAPI | `String` |
+| `plainText(o)` | Plain text | `String` |
+| `markdown(o)` | Markdown (fragment) | `String` |
+| `markdownDoc(o)` | Markdown (document) | `String` |
+| `ini(o)` | INI / properties | `String` |
+| `toml(o)` | TOML | `String` |
+| `hocon(o)` | HOCON | `String` |
+| `proto(o)` | Protobuf Text Format | `String` |
+| `msgPack(o)` | MessagePack | `byte[]` |
+| `cbor(o)` | CBOR (RFC 8949) | `byte[]` |
+| `bson(o)` | BSON | `byte[]` |
+| `parquet(o)` | Apache Parquet | `byte[]` |
+
+##### Full Overload Set
+
+Every format method is overloaded across the following signatures:
+
+| Signature | Purpose |
+|---|---|
+| `format(Object)` | Serialize to `String` (text) or `byte[]` (binary) |
+| `format(Object, Object)` | Serialize to a `Writer`, `OutputStream`, `File`, or `StringBuilder` |
+| `format(Object, Class<T>)` | Parse from any input (`String`, `byte[]`, `File`, `Reader`, etc.) — throws `IOException` |
+| `format(Object, Type, Type...)` | Parse from any input with a parameterized type |
+| `format(String, Class<T>)` | Parse from `String` — wraps exceptions in unchecked runtime exceptions |
+| `format(String, Type, Type...)` | Parse from `String` with a parameterized type — unchecked |
+| `format(byte[], Class<T>)` | Parse from `byte[]` (binary formats) — unchecked |
+| `format(byte[], Type, Type...)` | Parse from `byte[]` with a parameterized type (binary formats) — unchecked |
+
+The checked overloads (`Object`-based input) propagate `ParseException` and `IOException`. The `String`- and
+`byte[]`-based overloads wrap checked exceptions in unchecked runtime exceptions so they can be used directly
+in lambdas and streams without `try/catch`.
+
+All methods are designed for `static import`; no explicit marshaller reference is needed at call sites.
+
+##### Also updated: `Jcs` and `Parquet` marshallers
+
+Both marshaller classes were updated to expose the full static API:
+
+- **`Jcs`** — Added `of(Object, Object)`, `to(Object, Class)`, `to(Object, Type, Type...)`,
+ `to(String, Type, Type...)` to match the standard char-marshaller contract.
+- **`Parquet`** — Added `of(Object, Object)`, `to(Object, Class)`, `to(Object, Type)`,
+ `to(byte[], Type, Type...)` (Parquet parse methods return `List<T>` since the format is
+ always collection-oriented).
+
+### `JsonMap` and `JsonList`: New `toX()` Serialization Methods
+
+The serialization helper methods on `JsonMap` and `JsonList` have been renamed from the `asX()` convention
+to the `toX()` convention and extended with additional JSON-flavor methods.
+
+Note: in the same release `JsonMap.toString()` / `JsonList.toString()` were retargeted from JSON5 to
+strict JSON as part of an earlier change (see the "Neutral `MarshalledMap` / `MarshalledList`" section above).
+That means `toJson()` (not `toJson5()`) is now the synonym for `toString()`, and the rows below have
+been updated to reflect the new mapping.
+
+#### `JsonMap`
+
+| New Method | Description |
+|---|---|
+| `toJson()` | Serializes to strict RFC 8259 JSON (double-quoted keys/strings). Synonym for `toString()`. |
+| `toJson5()` | Serializes to JSON5 — unquoted keys, single-quoted strings. |
+| `toJsonl()` | Serializes to JSON Lines. |
+| `toJcs()` | Serializes to Canonical JSON (RFC 8785). |
+| `toHjson()` | Serializes to HJSON. |
+| `toReadableJson5()` | Serializes to indented, human-readable JSON5. |
+| `toString(WriterSerializer)` | Serializes using any `WriterSerializer`. |
+
+##### `JsonList`
+
+| New Method | Description |
+|---|---|
+| `toJson()` | Serializes to strict RFC 8259 JSON. Synonym for `toString()`. |
+| `toJson5()` | Serializes to JSON5. |
+| `toJsonl()` | Serializes to JSON Lines. |
+| `toJcs()` | Serializes to Canonical JSON (RFC 8785). |
+| `toHjson()` | Serializes to HJSON. |
+| `toString(WriterSerializer)` | Serializes using any `WriterSerializer`. |
+
+##### Removed Methods
+
+The following `asX()` methods have been removed in favor of their `toX()` equivalents:
+
+| Removed | Replacement |
+|---|---|
+| `asJson()` | `toJson()` or `toString()` (now both strict JSON) |
+| `asString()` | `toString()` — but note `toString()` is now strict JSON. Callers that genuinely want the old JSON5 form should switch to `Json5Map.toString()` / `Json5List.toString()` (or call `toJson5()` on `JsonMap` / `JsonList`). |
+| `asReadableString()` *(JsonMap only)* | `toReadableJson5()` |
+| `asString(WriterSerializer)` | `toString(WriterSerializer)` |
+
+### `BasicBeanStore` precedence-aware affordances
+
+`BasicBeanStore` (in `org.apache.juneau.cp`) gained two new affordances that power the 10.0 `RestContext` precedence flip (see *juneau-rest-server* below). Both are opt-in; existing call sites that use only `parent(...)` and local `addBean(...)` calls keep their current behavior.
+
+- **`Builder.overridingParent(BasicBeanStore)`** — Registers a parent bean store that is consulted *before* local entries during `getBean(...)` / `getBeanSupplier(...)`. The classic `parent(...)` slot keeps its post-entry fallback role. Useful for bridging an outer scope (Spring `ApplicationContext`, parent-resource bootstrap, test harness) on top of a child bean store and having the outer scope win without mutating local entries.
+- **`addDefaultSupplier(Class, Supplier)`** (and a named variant) — Registers a fallback supplier that fires only after local entries and the regular parent chain have all returned empty. Stored in a separate map from regular entries, so resolution order is unambiguous and `addBean(...)` calls can override the default at any time without removing the fallback.
+
+Final resolve order on `BasicBeanStore` is:
+
+```text
+overridingParent.getBeanSupplier(type, name) // outer scope (e.g. Spring)
+ -> entries[type][name] // addBean(...) / @Bean
+ -> parent.getBeanSupplier(type, name) // regular fallback
+ -> defaults[type][name] // addDefaultSupplier(...)
+```
+
+`hasBean(...)` follows the same precedence; a new `hasDefaultSupplier(...)` accessor lets callers ask "is there a registered default for this type?" without forcing the supplier to fire.
+
+### Removal of Legacy `cp` Injection Classes
+
+The following classes in `org.apache.juneau.cp` have been removed as part of the migration to the `org.apache.juneau.commons.inject` package:
+
+- **`BasicBeanStore`** (was `org.apache.juneau.cp.BasicBeanStore`) — replaced by `org.apache.juneau.commons.inject.BasicBeanStore` (renamed from `BasicBeanStore`).
+- **`BeanCreator`** — replaced by `org.apache.juneau.commons.inject.BeanInstantiator`.
+- **`BeanBuilder`** — replaced by `org.apache.juneau.commons.inject.BeanInstantiator` (was the base class for domain fluent builders; replaced with native builder fields and `BeanInstantiator`).
+- **`BeanCreateMethodFinder`** — replaced by `BeanStore.createBeanFromMethod(...)`.
+- **`ContextBeanCreator`** is **not** removed — it solves a different problem (persistent `Context.Builder` holder for repeated annotation application) and is retained as a first-class API.
+
+**Migration guide:**
+
+| Legacy (`org.apache.juneau.cp`) | Replacement (`org.apache.juneau.commons.inject`) |
+|---|---|
+| `BasicBeanStore.create().build()` | `new BasicBeanStore()` |
+| `BasicBeanStore.INSTANCE` | `BasicBeanStore.INSTANCE` |
+| `BasicBeanStore.of(parent)` | `new BasicBeanStore(parent)` |
+| `BeanCreator.of(MyBean.class, store)` | `BeanInstantiator.of(MyBean.class, store)` |
+| `creator.arg(Type.class, value)` | `instantiator.addBean(Type.class, value)` |
+| `creator.type(Impl.class)` | `instantiator.type(Impl.class)` |
+| `creator.impl(instance)` | `instantiator.impl(instance)` |
+| `creator.run()` | `instantiator.run()` |
+| `creator.orElse(default)` | `instantiator.asOptional().orElse(default)` |
+| `new BeanCreateMethodFinder(type, obj, bs)` | `bs.createBeanFromMethod(type, obj, predicate)` |
+
+### `BeanInstantiator` Static Convenience Methods
+
+Three static factory helpers have been added to `BeanInstantiator` to eliminate verbose ternary patterns at common call sites:
+
+| Method | Equivalent |
+|---|---|
+| `BeanInstantiator.createOrNull(Class<T>)` | `beanType == null ? null : BeanInstantiator.of(beanType).fallback(() -> null).run()` |
+| `BeanInstantiator.optionalOf(Class<T>)` | `beanType == null ? Optional.empty() : BeanInstantiator.of(beanType).asOptional()` |
+| `BeanInstantiator.createOrDefault(Class<T>, T)` | `beanType == null ? defaultValue : BeanInstantiator.of(beanType).run()` |
+
+All three return `null` / `Optional.empty()` / the default when the class argument is `null`, avoiding null-checks at every call site. Typical usage:
+
+```java
+// Before:
+var listenerCls = ctx.getListener();
+listener = listenerCls == null ? null : BeanInstantiator.of(listenerCls).fallback(() -> null).run();
+
+// After:
+listener = BeanInstantiator.createOrNull(ctx.getListener());
+```
+
+### `CreatableBeanStore` Removed
+
+`CreatableBeanStore` (in `org.apache.juneau.commons.inject`) has been removed. It was an unused internal interface with no production callers. `WritableBeanStore` serves as the write-capable interface for the inject package.
+
+### `WritableBeanStore` → `BeanStore` in Builder Classes
+
+The following builder-class constructors and fields have been narrowed from `WritableBeanStore` to `BeanStore` because they only perform read operations (bean lookup) on the store; they do not add beans:
+
+- `EncoderSet.Builder`
+- `SerializerSet.Builder`
+- `ParserSet.Builder`
+- `ResponseProcessorList.Builder`
+- `RestOpArgList.Builder`
+- `RestMatcherList.Builder`
+- `RestGuardList.Builder`
+- `RestConverterList.Builder`
+- `RestOperations.Builder`
+- `RestChildren.Builder`
+
+Callers that previously passed a `WritableBeanStore` still compile without changes, since `WritableBeanStore` extends `BeanStore`.
+
+### JSR-330 Alignment + Spring-Lite Additions in `commons.inject`
+The `org.apache.juneau.commons.inject` package now presents a coherent injection story:
+
+> *Juneau supports JSR-330 (`jakarta.inject` / `javax.inject`) annotations as closely as our lightweight model allows, without taking a hard dependency on `jakarta.inject-api`. Where Spring conventions add value to the lightweight model, we adopt them under our own annotations.*
+
+All JSR/Spring annotation recognition is **by fully qualified name** (FQN), so applications can use `jakarta.inject.Inject`, `javax.inject.Inject`, `org.springframework.beans.factory.annotation.Autowired`, or `org.apache.juneau.commons.inject.Inject` interchangeably — Juneau picks them up transparently without a compile-time dependency.
+
+#### New annotations and types
+
+| Type | Purpose |
+|------|---------|
+| `@Inject` (Juneau-owned) | Marks constructor parameters, methods, or fields as injection points. JSR-330 / Spring equivalents (`jakarta.inject.Inject`, `javax.inject.Inject`, `@Autowired`) are recognized by FQN. |
+| `@Named` | Bean qualifier (moved from `org.apache.juneau.annotation.Named` — see migration row below). |
+| `@Qualifier` | Meta-annotation marker used to declare custom qualifier annotations. |
+| `@Singleton` | Scope marker. Semantics: a singleton is registered once in a `BeanStore` and reused; no JVM-wide singleton, no proxying. |
+| `@Primary` | Disambiguates when multiple candidates of the same type exist. Unqualified `BeanStore.getBean(type)` prefers a single `@Primary` candidate; multiple primaries throw `BeanCreationException`. |
+| `@Order(int)` and `@Bean#priority()` | Ordering for `BeanStore.getBeansOfType(Class)`. Lower values are higher precedence. When both are present, `@Order` wins. |
+| `@Configuration` | Marker for types that host `@Bean` declarations. Supports `imports = { ... }` for chaining configuration classes (deduped transitively). `@Bean` members declared on superclasses are inherited. |
+| `@PostConstruct` / `@PreDestroy` | Lifecycle hooks. JSR-250 `jakarta.annotation` / `javax.annotation` equivalents are recognized by FQN. |
+| `@Conditional(Class<? extends Condition>)` | Predicate-driven registration guard. |
+| `@ConditionalOnClass(String)` | Skip the annotated configuration / `@Bean` when a class is absent from the classpath. |
+| `@ConditionalOnMissingBean(Class, name)` | Skip when a matching bean is already registered. |
+| `@ConditionalOnProperty(name, havingValue, matchIfMissing)` | Skip / register based on a `Settings` property. |
+| `Provider<T>` | Single-method `T get()` interface. Parameters and fields typed as `Provider<T>` (Juneau / `jakarta.inject` / `javax.inject`) are resolved via a `java.lang.reflect.Proxy` over a `Supplier<T>` from the bean store. |
+| `Condition` / `ConditionContext` | SPI for custom `@Conditional` predicates with access to the bean store, settings facade, classloader, and annotated element. |
+| `JsrSupport` | Single utility class centralizing recognized FQN constants (`JAKARTA_INJECT`, `SPRING_AUTOWIRED`, etc.) and matcher helpers. |
+
+##### `@Configuration` registration
+
+`WritableBeanStore.registerConfiguration(Class<?>)` and `registerConfigurations(Class<?>...)` register a `@Configuration` class with the store:
+
+- Static and non-static `@Bean` fields and methods are discovered.
+- `imports` are processed recursively and deduplicated; the same class registered twice (directly or transitively) is processed only once per bean store.
+- `@Bean` members declared on superclasses of the configuration type are inherited; the superclass is processed before the subclass so subclass `@Bean` methods can depend on superclass beans.
+- Duplicate `(type, name)` registrations within the same store throw `BeanCreationException`.
+- `@Conditional` annotations on the configuration class skip the entire class (and any imports it would have pulled in if the configuration class itself is conditional); on individual `@Bean` members they skip only that member.
+
+##### Bean lifecycle: `AutoCloseable`
+
+`WritableBeanStore` now extends `AutoCloseable`. Calling `close()`:
+
+- Invokes `@PreDestroy` methods on all beans that were resolved through `getBean(...)` / `getBeansOfType(...)`, in LIFO order (most-recently resolved first).
+- Aggregates per-bean destroy failures as suppressed exceptions on a single thrown `BeanCreationException`.
+- Marks the store as closed; subsequent mutating operations throw `IllegalStateException`.
+- Is idempotent; a second `close()` is a no-op.
+
+`RestContext.destroy()` now invokes `beanStore.close()` after the existing `@RestDestroy` hooks fire, so REST resources can rely on `@PreDestroy` for cleanup of bean-store-managed dependencies.
+
+##### `@Inject` on `@Rest` resource fields and methods
+
+`@Rest` resources now have `@Inject`-annotated fields and methods populated from the resource's bean store during context initialization, immediately before `@PostConstruct` callbacks fire. This applies to all four recognized FQNs (Juneau, `jakarta.inject.Inject`, `javax.inject.Inject`, Spring `@Autowired`). `Optional<T>`, collection types, and `Provider<T>` are all supported as field / parameter types.
+
+##### Constructor visibility for `BeanInstantiator`
+
+`BeanInstantiator` now considers **package-private** constructors as a final fallback after public and protected, both for the target bean type and for builder discovery. `private` constructors remain excluded (they signal "do not instantiate"). This means `@Configuration` classes and other framework-instantiated types no longer need to expose a `public` (or even `protected`) constructor purely to satisfy the bean store, as long as the class is reachable in the declaring package.
+
+##### Migration guide
+
+| Topic | Action |
+|-------|--------|
+| `org.apache.juneau.annotation.Named` | **Moved** to `org.apache.juneau.commons.inject.Named`. Update imports — no alias is kept at the old location. |
+| Simple-name annotation matching | Recognition is now strictly **FQN-based**. Local `@interface Inject` / `@interface Autowired` / `@interface PostConstruct` mocks in tests no longer trigger injection; use the real Juneau-owned types or the canonical JSR-330 / Spring FQNs. |
+| `@PostConstruct` discovery | Now matches by FQN (`org.apache.juneau.commons.inject.PostConstruct`, `jakarta.annotation.PostConstruct`, `javax.annotation.PostConstruct`). Custom annotations named `PostConstruct` are no longer picked up. |
+| `WritableBeanStore` implementations | Must now implement `void close() throws BeanCreationException` (typically by delegating to a wrapped `BasicBeanStore`). |
+| `@Bean` precedence chain | Unchanged from earlier in 10.0.0: Spring / overriding-parent → `@Bean` / local entry → memoizer-backed framework default. |
+
+## juneau-core
+
+### `BctConfiguration` defaults migrated to `@Value` (`juneau-test`, `org.apache.juneau.test.bct`)
+- `BctConfiguration` now exposes a static nested `Defaults` class whose two boolean fields
+ (`sortMaps`, `sortCollections`) are populated by concat-form `@Value` annotations:
+ `@Value("${" + BCT_SORT_MAPS + ":false}")` / `@Value("${" + BCT_SORT_COLLECTIONS + ":false}")`.
+ A `BctConfiguration.defaults()` static factory routes construction through
+ `BeanInstantiator`, so the defaults respect the standard `Settings` precedence chain.
+- The two public constants (`BCT_SORT_MAPS`, `BCT_SORT_COLLECTIONS`) are **kept**, since they
+ have external production consumers and heavy Javadoc as part of the BCT public API. See
+ [@Value Framework-Internal Adoption](/docs/topics/ValueFrameworkInternal).
+
+### ArrayUtils Deprecation
+
+`ArrayUtils` has been deprecated in favor of `CollectionUtils`. All array utility methods have been migrated to `CollectionUtils`, which now provides a single location for both array and collection operations. `ArrayUtils` methods delegate to their `CollectionUtils` counterparts and will remain available during the deprecation period.
+
+**Migration**: Replace `ArrayUtils` usage with `CollectionUtils`. Methods such as `last()`, `append()`, `combine()`, `indexOf()`, `toList()`, `contains()`, and others are available in `CollectionUtils` with the same signatures.
+
+### TupleXFunction Removed
+
+The `Tuple2Function`, `Tuple3Function`, `Tuple4Function`, and `Tuple5Function` classes have been removed. Use `Function2`, `Function3`, `Function4`, and `Function5` instead. The API is equivalent—replace `Tuple2Function` with `Function2`, and so on.
+
+### ThrowableUtils Enhancements
+
+New exception creation methods have been added to `ThrowableUtils`:
+- `unsupportedOp(String msg, Object... args)` and overload with cause
+- `ioException(String msg, Object... args)` and overload with cause
+- Cause overloads for existing methods: `illegalArg(Throwable cause, String msg, Object... args)` and `runtimeException(Throwable cause, String msg, Object... args)`
+
+All methods support MessageFormat-style formatting. `BasicRuntimeException` has been retired in favor of `runtimeException()`.
+
+### Value Class Enhancements
+
+The `Value` family of classes has been expanded:
+
+- **New classes**: `StringValue` and `ByteValue`
+- **Numeric and character types** (`IntegerValue`, `ShortValue`, `LongValue`, `CharValue`, `ByteValue`): New methods `increment()`, `decrement()`, `incrementAndGet()`, `decrementAndGet()`, `add(X)`, `addAndGet(X)`, `is(X)`, `isAny(X...)`. `CharValue` also has `isAny(String)`. `FloatValue` and `DoubleValue` have `isAny(X precision, X... values)` for precision-based equality.
+- **All Value classes**: New methods `orElse(X)`, `setIf(boolean, value)`, and `update(Function<V,V>)` for in-place modification.
+- **`Value` base class**: Enhanced to mirror `Optional` with `empty()`, `filter(Predicate)`, `flatMap(Function)`, `ifPresent(Consumer)`, `map(Function)`, `orElseGet(Supplier)`, `orElseThrow(Supplier)`.
+
+### Console.format Removed
+
+`Console.format()` has been removed. It duplicated functionality already provided by `Utils.f()`. Use `Utils.f(String format, Object... args)` for MessageFormat-style formatting. `Console.err()` and `Console.out()` continue to work as before.
+
+### `CollectionUtils.treeSet`
+
+**`CollectionUtils.treeSet(Comparator<? super E>, Collection<? extends E>)`** creates a modifiable **`TreeSet`** with the given comparator and adds all non-null elements from the collection (or none if the collection is **`null`**). Use with **`String.CASE_INSENSITIVE_ORDER`** for a case-insensitive sorted string set in one call.
+
+### StringTokenSet
+
+**`StringTokenSet`** (juneau-commons) builds an insertion-ordered unique set of string tokens. **`append(String...)`** splits each argument on commas with `StringUtils.split` escape rules, then applies mechanical rules (token starting with `-` and length > 1 removes the trimmed remainder; otherwise the token is added). **`appendAtomic(String...)`** applies one mechanical step per argument with no comma splitting. **`addAllPlain(Iterable)`** copies members without hyphen rules (used wh [...]
+
+### `AnnotationInfo.getAnnotatable()`
+
+**`AnnotationInfo.getAnnotatable()`** returns the **`Annotatable`** (class, method, field, …) where the annotation was found, for callers that need the annotation’s declaration site.
+
+### Simple Variable Language (SVL) Moved to `juneau-commons`
+The Simple Variable Language (SVL) engine — `VarResolver`, `VarResolverSession`, `Var`, and the bundled `vars/*` implementations — has been physically moved out of `juneau-marshall` into `juneau-commons` so that callers (including `juneau-config`, `juneau-microservice-core`, and downstream services) can depend on the variable-resolution engine without pulling in the full marshalling stack.
+
+#### Package Moves (breaking)
+
+| Old | New |
+|-----|-----|
+| `org.apache.juneau.commons.svl.*` | `org.apache.juneau.commons.svl.*` |
+| `org.apache.juneau.commons.svl.vars.*` | `org.apache.juneau.commons.svl.vars.*` |
+| `org.apache.juneau.commons.svl.ResolvingJsonMap` | `org.apache.juneau.collections.ResolvingMarshalledMap` |
+
+Source code referencing the old packages must update its import statements. The map has also been renamed to `ResolvingMarshalledMap` and re-parented from `JsonMap` to the new neutral `MarshalledMap` base (see the earlier change entry near the top of this file) — its SVL-resolution behavior is language-agnostic. Hard rename; no deprecation shim, so callers must update both the import and any `new ResolvingJsonMap(...)` constructor calls.
+
+##### `Args` and `ManifestFile` Redesigned and Moved (breaking)
+
+The legacy `org.apache.juneau.collections.Args` and `org.apache.juneau.utils.ManifestFile` classes — both of which extended `JsonMap` — have been replaced with lean, immutable `Optional`-returning equivalents in `juneau-commons`:
+
+| Old | New |
+|-----|-----|
+| `org.apache.juneau.collections.Args` (extends `JsonMap`) | `org.apache.juneau.commons.runtime.Args` (immutable, no `JsonMap` dependency) |
+| `org.apache.juneau.utils.ManifestFile` (extends `JsonMap`) | `org.apache.juneau.commons.runtime.ManifestFile` (immutable, no `JsonMap` dependency) |
+
+The new `Args`:
+
+- Returns `Optional<String>` from `get(int)` and `get(String)`; multi-valued options are accessed via `getAll(String)`.
+- Provides `positional()`, `argCount()`, `optionCount()`, `has(int)`, `has(String)`, `isEmpty()`, and `asMap()` accessors.
+- Exposes a configurable `Args.Builder` (`allowEquals`, `allowShortFlags`, `allowLongFlags`, `allowSystemPropStyle`, `caseSensitive`, `customPrefix`) so callers can opt into stricter or different grammars (`--key=value`, `-Dkey=value`, etc.).
+
+The new `ManifestFile`:
+
+- Returns `Optional<String>` from `get(String)` (main attributes) and `get(String section, String key)` (per-section attributes).
+- Provides `sections()`, `asMap()`, and `asMap(String section)` accessors.
+- Constructors accept `Class<?>`, `Manifest`, `File`, `Path`, `InputStream`, and `Reader`.
+
+`Microservice.Builder.args(Args)` and `Microservice.Builder.manifest(Object)` continue to work; their parameter types now resolve to the new `commons.runtime` classes. Direct callers of `args.getArg(...)` / `args.hasArg(...)` / `manifest.getString(...)` / `manifest.containsKey(...)` must adopt the new `Optional`-returning API.
+
+##### `ArgsVar` and `ManifestFileVar` — `Supplier` Overloads
+
+`ArgsVar` and `ManifestFileVar` retain their static `init(Args)` / `init(ManifestFile)` registration for backwards compatibility, and gain new factory methods that accept a `Supplier`:
+
+```java
+VarResolver vr = VarResolver.create()
+ .defaultVars()
+ .vars(ArgsVar.create(() -> myArgs), ManifestFileVar.create(() -> myManifest))
+ .build();
+```
+
+This lets each `VarResolver` instance carry its own per-resolver `Args` / `ManifestFile` state instead of relying on the JVM-wide static. The static `init(...)` form is targeted for deprecation in a follow-up release.
+
+##### Removed APIs
+
+| Removed | Replacement |
+|---------|-------------|
+| `org.apache.juneau.collections.Args` | `org.apache.juneau.commons.runtime.Args` |
+| `org.apache.juneau.utils.ManifestFile` | `org.apache.juneau.commons.runtime.ManifestFile` |
+| `Args.getArg(int)` / `getArg(String)` / `getArgs(String)` / `hasArg(String)` / `size()` | `Args.get(int)` / `get(String)` (returns `Optional<String>`) / `getAll(String)` / `has(String)` / `argCount() + optionCount()` |
+| `ManifestFile.getString(...)` / `getWithDefault(...)` / `containsKey(...)` (and other `JsonMap` accessors) | `ManifestFile.get(String).orElse(...)` / `get(String, String)` |
+
+## juneau-test (BCT)
+
+### BctAssertions API Changes
+
+The `AssertionArgs` class has been removed. Assertion methods now use a leading `Supplier<String>` parameter for custom messages. The default converter can be overridden and reset via `setConverter()` and `resetConverter()`. BCT now lives in `juneau-test` (package `org.apache.juneau.test.bct`):
+
+```java
+// Before: AssertionArgs.withConverter(customConverter).assertBean(...)
+// After: setConverter(customConverter); assertBean(...); resetConverter();
+
+// Custom message: assertBean(() -> "custom msg", expected, actual, "propertyPath");
+```
+
+The thread-local converter supplier defaults to `BasicBeanConverter.DEFAULT` (`org.apache.juneau.test.bct`) and can be configured per test or per thread.
+
+- **New `BctAssertions.assertNotContains(String unexpected, Object actual)`:** asserts that the converted string form of `actual` does not contain `unexpected`. A `Supplier<String>` message overload (`assertNotContains(Supplier<String>, String, Object)`) is also available.
+
+## juneau-bean
+
+### ParamInfo Replaced with ParameterInfo
+
+The legacy `ParamInfo` type in the Swagger v2 module has been fully replaced with `ParameterInfo`. All references have been migrated. If you have code that references `ParamInfo`, update it to use `ParameterInfo`.
+
diff --git a/pages/release-notes/10.0.0-microservice.md b/pages/release-notes/10.0.0-microservice.md
new file mode 100644
index 0000000000..34b89da358
--- /dev/null
+++ b/pages/release-notes/10.0.0-microservice.md
@@ -0,0 +1,465 @@
+---
+id: "10.0.0-microservice"
+title: "Release 10.0.0: Microservice"
+---
+
+# Release 10.0.0: Microservice
+
+This page is part of the [Release 10.0.0](/docs/release-notes/10.0.0) release notes. The overview page lists the major changes, the breaking changes and the API changes for the whole release.
+
+## juneau-microservice-jetty
+
+### `JettyMicroservice` zero-config facade + bundled defaults
+
+`juneau-microservice-jetty` now ships a thin convenience facade, `JettyMicroservice`, that collapses the `Microservice` + `JettyConfiguration` bootstrap into a single static call, plus classpath defaults for `jetty.xml` and `juneau.cfg`. End result: stand up a Jetty-backed Juneau microservice with one Maven dependency and one `@Bean Servlet`.
+
+```java
+public class App {
+ public static void main(String[] args) throws Exception {
+ JettyMicroservice.run(args, new RootResources()).join();
+ }
+}
+```
+
+Bundled classpath defaults (all overridable):
+
+- **`jetty.xml`** — single plain-HTTP `ServerConnector` on `$S{availablePort,10000}`. No `resourceBase` / `WebAppContext` / `ResourceHandler` filesystem mount is registered (no static-file serving by default). Servlets are auto-mounted by `JettyServerComponent` from `@Bean Servlet` contributions.
+- **`juneau.cfg`** — sections for `[Jetty]` (port, `jetty.xml` reference, var resolution), `[REST]` (default theme), `[Console]` (interactive console enabled with the standard `exit` / `restart` / `help` / `config` commands), `[Logging]` (`logDir`), and `[SystemProperties]` (Jetty SLF4J adapter wiring).
+
+Override semantics are unchanged: a consumer-supplied `jetty.xml` or `juneau.cfg` on the working directory or earlier on the classpath wins, as does a `@Bean JettyServerFactory` / `@Bean JettySettings` in the consumer's `@Configuration`.
+
+The `JettyMicroservice.run(...)` facade has three overloads:
+
+- `run(String[] args, Servlet rootServlet)` — interactive console enabled.
+- `run(String[] args, Servlet rootServlet, boolean startConsole)` — explicit console opt-in/out.
+- `run(String[] args, WritableBeanStore beanStore, boolean startConsole, Class<?>... configurations)` — power-user form for callers contributing multiple `@Bean Servlet`s, custom `MicroserviceListener`s, or a parent bean store.
+
+### `juneau-my-jetty-microservice` retired (BREAKING)
+
+The legacy `juneau-my-jetty-microservice` template module has been removed from the reactor. Consumers should migrate to the new `JettyMicroservice` facade in `juneau-microservice-jetty` — it delivers the same zero-config developer experience without requiring the consumer to copy a template project.
+
+### `ThemeMenuItem` widget removed (BREAKING)
+
+`org.apache.juneau.rest.server.widget.ThemeMenuItem` and its `$W{ThemeMenuItem}` variable are gone, with no `@Deprecated` stage. Its links used a `?stylesheet=` query parameter that nothing in the server ever read, so it never switched a page's theme. Remove it from `@HtmlDocConfig(widgets, navlinks)`; to change the stylesheet, set `@HtmlDocConfig(stylesheet)` or the `REST/theme` config entry. See [Stylesheets](/docs/topics/HtmlStylesheets).
+
+## juneau-microservice-jetty / juneau-microservice-tomcat
+
+### Graceful shutdown + readiness gating (zero-downtime k8s rollouts)
+
+Both embedded-server microservice flavors now perform a zero-downtime graceful shutdown suitable for rolling Kubernetes deployments, sharing one identical shutdown contract.
+
+On shutdown, a shared `ReadinessState` flag is flipped **before** the listening connector stops, so `GET /readyz` immediately returns `503` (`state=OUT_OF_SERVICE`) while a load balancer / Kubernetes is still routing. `GET /livez` and `GET /healthz` stay healthy, so the orchestrator does not kill the pod mid-drain. The server then waits up to a bounded `stopTimeout` (default `30s`) for in-flight requests to drain — Jetty via `server.setStopTimeout(...)`, Tomcat by pausing the connector a [...]
+
+- New shared `org.apache.juneau.rest.server.health.ReadinessState` flag, consulted by `HealthAggregator` to gate the `READY` probe (liveness and the aggregate health surface are unaffected).
+- New `stopTimeout` / `shutdownSettleDelay` knobs on both `JettySettings` and `TomcatSettings`, also configurable via `juneau.cfg` (`[Jetty]` / `[Tomcat]` sections, in milliseconds). Precedence: programmatic settings > config-file entry > (Jetty only: `jetty.xml` value) > 30s default.
+- Bundled `juneau.cfg` documents the knobs and the recommended Kubernetes `preStop` hook + `terminationGracePeriodSeconds` pairing.
+- MCP `subscriptions/listen` streams no longer hold the drain open. Those SSE streams never finish on their own, so a stop with any subscriber connected used to wait out the full `stopTimeout` (30s by default). `ReadinessState` gained `onOutOfService(key, callback)`, servlets can opt in via the new `ReadinessStateAware` interface, and `AbstractMcpRestServlet` uses it to close its subscriptions (new `McpSubscriptionBroker.closeAll()`, a no-op by default) as soon as shutdown begins, so eac [...]
+
+```java
+@Bean
+JettySettings jettySettings() {
+ return JettySettings.create()
+ .stopTimeout(Duration.ofSeconds(45))
+ .shutdownSettleDelay(Duration.ofSeconds(2))
+ .build();
+}
+```
+
+See the new [Graceful Shutdown & Readiness Gating](/docs/topics/GracefulShutdown) topic page for details, including the Jetty/Tomcat parity table and Kubernetes deployment guidance.
+
+### Servlet filter registration for microservice apps
+
+A new `ServletFilterRegistration` bean mounts a `jakarta.servlet.Filter` on `JettyServerComponent` or `TomcatServerComponent` before the server starts, with URL patterns, explicit dispatcher types (`REQUEST`, `FORWARD`, `ASYNC`, `ERROR`) and a defined order relative to the auto-mounted `AuthFilterChain`. Previously only the `REQUEST` dispatch was reachable and only after start-up. The `AuthFilterChain` auto-mount now goes through the same ordered path. The petstore Jetty runner uses it f [...]
+
+On Tomcat, servlets mounted through `TomcatServerComponent.addServlet` are now async-capable (`request.isAsyncSupported()` is true), matching Jetty, so async `@RestOp` return types use the real async path instead of falling back to synchronous handling.
+
+## juneau-microservice-test (new module)
+
+### `@MicroserviceTest` — whole-microservice JUnit 5 integration tests
+
+A new `juneau-microservice-test` module adds `@MicroserviceTest`, a JUnit 5 extension that boots a whole `Microservice` (config + lifecycle + embedded Jetty on an ephemeral port) for a test class — the standalone-microservice analog of Spring's `@SpringBootTest`.
+
+- **Explicit SUT.** Declare the system under test via `@MicroserviceTest(configurations={...})` and/or a `static Microservice.Builder microserviceBuilder()` supplier method (no classpath scanning). The extension adds an ephemeral-Jetty `Server` (port 0) + `JettyConfiguration` automatically; Jetty for v1 (Tomcat pluggability is a follow-on).
+- **Composes the existing `@TestBean` substrate** (no parallel injection annotation). Default **Mode INJECT** installs overrides via `Microservice.Builder.overridingBeanStore(...)` *before* boot, so the service reads them from startup; **Mode OVERLAY** pushes/pops against the booted instance.
+- **Parameter resolution** for `RestClient` (bound to the booted root URL — the primary convenience), `Microservice`, `WritableBeanStore`, and the bound port.
+- **Fresh instance per class**, stopped cleanly in teardown (restart is unsupported); `stop()` restores process globals so multiple `@MicroserviceTest` classes are isolated within a JVM. The bound port is always read from the live `ServerConnector`.
+- Distinct from `MockRestClient`: use `@MicroserviceTest` for a real server/lifecycle over HTTP, `MockRestClient` for in-JVM single-resource tests.
+- This also added a small public composition entry point — `JuneauBeanStoreExtension.discoverOverrides(...)` — so other JUnit 5 extensions can reuse the `@TestBean` discovery without driving that extension's own lifecycle.
+
+See the new [Whole-Microservice Integration Tests (@MicroserviceTest)](/docs/topics/MicroserviceTesting) topic page.
+
+## juneau-microservice (formerly juneau-microservice-core)
+
+### Per-microservice `Config` wired into `Settings`
+- `Microservice`'s constructor now installs a `ConfigPropertySource` for the microservice's own
+ `Config` onto `Settings.get()`, and `stop()` removes it. Any `@Value("${section/key}")`
+ injection — on REST resources mounted under the microservice, on listeners, or on any other
+ bean built through `BeanInstantiator` — resolves against the microservice config without an
+ explicit lookup call.
+
+### `juneau.workingDir` migrated to `@Value`
+- The `juneau.workingDir` env-driven default on `Microservice.Builder` is now populated by an
+ `@Inject`-annotated initializer (`initWorkingDirFromEnv`) that takes a
+ `@Value("${juneau.workingDir}") String` parameter. The builder is now constructed via
+ `BeanInstantiator.of(Builder.class, beanStore).run()`. Existing programmatic
+ `Microservice.create().workingDir(...)` callers are unaffected — the `@Inject` initializer
+ only sets `workingDir` when it has not already been assigned. See
+ [@Value Framework-Internal Adoption](/docs/topics/ValueFrameworkInternal).
+
+### Module Renamed
+The `juneau-microservice-core` Maven artifact has been **renamed** to `juneau-microservice`. The aggregator pom is now
+`juneau-microservice-parent`. The Java package `org.apache.juneau.microservice` is unchanged. Update your `pom.xml`:
+
+```xml
+<!-- OLD -->
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-microservice-core</artifactId>
+ <version>${juneau.version}</version>
+</dependency>
+
+<!-- NEW -->
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-microservice</artifactId>
+ <version>${juneau.version}</version>
+</dependency>
+```
+
+### `MicroserviceListener` Fan-Out
+`Microservice.start()` / `Microservice.stop()` / `Microservice.onConfigChange(...)` now invoke **every**
+`MicroserviceListener` bean in the bean store, not just a single resolved primary listener.
+
+- `onStart` invokes listeners in registration order.
+- `onStop` invokes them in **reverse** registration order.
+
+The builder-supplied `.listener(...)` value is registered into the bean store under the unnamed key and
+participates in the fan-out like any other `@Bean MicroserviceListener`. If no listeners are contributed by
+configurations and none is supplied via the builder, the default `BasicMicroserviceListener` is registered so the
+fan-out always has at least one no-op participant. This change enables companion modules (notably
+`juneau-microservice-jetty`) to drive server lifecycles entirely through standard `MicroserviceListener` hooks,
+without needing a `Microservice` subclass.
+
+### Inject-Aware Microservice
+`Microservice` is now inject-aware. Every microservice owns an internal `WritableBeanStore`
+(from `juneau-commons`) populated from `@Configuration` classes registered on the builder.
+
+**New builder methods**:
+
+- `Microservice.Builder.configurations(Class<?>...)` — register one or more `@Configuration` classes
+ whose `@Bean` methods/fields are processed at bootstrap.
+- `Microservice.Builder.configurations(List<Class<?>>)` — same, list form.
+- `Microservice.Builder.beanStore(WritableBeanStore)` — supply an externally-owned store instead of
+ letting the microservice construct a fresh one. Useful for composing into a larger application or
+ integrating with `juneau-rest-server-springboot`.
+
+**New accessor**:
+
+- `Microservice.getBeanStore()` — never null. Contains the resolved `Args`, `ManifestFile`, `Config`,
+ `VarResolver`, `MicroserviceListener`, the `Microservice` instance itself, plus everything
+ contributed by registered `@Configuration` classes.
+
+**Lifecycle**:
+
+- `@PostConstruct` callbacks fire on beans instantiated through the inject framework (e.g. via
+ constructor injection in `@Bean` factory methods or `BeanStore.instantiate(X)`).
+- `@PreDestroy` callbacks fire on `Microservice.stop()`, which now closes the bean store and walks
+ every resolved bean in LIFO order. Errors are logged at `WARNING` and do not abort the rest of
+ the shutdown sequence.
+
+**Resolution priority** for core types (`Args`, `Config`, etc.): explicit builder call wins, then
+`@Bean`-supplied value, then built-in default. The final resolved value is registered into the bean
+store so downstream `@Bean` factory methods see the same instance that the `Microservice.getXxx()`
+getter returns.
+
+This change is **fully additive** — every existing builder method continues to work unchanged, and
+microservices that never call `.configurations(...)` behave identically to pre-10.0.0.
+
+See <a href="/docs/topics/InjectAwareMicroservice">Inject-Aware Microservice</a> for the full guide.
+
+### `View` interface — engine-agnostic server-side render contract
+
+- New `org.apache.juneau.rest.server.view.View` interface. Carries the data a templating engine needs to render a response: `String getTemplateName()`, `Map<String, Object> getAttributes()`, and a `default Map<String, String> getResponseHeaders()` seam. Engine-agnostic — concrete implementations live in per-engine bridge modules (`juneau-rest-server-view-jsp`, `juneau-rest-server-view-thymeleaf`, `juneau-rest-server-view-mustache`, and `juneau-rest-server-view-freemarker` all ship in 10. [...]
+- Designed as the stable extension point for the new view-module family. New methods will be added as `default`-bodied where possible to preserve backward compatibility with downstream view impls.
+
+```java
+@RestGet("/hello/{name}")
+public View hello(@Path String name) {
+ return JspView.of("hello.jsp").attr("name", name); // JspView implements View
+}
+```
+
+## juneau-microservice-jetty
+
+### `JettyServerComponent.addFilter(...)` + `AuthFilterChain` auto-mount
+`JettyServerComponent` now exposes two public filter-registration methods:
+
+```java fragment
+public JettyServerComponent addFilter(Filter filter, String urlPattern) { ... }
+public JettyServerComponent addFilter(Filter filter, String... urlPatterns) { ... }
+```
+
+Both delegate to `ServletContextHandler.addFilter(FilterHolder, String, EnumSet<DispatcherType>)` for `DispatcherType.REQUEST` dispatches. Filters registered via these methods are mounted before any servlet, preserving filter-before-routing semantics.
+
+`onStart(Microservice)` now scans the `BeanStore` for an `AuthFilterChain` bean and auto-registers it at `/*` before servlet discovery — no explicit `addFilter(...)` call is needed when the chain is declared as a `@Bean`.
+
+### `JettyServerComponent` env reads migrated to `@Value`
+- `JettyServerComponent` previously called `env("availablePort")` and `env("juneau.serverPort")`
+ to skip an empty fallback during port resolution. These reads are now `@Value("${availablePort}")
+ Optional<String> availablePortEnv` and `@Value("${juneau.serverPort}") Optional<String>
+ serverPortEnv` fields populated by `ClassInfo.of(this).inject(this, store)` inside
+ `onStart(Microservice)`. Behavior is unchanged. See
+ [@Value Framework-Internal Adoption](/docs/topics/ValueFrameworkInternal).
+
+### `HealthProbeConfiguration`
+`juneau-microservice-jetty` now ships `HealthProbeConfiguration`, an opt-in `@Configuration` module
+that contributes:
+
+- `@Bean Servlet healthProbeServlet()` returning `HealthMixin` for auto-mount through
+ `JettyServerComponent`.
+- `@Bean HealthProbeSettings` defaulting to a `1s` indicator timeout (overridable via user bean).
+
+End-state bootstrap:
+
+```java
+Microservice.create()
+ .configurations(JettyConfiguration.class, HealthProbeConfiguration.class, AppConfig.class)
+ .build()
+ .start();
+```
+
+### Transport-Agnostic REST Client and HTTP Stack
+Juneau 10.0.0 introduces a new REST client and HTTP type stack that **decouples Juneau from Apache HttpClient 4.5** and promotes it to the canonical names. The pre-existing HC4.5-based stack continues to ship under explicit `.classic` packages and a new `juneau-rest-client-classic` module, so existing callers keep working without source changes other than adding a Maven dependency and importing from the new package.
+
+The non-classic types in `org.apache.juneau.rest.client.*`, `org.apache.juneau.rest.mock.*`, and `org.apache.juneau.http.*` are generally available in 10.0.0. The classic stack remains supported and is not deprecated.
+
+### `org.apache.juneau.http` (in `juneau-rest-common`)
+
+A JDK-native replacement for the Apache HttpCore-based types previously in this package. No `httpcore` dependency in the canonical types — all interfaces (`HttpHeader`, `HttpPart`, `HttpBody`, `HttpStatusLine`, `HttpResponseMessage`) use JDK types only.
+
+- Typed header hierarchy (`HttpStringHeader`, `HttpMediaTypeHeader`, `HttpMediaRangesHeader`, `HttpStringRangesHeader`, `HttpCsvHeader`, `HttpDateHeader`, `HttpUriHeader`, `HttpIntegerHeader`, `HttpLongHeader`, `HttpBooleanHeader`, `HttpEntityTagHeader`, `HttpEntityTagsHeader`) with ~73 RFC-named header classes (`Accept`, `ContentType`, `Authorization`, …).
+- Entity / body types: `HttpBodyBean`, `StringBody`, `ByteArrayBody`, `StreamBody`, `FileBody`, `MultipartBody` (RFC 7578 streaming).
+- Status / response types: `HttpStatusLineBean`, `BasicHttpResponse`, `BasicHttpException`, ~50 named status classes (`Ok`, `Created`, `NotFound`, `InternalServerError`, …).
+- Remote-proxy annotations stay under `org.apache.juneau.http.remote` (`@Remote`, `@RemoteGet`, `@RemotePost`, `@RemotePut`, `@RemotePatch`, `@RemoteDelete`, `@RemoteOp`, `@RemoteReturn`).
+
+The Apache HttpCore-based legacy types (~170 classes) moved to `org.apache.juneau.http.classic.*` in the same module, so existing `juneau-rest-common` consumers only need an `import org.apache.juneau.http.classic.*;` adjustment.
+
+### `org.apache.juneau.rest.client` (in `juneau-rest-client`)
+
+The transport abstraction and the new canonical `RestClient` / `RestRequest` / `RestResponse`. No `org.apache.http.*` dependency.
+
+- `HttpTransport` — single integration point replacing `org.apache.http.client.HttpClient`. Provides synchronous `send(TransportRequest)` and a `sendAsync(...)` default that transports can override for native async.
+- `TransportRequest`, `TransportResponse` (`Closeable` — owns connection-release hook), `TransportHeader`, `TransportBody`, `TransportException` — transport-layer DTOs using JDK types only.
+- `HttpTransportBuilder` / `HttpTransportProvider` SPI — `ServiceLoader`-based auto-discovery; passing a fully-built transport or transport builder is also supported.
+- `RestClient` — composes an `HttpTransport`; one serializer/parser/marshaller per client (no multi-language mode); fluent `get/post/put/patch/delete/head/options/formPost/multipartPost` API; remote proxies via `getRemote(Class)`; explicit `shutdown()` (not `Closeable`).
+- `RestRequest` — the single `Closeable` in the user-facing API; resolves URI + path data + query, runs interceptors, serializes the body, calls the transport.
+- `CollectionFormat` enum (`COMMA`, `PIPE`, `SPACE`, `TAB`, `REPEATED`) for `Iterable` / array-valued params.
+- `BodyConverter` — pluggable request-body conversion; default chain handles `HttpBody`, `MultipartBody`, `InputStream`, `byte[]`, `Reader`, `File`, `PartList`, and falls back to a `SerializedBody` that streams through the client's serializer.
+- `RestCallInterceptor` — `onInit` / `onConnect` / `onClose` lifecycle hooks (no Apache types).
+- Debug logging — see the client debug-logging entry above (JUL-level–driven `RestClientDebugFormatter`).
+
+The legacy Apache HttpClient 4.5–based client moved to `org.apache.juneau.rest.client.classic.*` in the new `juneau-rest-client-classic` module — see "Classic Module Split" below.
+
+### Transport Implementations
+
+The JDK `HttpClient`–based `JavaHttpTransport` ships built into the canonical `juneau-rest-client` artifact and is the default — `RestClient.create()` works out of the box on Java 11+ with zero third-party dependencies (see an earlier change below).
+
+To swap in a different native HTTP client, add one of the optional transport modules. Each registers a higher-priority `HttpTransportProvider` via `ServiceLoader`, so `RestClient.create().build()` automatically picks it up.
+
+| Module | Artifact ID | Native client |
+|---|---|---|
+| Apache HttpClient 4.5 | `juneau-rest-client-apache-httpclient-45` | `org.apache.httpcomponents:httpclient:4.5.x` |
+| Apache HttpClient 5 | `juneau-rest-client-apache-httpclient-50` | `org.apache.httpcomponents.client5:httpclient5` |
+| OkHttp | `juneau-rest-client-okhttp` | `com.squareup.okhttp3:okhttp` |
+| Eclipse Jetty client | `juneau-rest-client-jetty` | `org.eclipse.jetty:jetty-client` |
+
+Each optional transport module ships `*Transport`, `*TransportBuilder` (with native client–specific configuration accessible via cast), and `*TransportProvider`. Common transport-builder configuration (`connectTimeout`, `readTimeout`, `sslContext`, `hostnameVerifier`, `proxy`, `maxConnections`, `maxConnectionsPerRoute`, `followRedirects`, …) lives on `HttpTransportBuilder` so most callers don't need to cast.
+
+### `juneau-rest-mock` — `org.apache.juneau.rest.mock`
+
+The canonical mock client now matches the new `RestClient`:
+
+- `MockHttpTransport` — routes `TransportRequest` directly to a Juneau `RestContext` without network I/O.
+- `MockRestClient` — composes `MockHttpTransport` and exposes the standard `RestClient` builder surface. Use `MockRestClient.create(MyRestResource.class).marshaller(Json5.DEFAULT).build()` for serverless integration tests.
+
+The Apache HttpClient 4.5–implementing legacy `MockRestClient` moved to `org.apache.juneau.rest.mock.classic.*` alongside its `MockRestRequest`, `MockRestResponse`, `MockHttpClientConnectionManager`, `MockLogger`, and `MockConsole` helpers. The shared servlet-mock plumbing (`MockServletRequest`, `MockServletResponse`, `MockHttpSession`, `MockPathResolver`) stays at `org.apache.juneau.rest.mock.*` and is reused by both client flavors.
+
+### Classic Module Split
+
+The pre-existing Apache HttpClient 4.5–based REST client moved into a new sibling Maven module:
+
+| Artifact | Contents |
+|---|---|
+| `juneau-rest-client` (canonical) | Transport-agnostic NG `RestClient`. No `org.apache.http.*` dependency. |
+| `juneau-rest-client-classic` (new) | Legacy `RestClient` (extends `org.apache.http.client.HttpClient`); types live under `org.apache.juneau.rest.client.classic.*`. Depends on `org.apache.httpcomponents:httpclient:4.5.14`. |
+
+Both modules can be on the classpath simultaneously — they share `juneau-rest-common` but expose disjoint packages. The aggregator artifacts (`juneau-shaded-rest-client`, `juneau-shaded-all`, `juneau-microservice`, `juneau-rest-mock`, `juneau-distrib`) pull in both flavors so existing assemblies see no behavioral change.
+
+### `juneau-rest-common` split into `juneau-rest-common` + `juneau-rest-common-classic`
+Mirroring the `juneau-rest-client` split above, the `juneau-rest-common` module is now split into a pure transport-neutral module and a new sibling that owns every Apache HttpClient 4.5–compatible facade.
+
+| Artifact | Contents | Apache HttpClient 4.5 dependency? |
+|---|---|---|
+| `juneau-rest-common` | Transport-neutral types under `org.apache.juneau.http.*` (annotations, lazy header beans, remote-proxy meta, parts, entities, `HttpHeaders` factory), plus `org.apache.juneau.http.remote.*`. | **No.** Zero `org.apache.httpcomponents:*` on the classpath. |
+| `juneau-rest-common-classic` (new) | Every type formerly at `org.apache.juneau.http.classic.*` — Apache HttpClient 4.5–style headers, parts, entities, resources, responses, the classic `HeaderList`, and the classic `Remote*Meta` proxy bridges. **FQCNs unchanged** — only the Maven coordinate moved. | **Yes.** Depends on `org.apache.juneau:juneau-rest-common` and `org.apache.httpcomponents:httpcore:4.4.16`. |
+
+**Why:** `juneau-rest-common` was the last `juneau-rest-*` module forcing `org.apache.httpcomponents:httpcore` onto every downstream consumer (transports, microservices, examples, mock). Splitting it lets the canonical NG stack ship genuinely transport-neutral.
+
+**Backward compatibility:**
+- Fully qualified class names are unchanged — `org.apache.juneau.http.classic.header.EntityTag` is still `org.apache.juneau.http.classic.header.EntityTag`. Source-compatible.
+- Code that already depended on `juneau-rest-common` and used a `classic.*` type now needs an additional `<dependency>` on `juneau-rest-common-classic`. The aggregator artifacts (`juneau-rest-client-classic`, `juneau-rest-mock`, `juneau-microservice`, `juneau-shaded-rest-server`, `juneau-shaded-rest-client`, `juneau-shaded-all`, `juneau-distrib`) already pull both modules in transitively, so most callers using those uber artifacts see no Maven change. (As of 10.0, `juneau-rest-server` an [...]
+- `EntityTag` / `EntityTags` value types (no HC 4.5 dependency, despite the historical package) moved out of `classic.header` to `org.apache.juneau.http.header.EntityTag` / `EntityTags`. **This is a source-incompatible change** for any caller that imported them explicitly — see the [v10.0 Migration Guide](/docs/topics/V10MigrationGuide).
+- `@Remote(headerList = ...)` annotation method changed from `Class<? extends HeaderList>` to `Class<?>`. Existing user annotations like `@Remote(headerList = MyHeaderList.class)` continue to work; the classic `RemoteMeta` performs an `instanceof HeaderList` check at runtime before invoking. The default sentinel changed from `HeaderList.Void.class` to `java.lang.Void.class`.
+
+### Usage Examples
+
+```java
+// Pre-built serializer/parser — configure marshalling externally
+var serializer = Json5Serializer.create().build();
+var parser = Json5Parser.create().build();
+
+// Pick a transport explicitly
+RestClient client = RestClient.create()
+ .transportBuilder(ApacheHc45Transport.create()
+ .connectTimeout(Duration.ofSeconds(10))
+ .sslContext(mySSLContext)
+ .maxConnections(50))
+ .serializer(serializer)
+ .parser(parser)
+ .rootUrl("https://api.example.com")
+ .build();
+
+// Or let the SPI auto-discover the best transport on the classpath
+RestClient zeroConfig = RestClient.create()
+ .marshaller(Json5.DEFAULT)
+ .rootUrl("https://api.example.com")
+ .build();
+
+// Always use try-with-resources on the request — close releases the connection
+try (var req = client.get("/users/{id}").pathData("id", 42)) {
+ var user = req.run().as(User.class);
+}
+
+// Multipart upload
+try (var req = client.multipartPost("/upload")
+ .multipartField("album", "vacation")
+ .multipartFile("photo", Path.of("/tmp/beach.jpg"), "image/jpeg")) {
+ req.run();
+}
+
+// At application shutdown
+client.shutdown();
+```
+
+### Migration Path
+
+The classic `RestClient` / `juneau-rest-common` APIs remain fully supported. Migration is **opt-in** and incremental:
+
+1. **Stay on classic** — add a `juneau-rest-client-classic` dependency and replace `import org.apache.juneau.rest.client.*;` with `import org.apache.juneau.rest.client.classic.*;` (similar for `org.apache.juneau.http.*` → `org.apache.juneau.http.classic.*`). No behavior change.
+2. **Migrate to the new stack** — add a transport module (e.g. `juneau-rest-client-apache-httpclient-45`), keep `import org.apache.juneau.rest.client.*;`, and adapt builder calls. Most configuration (`rootUrl`, default headers/query/form/path, interceptors, marshaller) maps 1:1; per-Apache-HttpClient options (`httpClientBuilder()`, `connectionManager()`, `defaultRequestConfig()`) move onto the transport builder.
+3. **Update tests** — use `org.apache.juneau.rest.mock.MockRestClient` (canonical) for new tests; keep `org.apache.juneau.rest.mock.classic.MockRestClient` for the legacy fluent surface (`.json5()`, `.plainText()`, deprecated builder methods).
+
+The classic stack is **not** deprecated in 10.0; the canonical stack will graduate to stable in a future release, at which point the classic stack may be deprecated and eventually removed.
+
+### `juneau-rest-server` decoupled from Apache HttpClient 4.5
+Completes the HC 4.5 removal started by earlier changes. `juneau-rest-server` is now genuinely transport-neutral — it neither imports `org.apache.http.*` nor depends on `juneau-rest-common-classic`, so `mvn -pl juneau-rest/juneau-rest-server dependency:tree | grep httpcomponents` returns nothing. Pulling `juneau-rest-server` no longer drags `org.apache.httpcomponents:httpcore:4.4.16` onto the consumer classpath.
+
+### New transport-neutral types in `juneau-rest-common`
+
+To give the server somewhere to migrate **to**, the following transport-neutral siblings of the classic HC 4.5 types were added in `org.apache.juneau.http.*`. They use only JDK types — no `org.apache.http.*` dependency.
+
+| New type | Replaces (classic) | Notes |
+|---|---|---|
+| `HttpRequestLine` (interface) + `request/HttpRequestLineBean` | `org.apache.http.RequestLine` | Method / URI / `HttpProtocolVersion`. |
+| `HttpProtocolVersion` (record) | `org.apache.http.ProtocolVersion` | `protocol()` / `major()` / `minor()` accessors plus `parse(String)`. |
+| `header/HttpHeaderList` | `org.apache.juneau.http.classic.header.HeaderList` | Mutable ordered list of `HttpHeader` with `getFirst/getLast/getAll/forEach/copy` plus case-sensitive and case-insensitive lookup. |
+| `part/HttpPartList` | `org.apache.juneau.http.classic.part.PartList` | Mutable ordered list of `HttpPart` mirroring `HttpHeaderList`. |
+| `part/HttpStringPart`, `HttpBooleanPart`, `HttpIntegerPart`, `HttpLongPart`, `HttpDatePart`, `HttpUriPart`, `HttpCsvArrayPart` | `org.apache.juneau.http.classic.part.Basic*Part` | Concrete typed parts so `RequestQueryParam` / `RequestFormParam` / `RequestPathParam` / `RequestHeader` can keep their typed conversion ergonomics (`.asInteger()`, `.asLong()`, `.asDate()`, etc.). |
+| `header/HttpStringHeader`, `HttpBooleanHeader`, `HttpIntegerHeader`, `HttpLongHeader`, `HttpDateHeader`, `HttpEntityTagHeader`, `HttpEntityTagsHeader`, `HttpMediaTypeHeader`, `HttpMediaRangesHeader`, `HttpStringRangesHeader`, `HttpCsvHeader`, `HttpUriHeader` | `org.apache.juneau.http.classic.header.Basic*Header` | Abstract bases with typed accessors; subclassed by the ~73 RFC-named header classes (`Accept`, `ContentType`, `Authorization`, …). |
+| `resource/HttpResourceBean` | `org.apache.juneau.http.classic.resource.BasicResource` | Concrete `HttpResource` implementation backed by `HttpBody` + `List<HttpHeader>`. |
+
+`BasicHttpException` (and therefore all ~62 named status subclasses in `org.apache.juneau.http.response.*` — `BadRequest`, `Ok`, `NotFound`, `InternalServerError`, …) gained the full classic fluent setter surface so server-side handlers that build responses via `throw new BadRequest().setHeader(...).setReasonPhrase(...)` keep working unchanged after the package swap:
+
+- `setStatusCode(int)`, `setReasonPhrase(String)`, `setProtocolVersion(HttpProtocolVersion)`
+- `setHeader(HttpHeader)`, `setHeader(String name, String value)`
+- `setHeaders(HttpHeader...)`, `setHeaders(List<HttpHeader>)`
+- `setContent(HttpBody)`, `setContent(String)`
+- `setLocale(Locale)`, `setUnmodifiable()`
+
+### Public API changes in `juneau-rest-server`
+
+The 10.0 release retypes the server-side public surface from `org.apache.http.*` onto the new transport-neutral types. The signature changes below are **source-incompatible** for any caller that imported the classic types directly:
+
+| Member | Was | Is |
+|---|---|---|
+| `RestRequest#getAllHeaders()` | `org.apache.http.Header[]` | `org.apache.juneau.http.HttpHeader[]` |
+| `RestRequest#getRequestLine()` | `org.apache.http.RequestLine` | `org.apache.juneau.http.HttpRequestLine` |
+| `RestRequest#getProtocolVersion()` | `org.apache.http.ProtocolVersion` | `org.apache.juneau.http.HttpProtocolVersion` |
+| `RestRequest#addDefault(...)` / `RestOpContext.getDefaultRequestHeaders()` | `org.apache.http.Header[]` / `HeaderList` | `org.apache.juneau.http.HttpHeader[]` / `HttpHeaderList` |
+| `RequestHeader`, `RequestQueryParam`, `RequestFormParam`, `RequestPathParam`, `RequestHttpPart`, `BasicNamedAttribute` | extended `org.apache.http.Header` / `org.apache.http.NameValuePair` | extend `org.apache.juneau.http.HttpHeader` / `org.apache.juneau.http.HttpPart` |
+| `BasicStaticFiles` / `BasicStaticFiles.Builder` header collections | `org.apache.http.Header[]` | `org.apache.juneau.http.HttpHeader[]` |
+| `FluentRequestLineAssertion` | over `org.apache.http.RequestLine` | over `HttpRequestLine` |
+| `FluentProtocolVersionAssertion` | over `org.apache.http.ProtocolVersion` | over `HttpProtocolVersion` |
+| `HttpStatusLine#getProtocolVersion()` | `String` | `HttpProtocolVersion` |
+
+### Class renames in `juneau-rest-server`
+
+The four request-side part-list classes were renamed for naming consistency with the new `HttpHeaderList` / `HttpPartList` siblings in `juneau-rest-common`. No backward-compatibility shims are kept — callers must update imports and references.
+
+| Was | Is |
+|---|---|
+| `org.apache.juneau.rest.server.httppart.RequestFormParams` | `org.apache.juneau.rest.server.httppart.RequestFormParamList` |
+| `org.apache.juneau.rest.server.httppart.RequestHeaders` | `org.apache.juneau.rest.server.httppart.RequestHeaderList` |
+| `org.apache.juneau.rest.server.httppart.RequestPathParams` | `org.apache.juneau.rest.server.httppart.RequestPathParamList` |
+| `org.apache.juneau.rest.server.httppart.RequestQueryParams` | `org.apache.juneau.rest.server.httppart.RequestQueryParamList` |
+
+The argument resolvers and `RestPreCall` / `RestPostCall` lifecycle hooks were updated to recognize the new names; any user method signature that referenced the old names needs to be updated.
+
+### Response processor rename
+
+`org.apache.juneau.rest.server.processor.HttpEntityProcessor` was renamed to `HttpBodyProcessor`. The class was already internally rewritten to operate on `HttpBody` instead of HC 4.5's `HttpEntity`, but the class name kept the legacy term until this release. The default response-processor chain registered by `DefaultConfig` was updated accordingly; any custom `RestContext.responseProcessors(...)` configuration that names `HttpEntityProcessor.class` needs to be updated to `HttpBodyProces [...]
+
+### Backward-compatibility shim for legacy return types
+
+A small reflective bridge — `org.apache.juneau.rest.server.processor.LegacyHttpResponseAdapter` — was added so user `@RestOp` methods that still return classic `org.apache.http.HttpResponse` / `org.apache.http.HttpEntity` / `org.apache.juneau.http.classic.resource.BasicResource` instances keep working when `juneau-rest-common-classic` is on the runtime classpath, even though `juneau-rest-server` itself no longer compiles against those types. The bridge is invoked by `HttpBodyProcessor` a [...]
+
+### Verification
+
+After this change:
+
+- `mvn -pl juneau-rest/juneau-rest-server dependency:tree | grep httpcomponents` returns nothing.
+- `rg -n '^import\s+org\.apache\.http\.' juneau-rest/juneau-rest-server/src/main/java` returns nothing.
+- `rg -n '^import\s+org\.apache\.juneau\.http\.classic\.' juneau-rest/juneau-rest-server/src/main/java` returns nothing.
+
+### JDK `HttpClient` is now the built-in default transport
+The JDK `java.net.http.HttpClient`–based transport (`JavaHttpTransport`) is now part of the `juneau-rest-client` artifact itself. The previously separate `juneau-rest-client-java-httpclient` module — which had no third-party dependencies and was never released — has been folded in and removed.
+
+**What this means for callers:**
+
+- `RestClient.create()` works out of the box on Java 11+ with **zero** third-party dependencies. No transport module is required for a baseline HTTP client.
+- The four remaining sibling transport modules (`juneau-rest-client-apache-httpclient-45`, `-apache-httpclient-50`, `-okhttp`, `-jetty`) continue to register higher-priority providers via `ServiceLoader` and automatically take over discovery when added to the classpath.
+- Explicit `RestClient.builder().transport(...)` always wins over auto-discovery.
+- `RestClient.discoverTransport()` now falls back to `JavaHttpTransport.create()` when `ServiceLoader` returns no providers (a defensive measure for heavily-shaded uber-jars that strip `META-INF/services` files).
+
+**Package promotion:**
+
+`JavaHttpTransport`, `JavaHttpTransportBuilder`, and `JavaHttpTransportProvider` were promoted from `org.apache.juneau.rest.client.javahttpclient.*` to the canonical `org.apache.juneau.rest.client.*` package root. No previously released version exposed the `javahttpclient` package, so this is an internal restructuring of unreleased beta code — no migration steps required.
+
+```java
+// Zero-config: uses the built-in JavaHttpTransport
+try (var req = RestClient.create().get("https://api.example.com/users")) {
+ var users = req.run().as(User[].class);
+}
+
+// Explicit tuning of the JDK client
+var transport = JavaHttpTransport.builder()
+ .httpClient(java.net.http.HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(5))
+ .build())
+ .build();
+RestClient client = RestClient.builder().transport(transport).build();
+
+// Or swap to a different transport entirely just by adding its module
+// (no code change needed — ServiceLoader picks up the higher-priority provider)
+```
+
diff --git a/pages/release-notes/10.0.0-other.md b/pages/release-notes/10.0.0-other.md
new file mode 100644
index 0000000000..52e78dd1d5
--- /dev/null
+++ b/pages/release-notes/10.0.0-other.md
@@ -0,0 +1,355 @@
+---
+id: "10.0.0-other"
+title: "Release 10.0.0: Other modules"
+---
+
+# Release 10.0.0: Other modules
+
+This page is part of the [Release 10.0.0](/docs/release-notes/10.0.0) release notes. The overview page lists the major changes, the breaking changes and the API changes for the whole release.
+
+## juneau-commons / juneau-secret-macos-keychain
+
+### `SecretStore` SPI — the secure, mutable sibling of `PropertySource`
+
+Juneau 10.0 adds a small, JDK-only `SecretStore` SPI (`org.apache.juneau.commons.secret`, `@since 10.0.0`) for
+storing and retrieving secrets by key — `store` / `find` / `exists` / `delete`. It is the secure, mutable sibling of
+`PropertySource`: values are held as `char[]` (never `String`), and are never `toString()`'d, logged, or dumped.
+
+- **`InMemorySecretStore`** — the zero-config, `ConcurrentHashMap`-backed, process-local default (honest about being
+ neither persistent nor cross-node), with defensive-copy in/out so callers can zero their own arrays.
+- **`EnvVarSecretStore`** — a read-only store backed by `System.getenv` (`store`/`delete` are unsupported).
+- **`SecretStoreProvider`** + `META-INF/services` — `ServiceLoader` discovery mirroring `PropertySourceProvider`;
+ opt-in via `SecretStores.fromServiceLoader()`.
+- **`SecretStores.resolve(beanStore)`** — resolves the active store from a `BeanStore`, defaulting to
+ `InMemorySecretStore` (the `BeanStore`-with-drop-in-default idiom used elsewhere in the framework).
+- **Opt-in Settings bridge** — `SecretStorePropertySource` exposes a chosen store as a `PropertySource` only where a
+ consumer deliberately wires it, and implements the `SensitivePropertySource` marker so dump/log/`toString()` paths
+ redact bridged values. Secrets stay out of the general config/`$P{...}`/SVL namespace by default.
+- **New opt-in module `juneau-secret-macos-keychain`** — a `KeychainSecretStore` backed by the macOS `security` keychain
+ CLI, honoring a `ReplayCache`-style `FailMode` (fail-open/fail-closed) for backend-unavailable behavior. It lives
+ outside `juneau-commons` because it shells out to an OS process. `store` sends the command to `security -i` on
+ stdin, so the secret never appears on a command line and a JVM started from a terminal is never left waiting on a
+ password prompt. `find` reads the password from `security find-generic-password -g`, so secrets containing tabs,
+ other control characters, non-ASCII text, quotes or backslashes come back exactly as stored.
+
+See the [Secrets](/docs/topics/JuneauCommonsSettings#secrets) section of the Settings topic page for details.
+
+## juneau-commons / juneau-beanquery-sql / juneau-beanquery-postgres
+
+### BeanQuery contexts and sessions
+
+`org.apache.juneau.commons.beanquery` is a protocol-agnostic API for searching, sorting, projecting and paging rows of beans, in memory or over JDBC. Engines follow a context/session split:
+
+- **Immutable, builder-built contexts.** `InMemoryBeanQueryContext.create(Person.class)...build()` and `SqlBeanQueryContext.create(Person.class)...build()` fix the columns, operators, limits and a server-side guard once. `copy()` derives a variant.
+- **Per-request sessions.** `getSession(rows)` / `getSession()`, or `createSession()...build()` for per-request rules: narrow the columns, override the limits, add a guard, or pass a caller-owned JDBC `Connection`. A session offers `find`, `findValues`, `count`, `stream`, `streamValues` and `resolve`.
+- **Validate before run.** Every query is resolved into a `ResolvedQuery` first. Unknown columns, disallowed operators, over-long search strings, too many clauses or sort keys, and over-deep expressions are rejected with `BeanQuerySyntaxException`; they are never silently dropped. `defaultLimit` (100) applies when `limit` is missing; a `limit` above `maxLimit` (1000) is clamped to it, never rejected.
+- **Counts by policy.** `CountPolicy` (`NONE`, `MATCHED`, `BOTH`, `IF_REQUESTED`) decides which counts a `Page` carries. `Page.total()`/`Page.matched()` are independently-optional (see below).
+- **Safe regex.** `$regex` is enabled by default; `allowRegex(false)` disables it. In memory, each call's regex matching shares one `regexTimeout` deadline (default 50 ms).
+- **Engine errors.** Failures raise `BeanQueryExecutionException` (a server error), whose message names no SQL or bind values.
+- **SQL engine.** Guards are parenthesized and AND-ed into every statement. There are fetch-size and query-timeout settings, and connections come from a `DataSource` or supplier and are released by the session. `stream` reads rows lazily and closes its cursor with the stream or the session.
+- **DataTables.** The `juneau-rest-server-datatables` adapter runs on the new session API. It asks for counts with `CountRequest.BOTH` on the query builder and maps `length=-1` to `maxLimit`.
+- **`BeanQuery.Builder`/`GroupBuilder`.** Replace the removed `BeanQueryBuilder`. Fluent `eq`/`ne`/`in`/`contains`/`prefix`/`gt`/`gte`/`lt`/`lte`/`between`/`regex`/`op`/`search` conditions, promotable into cross-column `$or`/`$and`/`$not` groups via `GroupBuilder`. Repeated conditions on the same column built through the fluent API no longer silently overwrite each other — they merge into an implicit `$and(...)`.
+- **One resolved search tree.** `Filter`/`FilterResolver`/`FilterEvaluator` give the in-memory and SQL engines a single, already-validated search tree to walk instead of each parsing search strings independently.
+- **Independently-optional counts.** `Page.hasTotal()` is gone; `Page.total()`/`Page.matched()` are `OptionalLong`, so a caller that did not ask for a count gets an empty `OptionalLong` back rather than an exception.
+- **DataTables global search box + legacy-search parity.** The DataTables adapter now maps the global search box to a synthesized `$or` across searchable columns, each with a leaf chosen by its declared `SearchType` (`$contains` for TEXT/ID, `$prefix` for VERSION, `$eqic` for ENUM, and `$eq` conditionally for NUMERIC/BOOLEAN/TIMESTAMP only when the global term parses as that type, otherwise that column is silently skipped) instead of `$contains` for every column, and reports counts throu [...]
+- **`RelativeDuration` literals.** A signed `[+-]<n>(ms|s|m|h|d)` wire literal (e.g. `-24h` for "24 hours ago") round-trips through the builder for `TIMESTAMP`/duration columns (parity with a common legacy search syntax).
+- **Search language hardening.** `$eq`/`$in`/`$ne` are now exact and case-sensitive, with a new case-insensitive `$eqic` counterpart; a `?` single-character wildcard joins the existing `*`; a quoted token bypasses wildcard interpretation entirely; `$$` is the literal-dollar escape; operator names are case-insensitive; `$regex` gains an optional `flags=i|m|s` argument; `SearchOperator` is now immutable (`create(...).build()`, with `copy()` for a pre-filled builder) with a typed `extension [...]
+- **Postgres dialect and typed binds.** `PostgresDialect` lives in its own module and package (`juneau-beanquery-postgres`, `org.apache.juneau.beanquery.postgres`). Search literals are parsed once per query by `SearchType.parse(String)` (`NUMERIC` to `BigDecimal`, `BOOLEAN` to `Boolean`, `TIMESTAMP` to `OffsetDateTime`/`LocalDateTime`/`LocalDate`) and exposed as `SearchExpression.typedValue()`/`typedArgs()`. A value that does not parse for its column type is `BeanQuerySyntaxException.Cod [...]
+- **ISO-8601 durations in BeanQuery.** Wherever a `NUMERIC` or `TIMESTAMP` column accepts a relative literal such as `-24h`, it now also accepts a signed ISO-8601 duration (`PT24H`, `-PT24H`, `+PT30M`, `P1D`) with identical semantics, in the in-memory and SQL engines and in `juneau-search.js`; invalid ISO text is `BAD_VALUE`. Existing literals and bare epoch milliseconds are unchanged.
+- **Built-in SQL row mappers.** `SqlRowMapper.map()` and `SqlRowMapper.bean(Class)` (records or setter beans), chosen automatically by `SqlBeanQueryContext.Builder.build()` when no `rowMapper(...)` is set.
+- **In-memory and SQL parity.** A SQL dialect that does not support an operator (such as `$regex`), or that does not declare `supportsVersionOrdering()` for the `VERSION` range operators, rejects that query with `BAD_VALUE` instead of silently returning different rows. `PostgresDialect` compares `ENUM` columns as text (wildcards included), compares `VERSION` columns as dotted-integer tuples, renders `$regex` with the POSIX `~`/`~*` operators (mirroring `flags=i|m|s`; Postgres and Java re [...]
+- **BREAKING:** `PostgresDialect` moved to `org.apache.juneau.beanquery.postgres`, and `SearchSqlRenderer`/`SqlDialect.renderBare` now receive typed `Object` values. See the [Migration Guide](/docs/topics/V10MigrationGuide).
+
+See the new [BeanQuery Package](/docs/topics/JuneauCommonsBeanQuery) topic page.
+
+## Build & packaging (BOM + curated bundles)
+
+### Published BOM + curated dependency bundles
+
+Juneau now publishes a **Bill of Materials** and a small set of **curated dependency bundles** so consumers can depend on a coherent, version-aligned slice of the framework without hand-maintaining `<version>` tags — while keeping Juneau's **explicit-over-magic** configuration model (no Spring-style classpath auto-configuration).
+
+- **`org.apache.juneau:juneau-bom`** (packaging `pom`) — import it (`scope=import`, `type=pom`) and drop explicit versions from your Juneau dependencies. Enumerates the consumer-facing Juneau modules plus the small set of starter-relevant third-party versions the bundles need (`micrometer-core`, `opentelemetry-api`, `jakarta.servlet-api`, `jakarta.validation-api`). It is **not** a whole-world third-party BOM, so it won't collide with a third-party BOM you import yourself.
+- **Three deployment-shape bundles** (packaging `pom`) — depend on one and transitively pull a coherent module set plus `juneau-rest-client`:
+ - `juneau-microservice-jetty-bundle`
+ - `juneau-microservice-tomcat-bundle`
+ - `juneau-springboot-bundle`
+
+```xml
+<dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-bom</artifactId>
+ <version>10.0.0</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+</dependencyManagement>
+```
+
+The bundles are **dependency aggregators only** — they register no configurations and perform no classpath auto-discovery. Configuration registration stays explicit via `Microservice.Builder.configurations(Class<?>...)`. This is a deliberate design choice: per the maintainer, *"I actually see auto-configuration as a weakness in Spring Boot that leads to applications with long startup times and impossible-to-understand architectures."* These bundles are also distinct from the clone-and-go [...]
+
+A reactor-consistency guard (`scripts/check-bom-completeness.py`, wired into the push gate) fails the build if a published module is missing from the BOM, so the enumeration can't silently drift. The binary distribution now also bundles `juneau-microservice-tomcat` (previously omitted).
+
+See the new [Dependency Management (BOM & Bundles)](/docs/topics/DependencyManagement) topic page for usage and the full explicit-over-magic rationale.
+
+### GraalVM native-image feasibility verdict + Docker layering guidance
+
+A feasibility assessment of GraalVM **native-image** support for Juneau's reflection-heavy marshalling/bean core landed a **CONDITIONAL GO** verdict: all reflection is funnelled through one wrapper layer (`org.apache.juneau.commons.reflect.*`, single `setAccessible()` choke point) with no `Unsafe`/`MethodHandle`/bytecode generation, and the `BeanContext`/`ClassMeta` model already computes the exact member set reachability metadata needs — making a Juneau-authored metadata emitter realist [...]
+
+## juneau-petstore (new module family)
+
+Three new modules under a top-level `juneau-petstore/` aggregator together form the canonical Juneau petstore showcase application. The legacy `juneau-examples-rest{,-jetty,-springboot,-jetty-ftest}` family of modules has been retired in this release in favor of the petstore family — see the **Breaking Changes** section below.
+
+### `juneau-petstore-core`
+
+Shared, deployment-agnostic surface consumed verbatim by both the Jetty and Spring Boot deployments. Owns:
+
+- **Domain beans** — `Pet`, `Order`, `User` (plus `Species`/`PetStatus`/`OrderStatus`/`UserStatus` enums) under `org.apache.juneau.petstore.dto`. Plain Juneau beans, no JPA/Hibernate/Derby.
+- **In-memory `PetStore` service** — `ConcurrentHashMap`-backed, lazy-loads classpath seed JSON at `petstore/init/{Pets,Orders,Users}.json` via `Json5Parser`. Auto-IDs via `AtomicLong`.
+- **`PetStoreResource`** — full CRUD `@Rest` servlet (`GET`/`POST`/`PUT`/`DELETE` over `/pets`, `/orders`, `/users`). Uses `org.apache.juneau.http.response.NotFound` (next-gen) and `@org.apache.juneau.http.Path`/`@Content`. Spring-agnostic — extends `BasicRestServlet` so both deployments mount it identically.
+- **View-engine demos** — the Mustache and Freemarker subtabs of the console's rendering-flavors page (`/console/dev/flavors/mustache`, `/console/dev/flavors/freemarker`) demonstrate the typed-`View` return path through the `juneau-rest-server-view-{mustache,freemarker}` bridges. Pure-classpath engines so both deployments inherit view rendering at parity. (They replace the retired `PetMustacheViewResource` / `PetFreemarkerViewResource`.)
+- **No-build React SPA demo** — `PetstoreUiResource` mounts `StaticFilesMixin` to serve `htdocs/petstore-ui.html`, a single-page React UI that calls `/petstore/pets` over `fetch()`. Demonstrates the headless-JSON + decoupled-SPA pattern alongside the server-rendered HTML-doc UI; no Node/npm in the build and no network access needed (React and ReactDOM 18.3.1 are vendored under `htdocs/vendor/`, MIT-licensed, acknowledged in the root `LICENSE`; the app uses plain `React.createElement`, so [...]
+- **Secure demo + `StubBearerTokenValidator`** — the console's bearer-token demo page (`/console/dev/secure`, with `SecureApiRest`) replaces the retired `PetSecureResource` (`/petstore-secure/*`); the auth-gated CRUD subset is gated by a `BearerTokenGuard` (fail-closed, op-level) backed by the in-memory stub validator. Demonstrates the bearer-token + `@Auth Principal` pattern; resource-Javadoc cross-references `AuthFilterChain` for multi-mechanism deployments.
+
+### `juneau-petstore-jetty`
+
+Jetty/`Microservice` deployment glue:
+
+- `App.main` calls `Microservice.create().configurations(JettyConfiguration.class, AppConfig.class)...`.
+- `RootResources extends BasicRestServletGroup` — mounts every core child resource plus the Jetty-only `Microservice` admin trio (`ConfigResource`/`LogsResource`/`ShutdownResource`), sourced from the new `juneau-microservice-examples` module (see Breaking Changes below).
+- Bundled `jetty.xml` and `juneau-petstore-jetty.cfg`.
+- Starter-zip assembly — sources + cfg + `jetty.xml` for IDE-import use.
+- In-module integration tests (`@JettyMicroserviceTest`, ephemeral port).
+- Default URL: `http://localhost:10000`.
+
+Launcher: `python3 scripts/start-petstore-jetty.py`.
+
+### `juneau-petstore-springboot`
+
+Spring Boot deployment glue:
+
+- `App` is a `@SpringBootApplication` with `@Bean RootResources` + `@Bean ServletRegistrationBean<Servlet>` mounted at `/*`.
+- `RootResources extends BasicSpringRestServletGroup` so child resources resolve as Spring beans (required for `HelloResource`'s `@Autowired HelloMessageProvider` injection demo).
+- Inherits `${spring.version}` from the repo parent.
+- `application.properties` configures port 5000.
+- `spring-boot-maven-plugin repackage` produces a runnable fat jar.
+- `@SpringBootTest(webEnvironment=RANDOM_PORT)` integration tests.
+- Default URL: `http://localhost:5000`.
+
+Launcher: `python3 scripts/start-petstore-springboot.py`.
+
+### Parity story
+
+The core-hosted surface (CRUD, console, view demos, React SPA demo, auth-gated subset) is identical across both deployments by construction — it's shared code, not parallel copies. The deployment-specific surface (Jetty's microservice console + admin resources vs Spring Boot's `@Autowired` injection demo) is intentionally non-parity and documented as such.
+
+### Petstore admin console (`/console`)
+
+The petstore now ships a complete admin console at `/console`, built on `console-ui` and offered as the worked example for the framework pages. See [juneau-petstore](/docs/topics/JuneauPetstore).
+
+- **Fourteen pages (P1-P14).** Each page has an FTL template and a Java builder twin (`?builder`), so the same page is shown both ways.
+- **Server-mode tables** driven by `BeanQuery`: filter, sort and page the pets, orders and users on the server.
+- **Staged pet edits** with the pending-changes badge (the photo field is excluded from staging).
+- **Restock async job** with a job-log card that follows the job as it runs.
+- **Audit trail** of console writes.
+- **Custom cards.** Petstore: `kpi` and `inventory-gauge` custom cards on `/console/dev/cards` demonstrate adopter card types.
+- **Four rendering flavors** (Builder, Html, Mustache and Freemarker) of the same pet list under `/console/dev/flavors`.
+- **Bearer-token demo** at `/console/dev/secure`, next to a CSRF boundary on `/console/*` in both deployments.
+- **Tests.** A Playwright browser harness exercises the pages, and FailLoud negative fixtures pin the error messages for mis-built pages.
+- **Retired legacy resources.** `PetInfoResource`, `PetHtmlResource`, `PetSecureResource`, `PetMustacheViewResource` and `PetFreemarkerViewResource` (and their `/petstore-info`, `/petstore-html`, `/petstore-secure` and `/pet-views` URLs) are gone, replaced by console pages. This is a breaking change for users of the sample only; the petstore is a sample, so it is not listed under Breaking Changes below.
+
+### Petstore legacy-surface completion
+
+The retired `juneau-examples-rest` family (see Breaking Changes below) carried six REST-servlet demonstrations that hadn't yet been ported into petstore or consciously dropped. That gap is now closed — three demos got petstore-flavored equivalents, and three were consciously dropped:
+
+**Ported to `juneau-petstore-core`, wired into both the Jetty and Spring Boot deployments:**
+
+- **`AboutRest`** (`/console/about`, replacing the retired `PetInfoResource` at `/petstore-info`) — utility-beans demo: `BeanDescription`/`Hyperlink`/`SeeOtherRoot` rendered against the petstore's own `Pet` domain bean.
+- **`HtmlFlavorRest`** (`/console/dev/flavors/html`, replacing the retired `PetHtmlResource` at `/petstore-html`) — HTML5 bean-builder demo: a hand-built HTML5 `Table` fragment of all pets rendered from live petstore data.
+- **`GET`/`PUT /petstore/pets/{id}/photo`** (added to `PetStoreResource`) — image upload/serve demo, backed by new petstore-local `PetPhotoSerializer`/`PetPhotoParser` classes (`org.apache.juneau.petstore.marshall`) and wired to the previously-unused `Pet.photo` field.
+
+**Consciously dropped (not ported):**
+
+- **ATOM-feed DTO serialization demo** — generic marshalling showcase with no petstore-domain tie-in; Atom (de)serialization itself remains exhaustively covered by `juneau-bean-atom`'s own test suite and docs.
+- **JSON-Schema DTO serialization demo** — same shape as the Atom case; JSON-Schema generation is already exhaustively covered by `juneau-bean-jsonschema`'s test suite and docs.
+- **Server-Sent-Events demo** — SSE transport mechanics are already integration-tested independent of any demo resource; a petstore-flavored SSE feature (e.g. live status updates) would be net-new feature work, out of scope for a port-or-drop cleanup.
+
+The plan's remaining stretch scope — a `@Remote` typed-client demo and a Config API + SVL var-resolver showcase — was split out to a separate follow-on work item rather than folded in here.
+
+## juneau-examples-core / juneau-petstore-core
+
+- **`image/jpeg` accepted:** the example `ImageParser` and the petstore `PetPhotoParser` now consume `image/jpeg` as well as `image/png` and `image/jpg`.
+
+## juneau-bean-rfc7807 (new module)
+
+A new bean module, `juneau-bean-rfc7807`, provides typed beans for [RFC 7807 — Problem Details for HTTP APIs](https://www.rfc-editor.org/rfc/rfc7807) (`application/problem+json`). RFC 7807 was obsoleted by [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) in July 2023, but the data model and the IANA media-type registration are unchanged.
+
+The module ships a single `Problem` bean covering the five canonical members defined in §3.1 (`type`, `title`, `status`, `detail`, `instance`) plus an open-ended extension-member fan-out per §3.2. Only depends on `juneau-marshall`.
+
+### Bean shape
+
+- `URI type` — nullable. `getType()` returns the raw value; `getTypeOrDefault()` returns `URI.create("about:blank")` per §3.1 when unset (without mutating the field).
+- `String title` — short human-readable summary of the problem type.
+- `Integer status` — OPTIONAL HTTP status code; serialized as a JSON number when set, omitted when `null`.
+- `String detail` — human-readable explanation specific to this occurrence.
+- `URI instance` — URI identifying the specific occurrence.
+- Extension members (§3.2) flatten into the top-level JSON object via a `@BeanProp("*")` triplet (`extraKeys()` / `get(String)` / `set(String, Object)`) over a private `Map<String,Object>`.
+
+A single static factory, `Problem.fromStatus(int status, String title, String detail)`, covers the common "build a problem from status code + title + detail" call site. Callers wire from `BasicHttpException` themselves with one line:
+
+```java
+Problem.fromStatus(e.getStatusCode(), e.getStatusLine().getReasonPhrase(), e.getMessage());
+```
+
+The bean does not eagerly default the `type` field, so a fresh `new Problem()` round-trips without a synthetic `"type":"about:blank"` on the wire.
+
+#### Example
+
+```java
+import org.apache.juneau.bean.rfc7807.*;
+import org.apache.juneau.marshall.json.*;
+
+Problem problem = new Problem()
+ .setType(new URI("https://example.com/probs/out-of-credit"))
+ .setTitle("You do not have enough credit.")
+ .setStatus(403)
+ .setDetail("Your current balance is 30, but that costs 50.")
+ .setInstance(new URI("/account/12345/msgs/abc"))
+ .set("balance", 30)
+ .set("accounts", List.of("/account/12345", "/account/67890"));
+
+String json = JsonSerializer.DEFAULT.serialize(problem);
+Problem back = JsonParser.DEFAULT.parse(json, Problem.class);
+```
+
+The matching `Content-Type` constants (`ContentType.APPLICATION_PROBLEM_JSON` and `ContentType.APPLICATION_PROBLEM_XML`) have shipped in `juneau-rest-common` since 9.2.x; this module closes the loop by adding the typed wire bean.
+
+#### Throw + map SPIs
+
+The module also ships the seams used by the new `juneau-rest-server` problem-details wiring (see [RFC 7807 / 9457 Problem-Details server-side wiring](/docs/release-notes/10.0.0-rest-server#rfc-7807--9457-problem-details-server-side-wiring) above):
+
+- **`ProblemException`** — `RuntimeException` carrying a `Problem`. Lets handlers throw a custom problem without manually building a `BasicHttpException`. The bean module owns this type so the `juneau-rest-server` processor can unwrap it without a reverse dependency.
+- **`ProblemMapper<T extends Throwable>`** — pluggable interface for translating arbitrary throwables into `Problem` shapes; the server-side processor discovers it via the resource bean store and dispatches in most-specific-first order across the exception hierarchy.
+- **`ProblemMapperList`** — aggregator bean for registering more than one mapper on the same resource (works around the single-slot collapse of multiple `@Bean public ProblemMapper foo()` factories that share a return type).
+- **`ProblemLocalizationStrategy`** — functional-interface SPI seam for future locale-aware translation of `Problem.title` / `Problem.detail`. Defaults to `ProblemLocalizationStrategy.IDENTITY` (pass-through). A reference `Messages` (resource-bundle) implementation is intentionally deferred to a future release.
+
+See [juneau-bean-rfc7807](/docs/topics/JuneauBeanRfc7807) for the full topic.
+
+## juneau-bean-hal (new module)
+
+A new bean module, `juneau-bean-hal`, models the [HAL (Hypertext Application Language)](https://stateless.group/hal_specification.html) hypermedia format ([draft-kelly-json-hal-08](https://datatracker.ietf.org/doc/html/draft-kelly-json-hal-08)) as Juneau `@Marshalled` POJOs. The beans round-trip cleanly through the default `JsonSerializer` / `JsonParser` for `application/hal+json` payloads — no custom marshaller required. Only depends on `juneau-marshall`. The matching `Content-Type` con [...]
+
+### Bean shape
+
+- **`HalResource`** — top-level HAL resource carrying the reserved `_links` map (`Map<String,Object>` whose values are single `HalLink` or `HalLinkArray`), the reserved `_embedded` map (`Map<String,Object>` whose values are single `HalResource` or `HalResourceArray`), and an open-ended payload via the `@BeanProp("*")` extra-properties triplet (`extraKeys()` / `get(String)` / `set(String, Object)`).
+- **`HalLink`** — all eight Link Object fields per draft-kelly-json-hal-08: `href` (required URI/URI-template), `templated`, `type`, `deprecation`, `name`, `profile`, `title`, `hreflang`.
+- **`HalLinkArray`** — `LinkedList<HalLink>` used as the multi-link branch inside an `_links` map. Mirrors the top-level-array pattern used by `JsonSchemaArray`.
+- **`HalResourceArray`** — `LinkedList<HalResource>` used as the multi-resource branch inside an `_embedded` map.
+- **`HalLinkOrArraySwap`** / **`HalResourceOrArraySwap`** — per-property `ObjectSwap`s applied to `_links` / `_embedded` so each map entry round-trips as either a single object or a JSON array per the spec.
+
+Convenience methods on `HalResource` make the spec's single-or-array shapes ergonomic: `addLink(rel, link)` sets a single-link value; `addLinks(rel, links...)` promotes the entry to a `HalLinkArray` (creating one or appending to an existing array as needed). The same pattern is mirrored for `_embedded` via `addEmbedded(rel, res)` / `addEmbedded(rel, res...)`.
+
+#### Example
+
+```java
+import org.apache.juneau.bean.hal.*;
+import org.apache.juneau.marshall.json.*;
+
+HalResource order = new HalResource()
+ .addLink("self", new HalLink("/orders/123"))
+ .addLinks("curies",
+ new HalLink("https://acme.example/{rel}").setName("acme").setTemplated(true))
+ .set("currency", "USD")
+ .set("total", 99.50)
+ .addEmbedded("customer",
+ new HalResource()
+ .addLink("self", new HalLink("/customers/7"))
+ .set("name", "Kelsey"));
+
+String json = JsonSerializer.DEFAULT.serialize(order);
+HalResource back = JsonParser.DEFAULT.parse(json, HalResource.class);
+```
+
+See [juneau-bean-hal](/docs/topics/JuneauBeanHal) for the full topic.
+
+## juneau-bean-jsonapi (new module)
+
+A new bean module, `juneau-bean-jsonapi`, models the [JSON:API v1.1](https://jsonapi.org/format/) wire format (`application/vnd.api+json`) as Juneau `@Marshalled` POJOs. The beans round-trip cleanly through the default `JsonSerializer` / `JsonParser` — no custom marshaller required. Only depends on `juneau-marshall`. The matching `Content-Type` constant (`ContentType.APPLICATION_VND_API_JSON`) has shipped in `juneau-rest-common` since 9.2.x.
+
+### Bean shape
+
+- **`JsonApiDocument`** — top-level container with `data`, `errors`, `meta`, `jsonapi`, `links`, `included`. Includes a `validate()` helper that enforces the document-level invariants: at least one of `data` / `errors` / `meta` must be present, and `data` and `errors` MUST NOT coexist.
+- **`JsonApiResource`** — resource object with `type`, `id`, `attributes`, `relationships`, `links`, `meta`. `type` is modeled as a plain `String` (and **not** as a `@Marshalled(typePropertyName="type", dictionary=...)` discriminator) because JSON:API `type` is an open-ended entity-type name chosen by the API; turning it into a Juneau dispatch discriminator would break round-tripping of unknown values and force every API to enumerate its resource types in a Java dictionary up front.
+- **`JsonApiResourceIdentifier`** — slim `type`/`id`/`meta` form used inside relationship linkage.
+- **`JsonApiRelationship`** — `data` linkage (`null`, single `JsonApiResourceIdentifier`, or list of them), `links`, `meta`.
+- **`JsonApiError`** + **`JsonApiErrorSource`** — error object with `id`, `links`, `status`, `code`, `title`, `detail`, `source`, `meta`; the `source` sub-object carries `pointer`, `parameter`, `header`.
+- **`JsonApiLink`** — link object with `href`, `rel`, `describedby`, `title`, `type`, `hreflang`, `meta`.
+- **`JsonApiVersion`** — the `jsonapi` member (`version`, `meta`).
+- **`JsonApiLinkOrStringSwap`** — per-property `ObjectSwap` applied to every `links` map (on `JsonApiDocument`, `JsonApiResource`, `JsonApiRelationship`, `JsonApiError`) so that each map entry round-trips as either a JSON string URL or a `JsonApiLink` JSON object per the spec.
+
+#### Example
+
+```java
+import org.apache.juneau.bean.jsonapi.*;
+import org.apache.juneau.collections.*;
+import org.apache.juneau.marshall.json.*;
+
+JsonApiResource article = new JsonApiResource("articles", "1")
+ .setAttributes(JsonMap.of("title", "JSON:API paints my bikeshed!"))
+ .putRelationship("author",
+ new JsonApiRelationship().setData(new JsonApiResourceIdentifier("people", "9")));
+JsonApiDocument doc = new JsonApiDocument()
+ .setJsonapi(new JsonApiVersion("1.1"))
+ .setData(article);
+
+String json = JsonSerializer.DEFAULT.serialize(doc);
+JsonApiDocument back = JsonParser.DEFAULT.parse(json, JsonApiDocument.class);
+```
+
+See [juneau-bean-jsonapi](/docs/topics/JuneauBeanJsonApi) for the full topic.
+
+## juneau-bean-jsonpatch (new module)
+
+A new bean module, `juneau-bean-jsonpatch`, models [JSON Patch (RFC 6902)](https://datatracker.ietf.org/doc/html/rfc6902) operation documents (`application/json-patch+json`) as Juneau `@Marshalled` POJOs with polymorphic dispatch on the `op` discriminator. Only depends on `juneau-marshall`. The matching `Content-Type` constant (`ContentType.APPLICATION_JSON_PATCH`) has shipped in `juneau-rest-common` since 9.2.x.
+
+### Bean shape
+
+- **`JsonPatchOperation`** — abstract base carrying the shared `path` (JSON Pointer / RFC 6901) field. Annotated `@Marshalled(typePropertyName="op", dictionary={AddOp, RemoveOp, ReplaceOp, MoveOp, CopyOp, TestOp})` so the parser materializes the correct concrete subclass keyed off the `op` member. Kept open (not `sealed`) so downstream callers can extend the dictionary with custom ops.
+- **`AddOp`**, **`RemoveOp`**, **`ReplaceOp`**, **`MoveOp`**, **`CopyOp`**, **`TestOp`** — the six standard operations from §4. `add`/`replace`/`test` carry a `value` payload (`Object`); `move`/`copy` carry a `from` JSON Pointer; `remove` has no extra members.
+- **`JsonPatch`** — `LinkedList<JsonPatchOperation>` representing a JSON Patch document, which serializes as a top-level JSON array per the RFC. Mirrors the `JsonSchemaArray` top-level-array pattern (rather than `@Json(wrapperAttr=...)`).
+
+#### Polymorphic Serialization
+
+`JsonPatchOperation` uses `@Marshalled(typePropertyName="op", dictionary={...})` to discriminate at the wire level. Configure your serializer with `addBeanTypes()` to emit the discriminator on output:
+
+```java
+JsonSerializer ser = JsonSerializer.create()
+ .addBeanTypes()
+ .typePropertyName(JsonPatchOperation.class, "op")
+ .build();
+
+JsonParser par = JsonParser.create()
+ .typePropertyName(JsonPatchOperation.class, "op")
+ .build();
+```
+
+#### Example
+
+```java
+import org.apache.juneau.bean.jsonpatch.*;
+
+JsonPatch patch = new JsonPatch()
+ .append(new TestOp("/a/b/c", "foo"))
+ .append(new RemoveOp("/a/b/c"))
+ .append(new AddOp("/a/b/c", java.util.List.of("foo", "bar")))
+ .append(new ReplaceOp("/a/b/c", 42))
+ .append(new MoveOp("/a/b/d", "/a/b/c"))
+ .append(new CopyOp("/a/b/e", "/a/b/d"));
+
+String wire = ser.serialize(patch);
+JsonPatch back = par.parse(wire, JsonPatch.class);
+```
+
+See [juneau-bean-jsonpatch](/docs/topics/JuneauBeanJsonPatch) for the full topic.
+
diff --git a/pages/release-notes/10.0.0-rest-client.md b/pages/release-notes/10.0.0-rest-client.md
new file mode 100644
index 0000000000..3f3934682f
--- /dev/null
+++ b/pages/release-notes/10.0.0-rest-client.md
@@ -0,0 +1,143 @@
+---
+id: "10.0.0-rest-client"
+title: "Release 10.0.0: REST client"
+---
+
+# Release 10.0.0: REST client
+
+This page is part of the [Release 10.0.0](/docs/release-notes/10.0.0) release notes. The overview page lists the major changes, the breaking changes and the API changes for the whole release.
+
+## juneau-rest-client / juneau-rest-common
+
+- **`juneau-rest-client` is generally available in 10.0.0 and is the recommended REST client.** The "Beta — API subject to change" labels are removed from its Javadoc and docs, and from the transport modules and the HTTP types in `juneau-rest-common` and `juneau-rest-mock`. `juneau-rest-client-classic` stays supported and is not deprecated.
+- **`RestRequest.timeout(Duration)` is now honored by every transport** (bug fix) — previously only `JavaHttpTransport` applied the per-request timeout; the Apache HttpClient 4.5/5, OkHttp, Jetty and mock transports silently ignored it. Each now uses its native per-request mechanism (HttpClient 4.5 `RequestConfig` socket/connect timeouts, HttpClient 5 request-scoped response timeout, OkHttp per-call read timeout, Jetty response-header wait, and a simulated handler deadline in `MockHttpTr [...]
+- **Apache HttpClient 5 transport: credentials on cross-origin redirects with a caller-supplied client** — with a caller-supplied `CloseableHttpClient`, the transport passes a per-request context that removes `Authorization`, `Cookie`, `Proxy-Authorization` and `X-API-Key` from a redirect hop to another origin (scheme, host or port) and from every later hop, whatever `RedirectStrategy` the client uses (including `LaxRedirectStrategy`); the client's own redirect settings are unchanged. On [...]
+- **Apache HttpClient 5 transport: cross-origin redirects followed with credentials stripped on the client it creates** (behaviour change) — when the transport creates its own HttpClient, a redirect to another origin on a request carrying `Authorization`, `Cookie`, `Proxy-Authorization` or `X-API-Key` is now followed with those headers removed from that hop and every later hop, as on the other transports, using the same per-request context. Previously HttpClient's `DefaultRedirectStrateg [...]
+
+### Secure-by-default NG REST *client* debug hardening (`RestClientDebugFormatter`)
+
+The next-generation `RestClient`'s debug logging pipeline (`RestClientDebugPipeline` /
+`BasicRestClientDebugFormatter`) now gets the same stable-emission and secret-hardening treatment the server side's
+`RestDebugFormatter` got above — ported onto the client's parallel classes, reusing the same
+`juneau-rest-common` primitives (`DebugTextSanitizer`, `RestDebugBodyScrubber`). This is an unreleased-10.0 **hard
+break** in the client debug formatter's output — there is no compatibility shim.
+
+- **Stable-`INFO` emission.** Every debug `LogRecord` — on both the normal response-close path and the
+ transport-exception synthetic-response path — is now always stamped `Level.INFO`; the resolved tier (`FINE`/
+ `FINEST`) continues to control message *detail* only, not the emitted record level.
+- **No body dumps by default.** Request/response bodies are **never** dumped unless an operator explicitly sets the
+ **same** `JUNEAU_REST_DEBUG_ALLOW_DUMP_BODIES` environment variable the server uses (byte-identical name and
+ trim-then-parse truthy semantics — one env var now gates both sides). With the gate unset, `FINEST` emits a
+ suppression placeholder + byte count instead of body content, replacing the previous unconditional UTF-8 + hex
+ dump. Even opted in, bodies render only for renderable text content (via a duplicated `isBodyRenderable(String)`
+ predicate on `RestClientDebugFormatter`, resolved independently per request/response side) and only for an
+ identity `Content-Encoding`; non-renderable/binary content and non-identity encodings yield a distinct "not
+ rendered" placeholder.
+- **`RestDebugBodyScrubber` opt-in**, registered via the new `bodyScrubber(...)` builder method on
+ `BasicRestClientDebugFormatter` — gate-subordinate and fail-closed, same contract as the server side: a throw or
+ `null` result falls closed to a placeholder, never the raw body.
+- **Widened, formatter-local header redaction**, matching the server's added set (`X-Auth-Token`,
+ `X-Authorization`, `WWW-Authenticate`, `Referer`, `Location`, atop the shared `RedactedHeaders.DEFAULT`), matched
+ with separator-normalized folding (`X-Auth-Token`/`X_Auth_Token`/`XAuthToken` all match).
+- **Log-forging sanitization.** The request method/URI, header names/values, and any dumped body text are escaped
+ (CR/LF and other control characters → `\r`/`\n`/`\uXXXX`), so a server cannot inject a newline into a response
+ header/body to forge a second physical log line.
+
+Out of scope for this port (client-specific rationale): the server's `maxHeaders`/`maxHeaderScan`/`maxFieldLength`/
+`maxUriLength` bounding knobs have no client-side analog, since the client's header source is already a finite,
+bounded list rather than an adversarial unbounded servlet input.
+
+See the [Logging and Debugging](/docs/topics/RestClientLoggingAndDebugging#secure-by-default) topic page for the
+full model.
+
+### Client-side request correlation (`X-Request-Id`)
+
+`RestClient` now auto-participates in end-to-end request correlation, pairing with the server's always-on
+`requestId` resolver above.
+
+- **Auto-send / mint.** Every outgoing request carries an `X-Request-Id` header; if the caller hasn't set one, the
+ client mints a time-ordered `Uuid7`. A caller-supplied header (builder-global or per-request) is honored, never
+ overwritten. `RestClient.Builder.sendRequestId(false)` opts out of auto-sending.
+- **Pre-call setter + getters.** `RestRequest.requestId(String)` sets the id for one call; `RestRequest.getRequestId()`
+ reads the sent id; `RestResponse.getRequestId()` exposes the id the server echoed back.
+- **Effective-id logging.** `RestClientDebugPipeline` stamps each emitted `LogRecord` with a structured `requestId`
+ field (echoed id when present, else the sent id) via `LogRecordContext.attachIfAbsent(...)`, and
+ `BasicRestClientDebugFormatter` prepends a `[requestId=<id>] ` marker to the rendered message — matching the
+ server's `statusLine` prefix so a request reads identically on both sides.
+- **Shared constants.** The `X-Request-Id` header name and max length live in a shared `RequestIdConstants` in
+ `juneau-rest-common`, consumed by both client and server.
+
+See the [Logging and Debugging](/docs/topics/RestClientLoggingAndDebugging#request-correlation-x-request-id) topic
+page.
+
+### Next-generation remote-proxy declarative features
+
+Juneau 10.0 significantly expands what the [REST proxy](/docs/topics/RestProxies) annotation family (`@Remote`, `@RemoteOp`, and the verb annotations `@RemoteGet` / `@RemotePost` / `@RemotePut` / `@RemoteDelete` / `@RemotePatch`) can express declaratively. These additions originally landed on the **next-generation** proxy engine only (`RestClient.remote(...)` → `RemoteClient`); a later classic/next-gen parity pass brought the classic `getRemote(...)` engine up to the same level for every [...]
+
+- **Constant part values** — `@Remote` (interface) and `@RemoteOp` / verb annotations (method) gain `headers` / `queryData` / `formData` members that emit always-applied constant headers, query parameters, and form-data fields on every call, with no dummy parameter. Method-level constants take precedence over interface-level ones, caller-supplied values still compose, and all values resolve through `VarResolver.DEFAULT`. Honored by both engines. (Constant path values are out of scope; `@ [...]
+- **`@Content(def=…)` honored** — the next-gen engine now applies both the parameter-level body default (when the body argument is `null`) and a param-less, method-level constant body, matching the classic engine.
+- **Per-part serializers** — `@HttpPartMarshalling(serializer=…)` is now wired for outgoing query / header / path / form-data parts, with precedence parameter › method › interface and a fallback to the default `OpenApiSerializer` when absent. (Serializer side only; the parser member is not consumed by the next-gen engine.)
+- **Declarative call policy** — `@Remote` and `@RemoteOp` / verb annotations gain `interceptors()`, `timeout()`, `retries()`, `retryNonIdempotent()`, and `throwOnError()`. Interceptors apply as a union (builder → interface → method); timeout/retries/throwOnError scalars follow method › interface › builder-default precedence. Auto-retries are conservatively safe: triggered only by connection failures or `429`/`5xx`, with exponential backoff, and gated to idempotent verbs (POST/PATCH requi [...]
+- **Dynamic URL & base override** — a new `@Url` parameter annotation (`org.apache.juneau.http.Url`) supplies the whole request URL at call time (absolute replaces and bypasses the client root URL; relative resolves against it), plus a declarative `baseUrl()` attribute on `@Remote` / `@RemoteOp` / verb annotations that substitutes the authority+root while preserving the path and `{var}` templating. Precedence: `@Url` › method `baseUrl` › interface `baseUrl` › client `rootUrl`. **`@Url` / [...]
+- **Declarative multipart** — a new method-level `@Multipart` marker (`org.apache.juneau.http.remote.Multipart`) plus a `@Part` parameter annotation (`org.apache.juneau.http.Part`, with `name`/`value`/`fileName`/`contentType`) build a `multipart/form-data` request from annotated parameters. Text, `byte[]`, `File`, `InputStream`, `Reader`, `HttpBody`, and bean part sources are accepted; file/stream/reader/bean parts stream rather than buffer. A method is either multipart or single-`@Conte [...]
+- **Per-method format selection** — `contentType()` and `accept()` attributes on `@Remote` / `@RemoteOp` / verb annotations drive marshaller **selection** (not just header values). `contentType` selects the matching request serializer and emits a single clean `Content-Type`; `accept` sets the `Accept` header and acts as a fallback parser (the response `Content-Type` stays authoritative). A no-match media type falls back to the default marshaller but still sends the overridden label (vend [...]
+- **Streaming efficiency** — POJO and `Reader` request bodies now stream straight to the wire instead of being buffered, and `Reader` / `InputStream` return types hand the caller a lazy stream over the live response whose connection is released on close.
+
+See the expanded [Declarative Remote-Proxy Features](/docs/topics/NextGenRestClient#declarative-remote-proxy-features-next-gen) section of the Next-Generation REST Client topic page for full examples and precedence rules.
+
+### Transport robustness — stale pooled-connection retry
+
+The next-generation REST client's HTTP transports now transparently retry a request **exactly once** when a pooled keep-alive connection is torn down by the server before any response bytes are received (an immediate `EOF` / connection reset) — a common source of intermittent failures against servers that idle-close persistent connections. The retry is gated on provable safety: an [idempotent](https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.2) HTTP method (`GET`, `HEAD`, `PUT`, [...]
+
+- **`JettyHttpTransport`** and **`ApacheHc45Transport`** gained this in an earlier 10.0.0 change; **`JavaHttpTransport`** (the default, JDK `HttpClient`-backed transport) now matches them. The JDK client self-retries only body-less `GET`s on such a failure, so the transport-level retry closes the gap for the other idempotent methods (e.g. `PUT` / `DELETE`).
+- A shared predicate, `TransportException.isStaleConnectionFailure(Throwable)`, recognizes the failure signature across transports (`EOFException` / `ClosedChannelException` / a connection-reset `SocketException` / the JDK's `"HTTP/1.1 header parser received no bytes"` message).
+
+## juneau-rest-client
+
+### REST session option wire helpers
+
+`RestClient.Builder` and `RestRequest` provide fluent helpers for serializer/parser session options using the same HTTP names as the server (`RestSharedConstants`): default and per-request headers (JSON5 strings or property maps via `Json5.of`), and default or per-request query parameters (UON strings or property maps via `Uon.of`). `MockRestClient.Builder` and `MockRestRequest` override these methods for fluent typing.
+
+### Dynamic Root URL Support
+
+- **`rootUrl(Supplier<String>)` overload**: `RestClient.Builder` now accepts a `Supplier<String>` for the root URL, allowing it to be computed dynamically at request time rather than fixed at build time.
+
+- **`RestClient.getRootUrl()`**: New method to inspect the currently resolved root URL from a built `RestClient`.
+
+ Three common patterns are supported:
+
+ **Swappable reference** — use an `AtomicReference` when you need to point the client at a different host at any time:
+ ```java
+ var urlRef = new AtomicReference<>("https://host1");
+ var client = RestClient.create().rootUrl(urlRef::get).build();
+
+ // Switch to a different host at runtime
+ urlRef.set("https://host2");
+ ```
+
+ **Refreshable cached value** — use `Memoizer` (via `Utils.memoizer()`) when computing the URL is expensive (e.g. service discovery) and you want it cached until explicitly refreshed:
+ ```java
+ var url = memoizer(() -> serviceDiscovery.findUrl("my-service"));
+ var client = RestClient.create().rootUrl(url).build();
+
+ // Force re-evaluation on the next request (e.g. after failover)
+ url.reset();
+ ```
+
+ **Purely dynamic** — use a plain lambda when the URL must be re-evaluated on every request:
+ ```java
+ var client = RestClient.create().rootUrl(() -> config.getRootUrl()).build();
+ ```
+
+ The supplier is called at each read site (request creation, remote proxy invocation, RRPC path building), so URL changes are always reflected on the next request. No build-time URL validation is performed for the `Supplier<String>` overload; invalid values will surface as errors when requests are made.
+
+### juneau-rest-client bug fixes
+
+- **Fixed stream-closed exception when calling `RestResponse.getContent().asString()` after FULL request logging.** `RestResponse.close()` previously called `EntityUtils.consumeQuietly(...)` on the underlying HTTP entity *before* rendering FULL-detail log output. On real (non-mock) responses the consume step closes the network input stream, so the subsequent FULL-logging body read (and any later user call to `getContent().asString()`) failed with `IOException: Stream closed`, leaving `Re [...]
+
+- **`RemoteClient` now treats `java.time` types as scalar parts rather than beans.** `Temporal` and `TemporalAmount` values (e.g. `Instant`, `LocalDate`, `Duration`) passed to `@Remote` proxy methods are now rendered as scalar request parts (path, query, header, form-data) instead of being introspected as beans.
+
+### juneau-rest-client migration notes
+
+- **RestClient `rootUrl`**: The `rootUrl` field is now stored as a `Supplier<String>` internally. Code that relies on reflection to access the private `rootUrl` field directly (unusual but possible) will now see a `Supplier<String>` instead of a `String`.
+
diff --git a/pages/release-notes/10.0.0-rest-server.md b/pages/release-notes/10.0.0-rest-server.md
new file mode 100644
index 0000000000..af32f7ad69
--- /dev/null
+++ b/pages/release-notes/10.0.0-rest-server.md
@@ -0,0 +1,2884 @@
+---
+id: "10.0.0-rest-server"
+title: "Release 10.0.0: REST server"
+---
+
+# Release 10.0.0: REST server
+
+This page is part of the [Release 10.0.0](/docs/release-notes/10.0.0) release notes. The overview page lists the major changes, the breaking changes and the API changes for the whole release.
+
+## juneau-commons / juneau-rest-server
+
+### `@ConfigProperties` — prefix-scoped whole-object config binding, with `RestContext` as the pilot
+
+Juneau 10.0 adds a new `@ConfigProperties(prefix = "...")` annotation (`org.apache.juneau.commons.inject`) —
+Juneau's analog of Spring Boot's `@ConfigurationProperties` — plus its underlying, independently-callable engine,
+`ConfigPropertiesBinder`. Where [`@Value`](/docs/topics/ValueAnnotation) resolves one configuration value onto one
+field, `@ConfigProperties` resolves an entire prefix onto every field of a POJO in one pass:
+
+```java
+@ConfigProperties(prefix = "MyService")
+public class MyServiceProperties {
+ public String host = "localhost";
+ public int port = 8080;
+}
+```
+
+- **Relaxed key matching by default** — reuses `RelaxedPropertySource.candidates(...)`, so `MyService.host`,
+ `MY_SERVICE_HOST`, and `my.service.host` are all equivalent.
+- **Same source-chain precedence as `@Value`** — thread-local/global test overrides, then any caller-scoped
+ `PropertySource` beans, then the process-wide `Settings` chain (registered sources, system properties,
+ system environment).
+- **Nested config objects** — a field whose type is itself `@ConfigProperties`-annotated binds recursively under
+ a sub-prefix.
+- **Coexists with `@Value`/`@Inject`** on the same class — fields owned by either are never touched by the
+ whole-object bind.
+- **Auto-registered in the `BeanStore`** — the bound instance is retrievable via `beanStore.getBean(...)` or
+ plain `@Inject` anywhere downstream.
+- **Current limitations:** no `Optional<T>`/`Supplier<T>` field types (raw-type conversion only); validation is a
+ no-op extension seam (`ConfigPropertiesValidator.NO_OP`) reserved for a future release.
+
+**Pilot: `RestContextProperties`.** As proof, `RestContext`'s 15 same-prefix `RestContext.*` env-driven-default
+`@Value` fields have been collapsed into a single public `@ConfigProperties(prefix = "RestContext")` bean,
+`RestContextProperties`, retrievable via the new `RestContext.getRestContextProperties()` or from the resource's
+`BeanStore`. Every existing `RestContext` getter keeps its exact signature and `@Rest`-annotation-blending
+behavior — only the source of the underlying default moved. `RestContext.uriAuthority`/`uriContext` (a
+null-vs-empty distinction the binder's raw-type conversion can't express in v1) are unaffected and
+remain plain `@Value` fields.
+
+See the new [`@ConfigProperties` Annotation Basics](/docs/topics/ConfigProperties) topic page, and the
+[RestContext](/docs/topics/RestContext#restcontextproperties--env-driven-defaults-via-configproperties-1000) topic
+page for the pilot.
+
+**`@Bean` factory-method fixes (`juneau-rest-server` / `juneau-commons`).** Several `@Bean(name=...)` factory methods of the same type on a `@Rest` class now each register their own result. Previously they collapsed into one bean, so `@Named("cache")` could receive the `"db"` bean, and health indicators were lost. Each `@Bean` method is invoked exactly once: overridden methods once, and named framework types such as `@Bean(name="encoders") EncoderSet` are no longer invoked twice or expor [...]
+
+## juneau-rest-common / juneau-rest-server
+
+### `BeanQueryRequest` request bean
+
+`org.apache.juneau.http.BeanQueryRequest` is a stock `@Request` class (a concrete `BeanQuery` subclass) that binds the usual list-fetch query parameters as a single method argument and feeds a BeanQuery session directly:
+
+```java
+private static final InMemoryBeanQueryContext<Row> ROWS = InMemoryBeanQueryContext.create(Row.class).build();
+
+@RestGet
+public List<Row> getList(BeanQueryRequest q) {
+ try (var s = ROWS.getSession(rows)) {
+ return s.find(q).rows();
+ }
+}
+```
+
+Each getter binds one single-valued `@Query` whose wire name equals the property name: `search`, `view` (a column projection), `sort`, `position`, `limit` and `opts` (renamed from `opt`). The `$`-language and the `key=value` opts bag live inside those single strings and are decoded by the session, not on the wire. Each getter carries a `@Schema` description, so an operation taking a `BeanQueryRequest` documents all six parameters in Swagger automatically (`position` also declares `minimum: 0`).
+
+A malformed query is reported as HTTP 400 with an `X-BeanQuery-Error` header and, with problem details on, a `"code"` member in the body; see **`Queryable` rebuilt on the BeanQuery engine** below. The `@Request` bean's constructor and setters are now verified when the operation table is built (see the Breaking Changes entry on request beans).
+
+Apps add extras by subclassing `BeanQueryRequest` and redeclaring a getter with its own `@Query` name (e.g. `@Query("v") getView()`). Saved-view CRUD endpoints must not take `BeanQueryRequest` (`view` there is a saved-view id, not a column projection).
+
+See the [BeanQuery Package](/docs/topics/JuneauCommonsBeanQuery) topic page.
+
+`@Request` getters now honor `collectionFormat=multi` (`getAll`, not last-wins) and `@Query(def=)` / equivalent `def=` on the other part annotations, including `Optional<T>` returns.
+
+### Content Security Policy (CSP) support
+
+Juneau can now emit `Content-Security-Policy` response headers for the HTML it serves, including dynamic per-request nonce support. New in this release:
+
+- A first-class immutable `ContentSecurityPolicy` HTTP header bean + fluent builder in `juneau-rest-common` (`org.apache.juneau.http.header`), with typed directive setters (`defaultSrc`, `scriptSrc`, `styleSrc`, `imgSrc`, `connectSrc`, `fontSrc`, `objectSrc`, `baseUri`, `frameAncestors`, `reportUri`/`reportTo`), a generic `directive(name, sources...)` escape hatch, source-expression helpers, report-only mode, and a ready-made nonce-based "strict starter" preset.
+- Per-response `SecureRandom` nonce generation: the same nonce is injected into the emitted `Content-Security-Policy` header and stamped onto every inline `<script>`/`<style>` Juneau emits — the HtmlDoc template, the Swagger UI, the OpenAPI UI, and menu-item widgets.
+- Opt-in by design: turning CSP on is a deliberate, one-line choice; existing apps are unaffected by default.
+
+See the new [Content Security Policy](/docs/topics/ContentSecurityPolicy) topic page for details.
+
+This feature was inspired by an earlier proposal from **Gary Gregory** ([@garydgregory](https://github.com/garydgregory), PR #57); the implementation shipped here is a fresh one built against the current 10.0 APIs.
+
+### CSRF protection for apps that are not on loopback (`CsrfBoundary`)
+
+`LoopbackBoundary` protects an app only when every request arrives under one pinned `Host` over plain HTTP. A new `CsrfBoundary` in `org.apache.juneau.rest.server.filter` covers everything else: an app reached under a real host name, over HTTPS, behind a proxy, or under more than one name.
+
+- **Same-origin check with nothing pinned.** On an unsafe request, `Origin` (with `Sec-Fetch-Site` as a fallback) must match the request's own scheme, host and port. A literal `null` origin is refused. Default ports are equated, and IPv6 literals are compared in bracketed form exactly as sent.
+- **Header token.** A browser request must also carry a per-session synchronizer token in a request header (`X-Csrf-Token` by default). HTML form posts cannot send a header, so they are unsupported by design.
+- **Non-browser clients.** A request with neither `Origin` nor `Sec-Fetch-Site` is admitted without a token only when a browser could not have sent it cross-site without a preflight (for client authors: send JSON, or send `Origin` plus the token). `NonBrowser.REJECT` and `NonBrowser.REQUIRE_TOKEN` tighten that.
+- **Proxies.** `OriginSource.HOST_HEADER` (the default) never reads `X-Forwarded-*`; `OriginSource.CONTAINER` takes the origin from the container's own forwarded-header support (Jetty `ForwardedRequestCustomizer`, Tomcat `RemoteIpValve`, Spring Boot `forward-headers-strategy`).
+- **DNS rebinding.** `allowedHosts(...)` refuses any request, safe ones included, whose host is not listed.
+- **Applied by `RequestBoundaryFilter`**, which works with either boundary. Register it at `/*` for `REQUEST` dispatches.
+- **The microservice admin samples.** `AdminBoundary` in `juneau-microservice-examples` registers a `CsrfBoundary` over `ConfigResource`, `LogsResource` and `ShutdownResource`, answering loopback host names only unless others are given, and the Jetty petstore installs it. See the microservice examples entry below.
+- **Bus WebSocket handshakes.** `BusPolicy.boundary(...)` now accepts a `CsrfBoundary`. A missing, `null` or malformed `Origin` is refused with HTTP 403 before the upgrade; a well-formed cross-site or mismatched `Origin` is refused after the upgrade with close code `4403`, before any frame is sent or any bus session is touched. The handshake has no server name, so it judges the raw `Host` header (over HTTP/2, the request URI's authority) plus the request URI scheme, whatever `OriginSourc [...]
+- **Opt-in trusted forwarded origin.** `CsrfBoundary.Builder.trustedForwarding(X_FORWARDED | FORWARDED)` and `trustedHops(n)` let a boundary judge HTTP writes and bus WebSocket handshakes against the host and protocol a proxy you operate forwards, for deployments whose container does no forwarded-host handling. Off by default with no behavior change. Host and protocol are both required, the `n`th entry from the right is used, the forwarded origin replaces the container's rather than addi [...]
+- **Proxy allow-list for the forwarded origin.** `CsrfBoundary.Builder.trustedProxies(cidrs...)` believes the forwarded header only from listed peer addresses (IPv4 and IPv6 ranges, validated at build; host bits are masked). Others carrying it are refused (`FORWARDED_PEER_NOT_TRUSTED`); others without it are judged as if forwarding were off. Off by default. HTTP where the container reports the TCP peer; WebSocket handshakes on Jetty; on Tomcat a forwarded handshake is refused while the l [...]
+
+Behavior changes in the same work:
+
+- **HTTP/2 requests with no `Host` header are judged on the container's `:authority` view.** `CsrfBoundary` no longer refuses them with `HOST_MISSING`, and `LoopbackBoundary` no longer refuses them with `HOST_MISMATCH`. Bus WebSocket handshakes over HTTP/2 (RFC 8441 extended CONNECT, which Jetty enables by default with HTTP/2) are judged on their `:authority` instead of closing with 4403. Under `HOST_HEADER`, HTTP/2 requests are judged on the container's server name, which container forw [...]
+- **`LoopbackBoundary` judges the effective method.** A `GET` carrying `?method=DELETE` or an `X-HTTP-Method-Override` header now takes the write checks, even when the resource it reaches would not honor the override (it now does honor it where `allowedMethodHeaders` is set; see the next bullet).
+- **Method overrides: all four headers honored, conflicts refused.** `X-HTTP-Method-Override`, `X-HTTP-Method` and `X-Method-Override` are now honored under `@Rest(allowedMethodHeaders)` (and the global `J_ALLOWED_METHOD_HEADERS`), exactly as `X-Method` is; the setting is still empty by default, so applications that have not set it see no change. A request whose enabled override channels disagree (two different methods across `?method=` and the headers, or repeated different values on on [...]
+- **BREAKING: method overrides on safe requests are restricted.** A `GET`, `HEAD`, `OPTIONS` or `TRACE` request may now be overridden (through `?method=` or any of the four override headers) only to a safe method (`GET`, `HEAD`, `OPTIONS`, `TRACE`) or to a custom method whose operation is declared `@RestOp(read=true)`. An override to any other method, for example `GET ?method=DELETE`, `GET ?method=POST` on an RRPC operation, or `GET ?method=FOO` reaching a `method="*"` operation, is refu [...]
+- **Rejections and token-bearing pages are `no-store`.** A rejected request is answered with `Cache-Control: no-store`. Code that stamps a token into a response (`ViewTable.CSRF_ATTR`, `RegionTable.of(region, csrfToken)`, `PageSpec.csrf(...)`) must call `RequestBoundaryFilter.markTokenBearing(response)`, which sets `Cache-Control: private, no-store`.
+- **No token in a committed response.** A token is never embedded into a response that is already committed, including `csrf` attributes an author put on the page's body element. The page's writes then fail visibly.
+- **Breaking (unreleased API):** the nested `LoopbackBoundary.Reason` and `LoopbackBoundary.Result` types are now the top-level `BoundaryReason` and `BoundaryResult`, with no aliases. Code built against a 10.0.0 snapshot needs the new names.
+- **Breaking (unreleased API):** the rejection header is now `X-Request-Boundary` (it was `X-Loopback-Boundary`).
+
+See [REST Security](/docs/topics/RestSecurity#csrf-protection-loopback-vs-general) and the [Views Security Model](/docs/topics/RichTableViewsSecurity).
+
+### Actuator-style management surface
+
+Juneau 10.0 adds a coherent, discoverable **Actuator-style management surface** while keeping Juneau's a-la-carte composability. Every endpoint ships in two independently-mountable flavors — a composable `*Mixin` and a routed `*Resource` sharing one worker — and a convenience `BasicActuatorGroup` assembles them under one configurable path prefix (default `/actuator`, overridable via the `juneau.actuator.path` system property).
+
+New endpoints:
+
+- **`/info`** (`juneau-microservice`, `org.apache.juneau.microservice.management`) — renders the running application's `ManifestFile` main attributes (build/version/git metadata). Degrades to `{}` when no manifest is registered. Build-time git/version stamping goes in the consumer's application-jar build (a `git-commit-id` + `maven-jar-plugin` recipe is in the topic page).
+- **`/loggers`** (`juneau-rest-server`, `org.apache.juneau.rest.server.management`) — read all logger levels and (deny-by-default) set a level at runtime. Drives `java.util.logging` by default; Logback / Log4j2 backends are an explicit opt-in (see the dedicated entry below).
+- **`/metrics`** (`juneau-rest-server-metrics-micrometer`) — renders a Prometheus scrape from a consumer-provided Micrometer `MeterRegistry` resolved from the bean store; degrades to `501` when no scrapeable registry is present. Never auto-registers a default registry.
+- **`/threaddump` + `/heapdump`** (`juneau-rest-server`) — server-agnostic `ThreadMXBean` thread dump and HotSpot `.hprof` heap dump, both **deny-by-default** on security grounds (opt in via `DumpsSettings`).
+
+Exposure is **on-by-default-with-gating**: non-sensitive reads are exposed; mutating/sensitive operations (`/loggers` set-level, the dumps) are deny-by-default behind explicit opt-in settings beans (`LoggersSettings`, `DumpsSettings`). No auth provider is auto-wired — the consumer supplies their own guard.
+
+**`BasicActuatorGroup` mounts only `/healthz`/`/readyz`/`/livez` and the dumps by default; `/info` and `/loggers` are off.** The convenience group composes `HealthMixin` + `DumpsMixin` only — `InfoMixin` and `LoggersMixin` are deliberately left out, since an unauthenticated manifest dump (`/info`) and logger-topology disclosure (`/loggers` read) are both meaningful information leaks with no guard auto-wired. Re-enable either (or both) with an a-la-carte subclass adding `@Rest(mixins={Inf [...]
+
+See the new [Management Surface](/docs/topics/ManagementSurface) topic page for the endpoint catalog, the `BasicActuatorGroup` one-liner, the exposure policy, and the manifest-stamping recipe.
+
+### `/loggers` non-JUL backends (Logback, Log4j2)
+
+The `/loggers` endpoint (above) now drives a pluggable **`LogBackend`** instead of being hardwired to `java.util.logging`. `juneau-rest-server` still ships only the JUL backend (`JulLogBackend`, the default) so the core stays dependency-free, and a new opt-in **`juneau-rest-server-management-logging`** module adds `LogbackLogBackend` + `Log4j2LogBackend` (the backends declared `provided`, so they never leak transitively).
+
+- **Explicit selection** (no classpath auto-detection, per the explicit-over-magic principle): declare the backend on the existing settings bean — `LoggersSettings.create().backend(new LogbackLogBackend()).build()`.
+- **Identical response shape across backends**: read reports the **configured** level (empty string = inherited from an ancestor; `ROOT` key for the root logger); set is **process-lifetime-only** (no config-file rewrite). Level names are backend-native; an invalid name fails `400`.
+- **Drives the concrete backend, not the SLF4J facade**: `LogbackLogBackend` for SLF4J→Logback, `Log4j2LogBackend` for Log4j2; an `slf4j-jdk14` binding is JUL, so the default backend already covers it. Custom backends implement `LogBackend` directly.
+
+This closes the 174b JUL-only `/loggers` limitation. See the extended [Management Surface](/docs/topics/ManagementSurface#loggers-backends-jul-logback-log4j2) topic page.
+
+### Host-side mixin overrides — `@Rest(mixinDefs=@Mixin(...))`
+
+`@Rest(mixins=...)` lets a mixin class declare its own `@Rest` settings that apply only to its endpoints — but the composition decision often belongs to the *host*. New in 10.0.0, the `@Rest(mixinDefs=@Mixin(...))` attribute lets a host declare a mixin **and** override selected `@Rest`-level settings for that mixin's endpoints, in one place, without editing or subclassing the mixin:
+
+```java fragment
+@Rest(mixinDefs=@Mixin(type=AdminResource.class, guards=AdminGuard.class))
+public class ApiResource extends BasicRestServlet { ... }
+```
+
+- **Additive / non-breaking.** `mixinDefs` coexists with the existing `mixins=Class<?>[]`; a `@Mixin(type=X.class)` with no overrides is exactly equivalent to a bare `mixins=X.class` entry, and existing usages are untouched.
+- **Override slots** mirror the composition-shaped `@Rest` attributes: `guards`, `roleGuard`/`rolesDeclared`, `converters`, `encoders`, `serializers`/`parsers`, `responseProcessors`, `restOpArgs`, `partSerializer`/`partParser`, `messages`, the default request/response headers + attributes, `produces`/`consumes`/`defaultAccept`/`defaultContentType`/`defaultCharset`, and `maxInput`.
+- **Precedence:** the host override layers on top of the inherited host chain (list-shaped props append, replace-shaped props win) and wins over the mixin class's own declaration. `@Mixin` carries its own `noInherit` (unioned with the mixin class's `@Rest(noInherit=...)`) so the host override is authoritative but overridable by `noInherit`.
+- **Host-chosen re-mount:** `@Mixin(path=...)`/`paths={...}` re-mount the mixin's endpoints under host-chosen prefix(es), reusing the existing `pathToken` normalization.
+- **Transitive mixins** are overridden only when separately named.
+
+Internally this also normalized mixin discovery (one `ResolvedMixin` carrier for both bare and rich forms) and replaced the `RestContext.Args` boolean `mixinContext` flag with a typed `ContextKind` discriminator (`Root`/`Child`/`Mixin`). See [Host-side overrides with `@Mixin`](/docs/topics/RestServerMixinSubContexts#host-side-overrides-with-mixin-1000).
+
+### Mixin-declared response-processor fold — `@Rest(mergeResponseProcessorsIntoHost=true)`
+
+By default a mixin's `responseProcessors` apply only to the mixin's own endpoints — a host's own `@RestOp` methods see only the host's chain. The host-side `@Mixin(mergeIntoHost=true)` directive can fold *all* of a mixin's list-shaped attributes into the host, but it must be spelled out at every use-site. New in 10.0.0, a mixin class can declare `@Rest(mergeResponseProcessorsIntoHost=true)` on **itself**, so that a plain `@Rest(mixins=ThatMixin.class)` reference automatically folds **onl [...]
+
+```java fragment
+@Rest(responseProcessors=FreemarkerViewRenderer.class, mergeResponseProcessorsIntoHost=true)
+public class FreemarkerMixin { ... }
+```
+
+- **Opt-in and non-silent.** Defaults to `false`; only a mixin that declares it changes behavior. Mixins that don't keep today's isolated scoping exactly — no change to the default for arbitrary existing mixins.
+- **Response-processor-scoped.** Folds only `responseProcessors` — never `guards`, `serializers`, or any other list-shaped attribute (the narrower, mixin-declared counterpart to the host-side, fold-everything `@Mixin(mergeIntoHost=true)`). Same-class de-duplication applies.
+- **View mixins opt in.** All four view-renderer mixins — `FreemarkerMixin`, `JspMixin`, `MustacheMixin`, `ThymeleafMixin` — now declare it, so a bare `@Rest(mixins=FreemarkerMixin.class)` (etc.) renders the host's own `View` returns through the engine with no `mergeIntoHost` or explicit `responseProcessors=` ceremony.
+
+See [REST Server — Mixin Sub-Contexts](/docs/topics/RestServerMixinSubContexts#folding-a-mixins-contributions-into-the-host) and [FreeMarker View Support](/docs/topics/FreemarkerViewSupport).
+
+### Host-side child seeding — `@Rest(childrenDefs=@Child(...))`
+
+Children are deliberately **isolated** from the host's resolution chain — a child's serializers, parsers, guards, part serializer, etc. resolve against its own `RestContext` only, never inherited from the parent. New in 10.0.0, the `@Rest(childrenDefs=@Child(...))` attribute lets a host **seed** a curated set of settings onto a routed child's otherwise-isolated context, without editing the child class — the child-resource analog of the `@Rest(mixinDefs=@Mixin(...))` attribute above:
+
+```java fragment
+@Rest(childrenDefs=@Child(type=AdminResource.class, partSerializer=AdminPartSerializer.class, guards=AdminBearerGuard.class))
+public class ApiResource extends BasicRestServlet { ... }
+```
+
+- **Additive / non-breaking.** `childrenDefs` coexists with the existing `children=Class<?>[]`; a `@Child(type=X.class)` with no seed members is exactly equivalent to a bare `children=X.class` entry, and if both name the same class the rich entry wins (the bare entry is upgraded in place).
+- **Two seed-semantics buckets** — since children are isolated by design, a seed can't "override" an inherited chain the way a `@Mixin` override does; it can only add to or fill gaps in the child's own config: **additive-security** (`guards`, `converters`, `roleGuard`, `rolesDeclared`) — the host's contribution is added alongside the child's own (list-shaped members prepend; the two role-based members AND-stack) and can never be removed or weakened by the child; **child-wins scalars** (` [...]
+- **`noInherit` cuts the seed too.** A child's own `@Rest(noInherit="<property>")` suppresses the corresponding host-seeded value, for either bucket, so the child always stays in full control of its own configuration.
+- **Survives lazy materialization** — a seed applies to a `@Rest(lazyChildren="true")` child on first request exactly as it would to an eagerly-built one.
+- **Not (yet) seedable:** `defaultAccept`/`defaultContentType` were originally planned but dropped before shipping — they resolve through a shared first-wins mechanism that would make the seed win over the child's own value, the opposite of every other child-wins scalar's contract.
+
+See [Host-side seeding with `@Child`](/docs/topics/ChildResources#host-side-seeding-with-child-1000) and [REST Server — Children vs Mixins](/docs/topics/RestServerChildrenVsMixins).
+
+### Child resources inherit their parent's `@Bean` group beans
+
+`@Rest(children=...)` child resources now see the `@Bean` methods and non-`null` `@Bean` fields declared on their parent (the "group"), instead of only the parent's bootstrap bean store. Each `RestContext` now has an **inheritable bean store** — its own `@Bean` results layered over its bootstrap store — and that store is used as the parent/bootstrap store of every child.
+
+```java
+@Rest(children={PetResource.class, OrderResource.class})
+public class RootResources extends BasicRestServletGroup {
+
+ // One store shared by every child resource.
+ @Bean
+ public PetStore petStore() {
+ return new PetStore();
+ }
+}
+```
+
+- **New API:** `RestContext.getInheritableBeanStore()` returns the store a resource exposes to its children.
+- **Transitive.** Each child's inheritable store sits on top of its parent's, so a grandchild sees the beans of its parent, its grandparent, and so on up to the root (including any Spring context bridged in at the root).
+- **Child wins.** A child's own `@Bean` of the same type and name takes precedence over the inherited one.
+- **Framework-managed beans stay per-context.** Serializers, parsers, the call logger, `RestChildren` and similar are not exported; each child still builds its own from its own `@Rest` configuration.
+- **Beans flow down only.** A child's beans are never visible to its parent or siblings.
+
+### Custom observations + one-dependency OTLP export + log correlation
+
+Juneau 10.0 extends its request-boundary observability to cover custom (non-request) observations, ships a single OTLP export bundle, and adds OpenTelemetry trace-id log correlation — all keeping the explicit-over-magic, off-by-default contract (no classpath auto-configuration).
+
+- **Custom-observation API** (`org.apache.juneau.rest.server.observation`): a new explicit programmatic API — `Observations.observe(name, tags)` (try-with-resources `Observation` handle), an `Observer` that composes the existing SPIs, and non-request default methods on the two SPIs (`MetricsRecorder.record(name, tags, elapsed, error)` + `TracerHook.startSpan(name)`). Instruments an arbitrary block of work with a timer + span; reuses the exact request-path `metricName`/`metricTags` model. [...]
+- **`juneau-observability-otlp-bundle`**: a curated dependency bundle (under `juneau-bundles`) that pulls both Juneau observability bridges plus a version-aligned OTLP exporter stack — `opentelemetry-exporter-otlp` (+ SDK + autoconfigure) for traces and `micrometer-registry-otlp` for metrics — in one dependency. Metrics + traces for v1 (OTLP logs export deferred). A dependency aggregator only; you still wire the `@Bean MetricsRecorder`/`@Bean TracerHook` explicitly.
+- **Log correlation**: the JUL `LogEntryFormatter` gains optional `{traceId}`/`{spanId}` placeholders, sourced reflectively from the active OTel `Span.current()` (no hard OTel dependency — they render empty when OTel is absent). `MdcAsyncListener` additionally folds the active trace/span id into the propagated SLF4J MDC snapshot (keys `trace_id`/`span_id`) so correlation survives the async-completion hop, gated by `isMdcAsyncPropagation()` and kept reflective. SLF4J→Logback/Log4j2 apps w [...]
+
+See the extended [Observability — Micrometer + OpenTelemetry](/docs/topics/RestServerObservability) topic page.
+
+### Resource-level authentication — `RestAuthenticator`
+
+Juneau 10.0 adds a `RestAuthenticator` SPI (`org.apache.juneau.rest.server.auth`) — an ergonomic, **in-resource** way to resolve a request's `Principal` + roles so `roleGuard`, `RoleBasedRestGuard`, `@Auth Principal`, and `req.isUserInRole(...)` all work with no container-managed security and no servlet-layer filter. It **complements** the [AuthN Filter Framework](/docs/topics/AuthFilterFramework) and reuses the same underlying auth implementations.
+
+- **Shared `Authenticator` contract** — a new `Authenticator` functional interface (`Optional<AuthResult> authenticate(HttpServletRequest)`) is now implemented by both `AuthFilter` and `AuthFilterChain`, so every existing filter (SAML / OAuth / JWT / API-key / bearer) is reusable verbatim at the resource layer. `RestAuthenticator.of(Authenticator)` adapts any of them.
+- **`AuthResult` merge modes** — `AuthResult` gains an immutable merge mode: `of(...)` = `ADD` (union roles, principal required — unchanged), `ofRoles(...)` = roles-only `ADD` (null principal — keeps the inherited identity), `replacing(...)` = `REPLACE` (swap identity outright). The existing `of(...)` factories and behavior are preserved (backward compatible).
+- **Registration & DI** — resolved per `RestContext` with precedence registered bean › `@Bean` method › `@Rest(authenticator=...)` › `RestServlet.createAuthenticator(BeanStore)`. Both `Authenticator`-into-`RestAuthenticator` and `RestAuthenticator`-into-resource injection work through the bean store.
+- **Inheritance & folding** — unlike guards, authenticators inherit down the child-resource tree: a single root authenticator covers all descendants, which may **augment** (add roles), **replace** (swap identity), or opt out with `@Rest(noInherit={"authenticator"})`.
+
+```java
+@Rest(children = { ChildResource.class })
+public class RootResource extends BasicRestServlet {
+ @Bean
+ public RestAuthenticator authenticator(AuthFilterChain chain) {
+ return RestAuthenticator.of(chain); // covers root + all descendants
+ }
+}
+```
+
+See the new [REST Authenticator — Resource-Level Authentication](/docs/topics/RestServerAuthenticator) topic page.
+
+### `Queryable` rebuilt on the BeanQuery engine + `juneau-rest-server-datatables` (new module)
+
+Juneau 10.0 replaces `Queryable`'s hand-rolled search/view/sort/paging code (`ObjectSearcher`/`ObjectSorter`/`ObjectViewer`/`ObjectPaginator`) with the shared [BeanQuery](/docs/topics/JuneauCommonsBeanQuery) engine, so one search language, one set of limits and one error model cover REST responses, DataTables server-side processing and SQL-backed queries. A new opt-in module also ships a **DataTables server-side processing** adapter plus browser-side helpers.
+
+- **`Queryable` on BeanQuery** — the `search` / `view` / `sort` / `position` / `limit` / `opts` request parameters (bound by [`BeanQueryRequest`](#beanqueryrequest-request-bean)) run against an `InMemoryBeanQueryContext` built from the response's row type (one context per element class, cached for the life of the converter; `Map` rows get a per-request context). Example: `GET /people?search=age=$gte(18)&sort=name,age:desc&view=name,age&position=20&limit=10`. Non-tabular results are retur [...]
+- **`QueryableSettings`** — a bean-store-resolved per-resource bean (mirroring `IntrospectableSettings`) that tunes the context: `defaultLimit` (100), `maxLimit` (1000), `allowRegex` (on by default, `allowRegex(false)` opts out), `regexTimeout`, `countPolicy`, and the search-clause / sort-key / search-length / expression-depth caps. Unset settings keep the BeanQuery builder defaults.
+- **Counts** — `?opts=counts=true` adds `X-BeanQuery-Total` and `X-BeanQuery-Matched` response headers (each only when that count was computed). The body stays a bare list.
+- **Central error mapping** — `RestContext.convertThrowable` maps `BeanQuerySyntaxException` to HTTP 400 with an `X-BeanQuery-Error: <CODE>` header (`BeanQueryRequest.ERROR_HEADER`) and, with problem details on, a `"code"` member in the body; `BeanQueryExecutionException` maps to HTTP 500 with the fixed body `Query execution failed.` (the cause is logged, never sent to the client). The DataTables adapter uses the same contract (next bullet).
+- **`juneau-rest-server-datatables` module** (`org.apache.juneau:juneau-rest-server-datatables`) — a new opt-in add-on (not bundled in `juneau-distrib`, mirroring the `juneau-rest-server-management-logging` precedent) with:
+ - **`DataTablesQuery`** (`org.apache.juneau.rest.server.datatables.adapter`) — maps the [DataTables server-side contract](https://datatables.net/manual/server-side) onto [BeanQuery](/docs/topics/JuneauCommonsBeanQuery): `DataTablesQuery.toBeanQuery(req, ctx)` builds a `BeanQuery` from a `DataTablesRequest` (per-column search, order, paging, and a global search that expands to an `$or` of a per-`SearchType` leaf — `$contains`/`$prefix`/`$eqic`/`$eq` — over every searchable, resolvable c [...]
+ - **Views runtime server-mode tables now ride the same wire format.** A `juneau-rest-server-views` table with `dataMode: "server"` now gets `serverSide: true` and `ajax: JuneauDataTables.ajax(url, {...})` (in `juneau-views.js`: `buildOptions` → `buildServerAjax`) instead of a hand-built GET config, so there is one wire format end to end. **Breaking for existing server-mode endpoints: they must now be `@RestPost` methods taking `@Content DataTablesRequest`, not GET handlers reading quer [...]
+ - **`DataTablesResults<T>`** — the `{draw,recordsTotal,recordsFiltered,data}` (+ optional `error`) envelope bean, rendered by the negotiated serializer (no hardcoded JSON).
+ - **Browser-side helpers** — `DataTablesColumns` (generate `columns:[…]` from bean-property order + `@Schema` titles), `DataTablesTable` (build a DataTables-ready HTML5 `Table` DOM bean, with `MarshallingContext` overloads), and `DataTablesMixin` (serves a first-party `juneau-datatables.js` auto-init glue script). DataTables' own JS/CSS and jQuery remain **caller-provided** (not vendored — their license is not ASF category-A).
+
+### Themeable admin-console chrome + FreeMarker macro composition (new modules: `juneau-rest-server-console-ui`, `-console-ui-freemarker`)
+
+Juneau 10.0 adds two opt-in modules that give an admin-console-style resource a themeable CSS "chrome", an enum status/domain pill component, and (for FreeMarker consumers) macro-based composition — without pulling a template engine or the DataTables add-on into `juneau-rest-server` core. The two modules form an acyclic dependency chain (`console-ui` → `console-ui-freemarker`); a chrome-only consumer never drags in FreeMarker or DataTables transitively.
+
+- **`juneau-rest-server-console-ui`** — `ConsoleChromeMixin` serves a dynamically themed `chrome.css` (the module's shipped structural stylesheet plus a `:root` block built from validated `--jc-*` theme tokens); `Theme` validates every token value through a **normalize-then-accept-known-safe** pipeline (reject C0/C1/DEL control chars → strip CSS comments → case-insensitively reject any `url(` production → match an allowlist grammar for colors/fonts/lengths/gradients) rather than a denyli [...]
+- Console theming is FTL-only: custom palettes are authored with `<@t...@token>` (`value=` for leaves, `alias=` for live `var()` references). `ThemePack`, `ThemeSettings`, `ThemePackSettings`, `ConsoleChromeMixin.Builder.pack(...)`/`pageBackgroundImage(...)` and the `/juneau-console/assets/logo`/`page-bg` endpoints were removed; `ConsoleChromeMixin.create().theme("<stock-name>")` selects a stock palette for the served `chrome.css`.
+- **Page Tabs and Page Subtabs** — a Page Tab is 38.5px tall: a 3px accent border, 8px/10px padding, and 13px text at line-height 1.5. A Page Subtab is 30px tall. The selected Page Tab and the selected Page Subtab are bold. The box is themeable through `--jc-nav-tab-padding-block`, `--jc-nav-tab-padding-inline`, `--jc-nav-tab-line-height`, `--jc-page-nav-child-padding-block` and `--jc-page-nav-child-padding-inline`.
+- `<@console theme="<stock-name>">` still selects a built-in stock theme (an unknown name is rejected fail-closed); a nested `<@theme name=>` inside the directive overrides it.
+- The `juneau-rest-server-theme-packs` module was removed.
+- **`juneau-rest-server-console-ui-freemarker`** — `ConsoleFreemarkerMixin extends FreemarkerMixin` (register it typed as `FreemarkerMixin`, mirroring the load-bearing-return-type rule `FreemarkerMixin` beans already follow) augments the resolved `Configuration`'s `TemplateLoader` with a second, classpath-root-rooted `ClassTemplateLoader`, without shadowing the consumer's own `basePath`-relative templates. This resolves the reserved `org/apache/juneau/console/base.ftlh` template (pulled [...]
+- **`DataTablesTable` additive `@Html(render=...)` honoring** (in the existing `juneau-rest-server-datatables` module) — `DataTablesTable.of(MarshallingContext, String, Collection<?>, Class<?>)` now resolves and applies each row property's `@Html(render=...)` the same way the ordinary `HtmlSerializer`/`HtmlDocSerializer` path already does, so a column whose bean property declares a custom `@Html(render=...)` (for example an enum rendered as a status pill) renders through it instead of as [...]
+- **`juneau-rest-server-console-ui-freemarker` — shareable-URL state and more datatables catalog keys.** The `views` toolkit pack now includes `juneau-urlstate.js` (after `juneau-pagestate.js`), so the View Settings Copy link button and shareable-URL state work on `<@page toolkit="views">` pages without loading the script by hand. `<@card type="datatables">` catalogs now pass `copyLink`, `rowActions`, `rowClassRules` and `pausePollingWhileEditing` through onto the view, and `selection` a [...]
+- **`juneau-rest-server-views` / `TableSpec` — `urlState` key and the `day(YYYY-MM-DD)` directive.** A table view can now opt in to built-in shareable-URL directives with `TableSpec.urlState(String...)` (or `urlState: {day: {}}` in a catalog; the page schema validates it). `day` stores a calendar date in `?state=` and passes it to the server as a `day` query parameter (rename it with `TableSpec.urlStateDayParam(String param)`, emitted as `urlState.day.param`) so a resource can return tha [...]
+- **`juneau-rest-server-console-ui-freemarker` — open card-type registry.** Unknown well-formed card types now pass through the server, and the JS shell fails them with E-JS-4. `<@card>` gains a `src=` attribute. A bare-URL `datatables` body is removed (it is now E-25), and `searchType` validation moves to render time (E-32). The bulk wire contract is `"2"` (aggregate mode, `BulkResult`). `<@card type="datatables">` is the one table surface.
+- **`QuickStat` gains meter and segmented kinds.** `QuickStat.meter(id, label, value, max)` paints a filled track with `role="meter"`, and `QuickStat.segmented(id, label).segment(...)` paints a proportional bar with a text legend. Both require a non-blank label. The new `statusTone(StatusTone)` sets an item tone from the shared status palette; `tone(QuickStat.Tone)` is unchanged.
+- **Inline datatables catalogs refuse a malformed `quickStats` strip (E-37).** This includes an off-palette tone on any item, plain tiles included. Such tones used to be dropped silently by the client.
+
+See the new [Admin-Console Chrome](/docs/topics/AdminConsoleChrome) topic page.
+
+### Console page contract and JS shell
+
+`<@console>` pages now carry a versioned JSON page contract (`#juneau-page`, schema
+`juneau-page.schema.json`) and load one shell script, `/juneau-console/juneau-console.js`, which draws
+the header, nav (any depth, `aria-current` from `activeNav` or a segment-aware URL fallback), cards and
+footer. The server keeps rendering `<head>`.
+
+- **New:** `PageContractAssert` (published test helper), `ConsoleTemplateValidator` (static template
+ lint), `ConsoleFreemarkerMixin.Builder.devMode(boolean)` (schema validation at render time),
+ `jcHasToolkit(name)`, `<@footer text="…"/>`, and `window.JuneauConsole` (`contract`, `card`,
+ `registerCard`, `mount`, `chrome`, and the `juneau:console-mounted` event).
+- **Loud failures:** template mistakes fail the render with E-1 to E-18; contract problems in the
+ browser show a `.jc-console-error` banner (E-JS-1 to E-JS-12, then E-JS-14).
+- **Fix:** a `@Bean` returning `ConsoleFreemarkerMixin` (rather than `FreemarkerMixin`) is now found;
+ previously it silently fell back to the default mixin without the console directives.
+- **BREAKING (10.0 snapshots only):** the page contract's top-level `version` key is renamed `contractVersion`,
+ matching every other Juneau envelope contract (`ModalDef`, `FormDef`, `BarSlot`, `ActionResult`, `RegionDef`,
+ the card/slot envelopes). The value is unchanged (`"1"`). There is no fallback: `juneau-page.schema.json` now
+ requires `contractVersion` and forbids `version`, `juneau-console.js` reads only `contractVersion`
+ (`JuneauConsole.SUPPORTED_VERSIONS` is now `SUPPORTED_CONTRACT_VERSIONS`),
+ `PageContractAssert.hasVersion(String)` is renamed `hasContractVersion(String)`, and `assertPage(...)`
+ throws if a contract still carries `version`. Pages are generated by `<@console>`, so upgrading Juneau is the
+ only step; code that hand-builds a page contract, or calls `hasVersion`, must be updated. Browsers that cached
+ the previous `juneau-console.js` (served with a one-day `max-age`) show an `E-JS-2` banner until the cache
+ expires or the page is hard-reloaded.
+- **BREAKING:** the FTL globals `pageBody`, `pageTab`, `pageInit`, `pageCss`, `pageToolkit`,
+ `pageToolkitCss`, `pageToolkitJs` and `pageThemeCss` are removed (use `jcHasToolkit("views")`).
+ The legacy chrome templates that did not use `<@console>` are removed; a `<@page>` whose chrome
+ renders no `<@console>` is an error. The `<@card>` types `js`, `json` and `calendar` are removed
+ (use `type="html"` with a body or a registered card type), as are the
+ `data-juneau-card-sidecar` scripts (no directive emits them any more; the orphaned
+ `juneau-page-cards.js` asset is deleted). `juneau-chrome.js` is absorbed into
+ `juneau-console.js`. Server HTML no longer contains nav, header, footer or card wrappers; tests that
+ matched that HTML must move to `PageContractAssert`.
+
+### Card asset dependencies
+
+Console cards now declare the scripts and stylesheets they need, and `<@page>` loads each one once, in dependency
+order. See [Card Asset Dependencies](/docs/topics/CardAssetDependencies).
+
+- **New:** `ToolkitPack` (packs with `dependsOn` and a `VENDOR` / `RUNTIME` kind), the built-in packs `jquery`,
+ `datatables`, `datatables-glue` and `datatables-buttons`, `<@card requires=...>`, and
+ `ConsoleFreemarkerMixin.Builder` methods `registerToolkitPack(ToolkitPack)`, `cardRequires(type, packs...)` and
+ `providedPacks(names...)`. `build()` fails on an unknown pack, a cycle, or a VENDOR pack depending on a RUNTIME
+ pack.
+- **New:** `<@card class="...">` adds one or more CSS classes to the card's wrapper element, so a page can keep its own scope class on the card. Each name must be a plain CSS class token or the render fails. See [Admin Console Chrome](/docs/topics/AdminConsoleChrome).
+- **New:** `WebJarsMixin` and `WebJarResolver` in `juneau-rest-server` serve `/webjars/*` from the classpath.
+ `juneau-rest-server-datatables` now depends on the jQuery and DataTables WebJars (DataTables 2.3.8, Buttons 3.2.6,
+ jQuery 3.7.1).
+- **Changed:** every `<@card type="datatables">` loads jQuery, DataTables and `juneau-datatables.js`, in client and
+ server mode. Previously the glue loaded only for server-mode cards and the page had to load jQuery and DataTables
+ itself. A host with datatables cards composes `WebJarsMixin` as well as `DataTablesMixin`.
+- **BREAKING (10.0 snapshots only):** `juneau-datatables.js` left the `views` pack.
+ `ToolkitPackRegistry.Resolved` is split into `vendorCss`, `vendorJs`, `runtimeCss` and `runtimeJs`, and the
+ `resolve(names, req, boolean)` overload is removed. A page that loads jQuery or DataTables through
+ `<@scripts phase="after-toolkit">` must drop those tags (they would load twice, after the glue). A copy loaded
+ through `init=` or `css=` is skipped only when its file name matches the pack's; an app with its own copies
+ should name the packs in `providedPacks(...)` or mount `WebJarsMixin`.
+- **Removed (10.0 snapshots only):** APIs left unused once every consumer moved to card-declared packs.
+ `DataTablesMixin.JQUERY_CDN_URL`, `DATATABLES_JS_CDN_URL` and `DATATABLES_CSS_CDN_URL` are gone; jQuery and
+ DataTables now come from the WebJars. `PageCapture.hasServerModeTable()` is gone, because every datatables card
+ now requires the `datatables-glue` pack. On `ToolkitPackRegistry`, the 2-arg `resolve(names, req)`
+ and the `register(name, cssPaths, jsPaths[, resolver])` overloads are gone; use `resolve(names, req, pageUrls,
+ template)` and `register(ToolkitPack)`. `ConsoleFreemarkerMixin.Builder.registerToolkitPack(name, cssPaths,
+ jsPaths)` and `providedPacks(...)` stay.
+
+### JUL-level-driven REST debug/logging (`RestDebugFormatter`)
+
+Juneau 10.0 replaces the multi-signal REST debug/logging system (`@Rest(debug)`/`@Debug`/`DebugEnablement`/
+`CallLogger`) with a single, JUL-logger-level-driven mechanism. This is a **hard break with no deprecation shim**
+(unreleased 10.0 surface) — see the [Breaking Changes](/docs/release-notes/10.0.0#breaking-changes) section below and the
+[V10 Migration Guide](/docs/topics/V10MigrationGuide#rest-debuglogging-redesign-jul-level-driven-restdebugformatter)
+for the full removed-symbol mapping.
+
+- **One signal.** A resource's own `java.util.logging.Logger` level is the sole control for request/response debug
+ capture — no annotations, no per-request headers, no separate config keys. Verbosity is cumulative: `INFO` =
+ basic, `FINE` = + headers, `FINEST` = + bodies (only when the body-dump gate is set — see the secure-by-default
+ entry below), one `LogRecord` per request (stable emitted level: `INFO`).
+- **Per-operation granularity** falls out of ordinary JUL parent-inheritance via a hierarchical child logger
+ (`resourceClass.getName() + "." + methodName`) — elevate one method to `FINEST` without touching the rest.
+- **Off by default.** Under stock JUL (nothing configured for the resource) no debug record is built or logged, so a
+ running service no longer writes one line per request. The status line appears only when a level of `INFO` or finer
+ is set explicitly on the resource's logger (or an enclosing logger such as its package), and `FINE`/`FINEST` add
+ headers/bodies as before. The record is not constructed at all when it would not be emitted. Apps that relied on the
+ per-request line appearing by default must now set the level, e.g. `Logger.getLogger("com.acme.MyResource").setLevel(Level.INFO)`.
+- **Spring Boot propagation path.** For real Logback backends, `logging.level.*` now propagates into JUL by default
+ (`juneau.rest.logging.propagate-levels=true`), with an additive default posture
+ (`juneau.rest.logging.propagate-levels.reset-jul=false`) and silent no-op fallback for non-Logback backends.
+- **New `RestDebugFormatter` SPI** (`org.apache.juneau.rest.server.logging`) — a per-tier formatter interface
+ (`formatBasic`/`formatHeaders`/`formatBody`) with a shipped `BasicRestDebugFormatter` default. It's the only
+ public extension point; the two-phase capture pipeline underneath it is internal. Resolution precedence:
+ resource-implements-the-interface > `BeanStore`-registered bean > `BasicRestDebugFormatter`.
+- **Secure by default (hardened).** `BasicRestDebugFormatter` no longer dumps bodies just because a logger was
+ raised to `FINEST`. See the dedicated secure-by-default entry below for the full contract.
+- **Bounded body buffering.** Request/response body-caching wrappers are installed only when the resolved logger
+ is loggable at `FINEST`, and buffer at most the configured cap rather than the entire stream — an improvement
+ over the prior unbounded debug body caching.
+- **Completion-path emission for async/reactive responses.** True asynchronous responses
+ (`CompletableFuture`/`CompletionStage`) and reactive SSE/NDJSON streams now emit their single debug record on the
+ response-completion path — after the async body and any completion-time headers have been written — instead of
+ during the synchronous request finish. This fixes incomplete capture where an async record could be emitted before
+ its late headers/body existed. A resolved logger/formatter/detail-tier snapshot is captured on the request thread
+ at the async hand-off and carried across the completion hop; finish-time attributes such as `Exec time` are
+ recorded on the completion path. Synchronous responses and the `MockRestClient`/non-async-container fallback are
+ unchanged (still emitted during synchronous finish), and `@RestEndCall` still runs on the request thread. Exactly
+ one `INFO` record is emitted per request on every path.
+- **Logger level controls verbosity only.** REST-driven marshalling debug behavior and `EchoMixin` reachability are
+ **decoupled** from the JUL level (see the dedicated entry below). `RestRequest.isDebug()` still exists, now derived
+ and read-only (`true` when the resolved logger is `FINE`-or-finer), but the framework no longer keys any behavior
+ off it.
+
+See the new [Logging / Debugging](/docs/topics/RestServerLoggingAndDebugging) topic page for the full model.
+
+### Decoupled `EchoMixin` reachability and marshalling debug from log level
+
+Two side effects that previously rode the JUL logger level are now controlled by their own explicit, non-logging
+switches — so raising a logger to `FINE`/`FINEST` changes verbosity only. This is an unreleased-10.0 **hard break**
+(no compatibility shim, no new echo annotation).
+
+- **`EchoMixin` is disabled by default.** `/echo/*` now returns `404 Not Found` regardless of logger level. Enable it
+ explicitly: `EchoMixin.create().enabled().build()`. Enablement is tri-state — an explicit `enabled(true)`/
+ `enabled(false)` wins; when unset it resolves the `${juneau.echo.enabled:false}` SVL fallback (system property /
+ relaxed environment variable). Because that fallback is JVM-global, prefer per-host builder enablement and pair it
+ with the host's inherited `@Rest(guards=...)` chain. This closes an accidental attack-surface expansion where
+ raising a logger to `FINE` silently un-hid a request-reflecting endpoint.
+- **Marshalling debug behavior has a dedicated cascading setting.** Serializer/parser debug behavior (richer/
+ stack-prefixed serialization exceptions and retained/quoted parse input in error messages) is now driven by
+ `debugMarshalling`, a `String` on all eight REST annotation types (`@Rest`, `@RestOp`, and the six HTTP-method
+ annotations) defaulting **OFF** at every level. Blank `""` means *inherit* (op inherits resource, which resolves
+ `${juneau.debugMarshalling:false}`); query at runtime via `RestRequest.isDebugMarshalling()`. A serializer/parser
+ configured with its own marshall-layer `Context.debug` remains independent — the REST layer never clobbers it.
+
+See [Logging / Debugging](/docs/topics/RestServerLoggingAndDebugging#interaction-with-marshall-layer-debug) and the
+[Ops / Introspection Mixin Pack](/docs/topics/OpsIntrospectionMixins) topic pages.
+
+### Secure-by-default REST debug hardening (`RestDebugFormatter`)
+
+`BasicRestDebugFormatter` is hardened so that raising a resource/operation logger to `FINE`/`FINEST` can never, by
+itself, leak plaintext credentials or hand a remote client a log-forging channel. This is an unreleased-10.0
+**hard break** in the debug formatter's output — there is no compatibility shim. Two reusable primitives land in
+`juneau-rest-common` (a `sanitize(...)` log-forging escaper and the `RestDebugBodyScrubber` SPI); the formatter
+changes land in `juneau-rest-server`.
+
+- **No body dumps by default.** Request/response bodies are **never** dumped unless an operator explicitly sets the
+ `JUNEAU_REST_DEBUG_ALLOW_DUMP_BODIES` environment variable (environment-only, no system-property fallback, read
+ once/cached, trim-then-parse truthy kill-switch semantics). With the gate unset, `FINEST` emits a suppression
+ placeholder + byte count instead of body content. Even opted in, bodies render only at `FINEST` and only for
+ renderable text content (via the new `isBodyRenderable(String)` predicate); non-renderable/binary content and
+ non-identity `Content-Encoding` yield a distinct placeholder.
+- **`RestDebugBodyScrubber` SPI** (`org.apache.juneau.http`) — an optional, gate-subordinate, fail-closed transform
+ (`String scrub(String contentType, String body)`, registered via `bodyScrubber(...)`) that chooses scrubbed-vs-raw
+ body text once dumping is permitted; a throw or `null` result falls closed to a placeholder, never the raw body.
+- **All header values masked against a widened set.** Every value of every credential-bearing header (not just the
+ first) is masked with `[REDACTED]`, using a widened formatter-local set (the shared `RedactedHeaders.DEFAULT` plus
+ `X-Auth-Token`, `X-Authorization`, `WWW-Authenticate`, `Referer`, `Location`) matched with separator-normalized
+ folding (so `X-Auth-Token`/`X_Auth_Token`/`XAuthToken` all match). `RedactedHeaders.DEFAULT` is unchanged. New
+ additive `addRedactedHeaders(...)` knob alongside the replacing `redactedHeaders(...)`.
+- **Log-forging sanitization.** The request method/URI (including the 404/unrouted path), header names/values, and
+ any dumped body text are escaped (CR/LF, NEL, line/paragraph/bidi separators, C0/C1 controls → `\r`/`\n`/`\uXXXX`;
+ TAB preserved), so a client cannot inject a newline to forge a second log line or a fake status banner.
+- **Bounded output.** New `maxHeaders` (100), `maxHeaderScan` (1000), `maxFieldLength` (1024), and `maxUriLength`
+ (2048) knobs bound header and URI output; the existing `bodyCap()` remains the single body cap.
+- **Finish-path containment.** A formatter or scrubber that throws during request completion can no longer escape
+ and fail a completed request — only a fixed diagnostic token is logged (never the secret, message, or stack).
+
+See the [Logging / Debugging](/docs/topics/RestServerLoggingAndDebugging#secure-by-default) topic page for the full
+model and the operator opt-in danger callout.
+
+### Always-on request correlation (`X-Request-Id` / `requestId`)
+
+Juneau 10.0 adds always-on, end-to-end request correlation on the REST server. A per-request `requestId` is resolved
+during session build — before any `@RestStartCall` hook — so it covers **every** code path that produces a debug
+record, including 404s and early-error paths, and gives the debug formatter's `statusLine` a synchronous
+`RestRequest.getRequestId()` to read.
+
+- **Mint-or-honor.** An inbound `X-Request-Id` request header is honored when present; otherwise a fresh time-ordered
+ `Uuid7` is minted. The resolved id is echoed on the `X-Request-Id` **response** header. Exactly one `requestId`
+ `LogContext` scope is opened per request and closed after the debug record is emitted (emit-before-close), so every
+ record on the request thread — the cumulative debug record and any handler-emitted records — carries the structured
+ `requestId` field, and the rendered message is prefixed `[requestId=<id>] `. The id also rides the async-completion
+ snapshot, so true-async completion records correlate identically.
+- **`RequestIdSettings` bean** (public write-surface, resolved by `RestContext` via a `getBean` → `@Bean`-factory →
+ default ladder): `validator(Predicate<String>)` defaults to **sanitize-and-accept** (strip log-forging sequences,
+ accept the bounded remainder — not reject-and-remint), plus `attributeKey(String)` and `idSupplier(Supplier<String>)`.
+- **`RequestIdFilter` reconciled to a thin façade.** Its `apply()` is now idempotent with respect to the
+ already-resolved session id (honor, never double-mint); its per-instance builder knobs are documented **no-ops** —
+ request-id behavior is configured on `RequestIdSettings`.
+- **Auth non-influence.** `X-Request-Id` has zero influence on authentication/authorization outcomes; configuring an
+ auth guard to read it is an anti-pattern (regression-tested).
+
+See the [Logging / Debugging](/docs/topics/RestServerLoggingAndDebugging#always-on-request-correlation-requestid)
+topic page.
+
+## juneau-rest-server-widgets / juneau-rest-server-views
+
+### Shared endpoint-safety predicate (`juneau-commons`)
+
+A new `org.apache.juneau.commons.http.SafePathTemplate` centralizes the same-origin/non-templated URL check and the
+`MIN_POLL_INTERVAL_MS = 5000` poll floor. The views toolkit's region and polling definitions call it, and the
+client-side runtime re-implements the same rule as defense-in-depth.
+
+### `AsyncJobRegistry` de-duplicates jobs by `IdempotencyKey`
+
+`AsyncJobRegistry.tryCreate(IdempotencyKey)` and `create(IdempotencyKey)` register a job under the key's value; submitting again with a key that is already registered returns the **existing** job instead of creating a second one, so a double-click, an impatient re-submit or a browser retry collapses to one job.
+
+- **Atomic.** The lookup-or-create is atomic — concurrent submits with one key yield exactly one job.
+- **Finished jobs replay.** A key whose job has finished returns that terminal job and its result (the same replay-on-duplicate contract as `IdempotencyKey`) rather than triggering a second run. This holds until the job is swept after the 60-second retention window; after that the key is forgotten and the same key starts a fresh job.
+- **Duplicates are free.** A repeat request creates nothing, so it does not count against `MAX_CONCURRENT_JOBS`.
+
+### Async job streams resume from `Last-Event-ID` (BREAKING)
+
+Async job streams (`/juneau-jobs/{jobId}/stream`) now tag each progress event with an SSE id and resume from
+`Last-Event-ID` (or `?lastEventId=`). A fresh attach no longer replays earlier progress. The job-log card asks for the
+full history itself.
+
+- **Live-only default (BREAKING).** A fresh attach with no resume value no longer replays buffered progress. This
+ affects every `EventSource(streamUrl)` client, including third-party callers of the HTTP API. Add
+ `?lastEventId=0` to get the whole history.
+- **New `lastEventId` query parameter** on `GET /juneau-jobs/{jobId}/stream`. A non-empty `Last-Event-ID` header
+ beats it, so a browser's own reconnect resumes where it left off.
+- **`400` for a bad or past-the-end resume value.** The status checks run in the order `404`, then `400`, then `429`.
+- **An ended job still sends its `result`** on every attach, fresh attaches included.
+- **Job-log card:** connects with `?lastEventId=0` and no longer de-duplicates lines by count.
+
+See [Async Jobs](/docs/topics/RichTableViewsAsyncJobs#resuming-a-stream).
+
+### Datatable View Settings (`juneau-rest-server-views`)
+
+The View Settings dialog is reworked so that saved choices actually take effect on load.
+
+- **Applied on load.** Stored View Settings (columns, search membership, sort, page size, wrap, density, auto-refresh) are restored when the page loads, not only when the dialog opens. This fixes a hidden column reappearing after a reload, in both client and server data modes.
+- **Ordered Sort tab.** Sort is a priority list of column and direction entries, and an untouched Apply keeps the view's `defaultOrder` instead of persisting an ascending sort over every column.
+- **Auto-refresh.** The Options tab gains an auto-refresh interval (Off, 30 s, 1 m, 5 m, 15 m), stored as `autoRefreshMs`.
+- **Per-view tabs.** `columnConfig: {tabs: [...]}` restricts the dialog to a subset of `columns`, `search`, `sort` and `options`.
+- **Accessibility.** Dialog tabs carry `aria-selected`/`aria-controls` with arrow-key navigation, every row control's label names its column, and a polite live region announces search results and a dismissed, unapplied search.
+- **Copy link** button beside the gear, shown for the primary (or only) view table.
+- **Column search popover** shows a per-type help line (the text of `SearchType.bareHelp()`).
+- **BREAKING:** the `columnSearchToggle` ribbon type is removed. The ribbon ignores an unknown type silently, so an adopter just loses the button; per-column search is the search icon in each header cell.
+- **BREAKING:** the saved View Settings `schemaVersion` is now `2`, and `NS.config.readViewSettings` returns `{settings, reset}`. A saved blob at version 1, or with the old string-array sort, is discarded with a one-time notice; saved-view blobs at version 1 are rejected too. A stored blob is ignored for tables without `columnConfig`.
+- **BREAKING:** `NS.config.defaultSortMembership` is removed.
+- **BREAKING:** the Options blob gains `autoRefreshMs` (old blobs read as Off), and `isPollSuspended` now also returns `true` while the View Settings dialog is open, which pauses any existing `pausePollingWhileEditing` view.
+
+See the new [Datatable View Settings](/docs/topics/DatatableViewSettings) topic page.
+
+### Run-view region (`juneau-rest-server-views`, `juneau-run-report`)
+
+New run-view region: a multi-step run summary driven by a small, versioned event stream, with
+`<@card type="run-view">`, `RunViewMixin`, and readers that turn test-report files into events.
+
+- **New module:** `juneau-run-report` (package `org.apache.juneau.rest.server.runreport`) holds `RunEvent`, the
+ grammar checks (`RunViewChecks`) and the report readers. It does not depend on the REST server, so a producer can
+ use it alone; `juneau-rest-server-views` depends on it.
+- **Events:** `RunEvent` factories for `step`, `end`, `suite`, `test`, `replace`, `note` and `done`
+ (`contractVersion` `"1"`). The client reduces them to a failures-first summary, a step list, and per-suite
+ strips of test blocks with tooltips; counts stay exact past the retention caps.
+- **Sources:** `RunViewLog` (in memory) and `FileRunViewSource` (a growing JSON Lines file, with `410 Gone`
+ after truncation). Implement `RunViewSource` for anything else.
+- **Endpoint:** `RunViewMixin` serves `/juneau-run-view/{runId}/events`; `RunViewEndpoints` gives the same
+ logic for custom paths.
+- **Report readers:** `ReportReaders.forKind(...)` reads Surefire, JUnit XML, pytest, Jest JSON and
+ Playwright JSON reports without throwing on bad input, and `ReportResult.toEvents(step)` feeds the region.
+- **Re-run steps:** steps named `<id>.2`, `<id>.3` (titled `(attempt N)`) collapse under the latest attempt, behind
+ an "N earlier attempts" toggle that starts collapsed.
+- **Client:** `juneau-run-view.js` joins the `views` toolkit pack and publishes `JuneauViews.runView`.
+ `poll: false` regions take events through `append`, so any transport (such as SSE) works.
+- **Errors:** the region reports E-RV-1 to E-RV-9; the console shell adds E-JS-14. The console shell's
+ unknown-card-type message now lists `html|datatables|run-view`.
+
+See the new [Run View Region](/docs/topics/RunViewRegion) topic page.
+
+### Terminal region (`juneau-rest-server-terminal`)
+
+New terminal region: a command's raw output rendered by xterm.js, colours, redraws and all, with
+`<@card type="terminal">`, `TerminalMixin`, and a launcher that runs commands under a PTY.
+
+- **New module:** `juneau-rest-server-terminal` (opt-in). It depends on `juneau-rest-server-views` and the xterm.js
+ WebJar `org.webjars.npm:xterm__xterm` 6.0.0.
+- **Endpoints:** `TerminalMixin` serves `/juneau-terminal/{id}/bytes` (raw bytes by offset, with `Term-*` headers)
+ and `/raw`; `TerminalEndpoints` gives the same logic for custom paths.
+- **Sources:** `FileTerminalSource` (a growing log with a `.size` sidecar) and `MemoryTerminalSource` (a ring buffer).
+- **Launcher:** `TerminalProcess` runs a command under `juneau_run.py --pty` and serves its log and events.
+- **`juneau_run.py`:** new `--pty`, `--size COLSxROWS` and `--events PATH` options, plus `--input stdin` and
+ `--prompts stdio` to pass input or the job's prompt channel through. Non-PTY runs are unchanged.
+- **Prompts:** `TerminalProcess.start(..., TerminalPrompts)` relays the job's prompt lines and sends answers back
+ with `answer(jsonLine)`; closing it cancels the job.
+- **Run view:** `step`, `suite` and `test` events gain an optional `rawOffset`; `rawHref` takes `{offset}` and
+ `onBlockClick` gets `offset`. `rawLine`, `{line}` and `info.line` are gone. The contract version stays `"1"`.
+- **Console UI:** the `terminal` card type and the `xterm` and `terminal` toolkit packs. `terminal` is now a reserved
+ card type name. It replaces the console-output card.
+- **xterm options:** the client takes an allowlisted `terminal` object (`convertEol`, `scrollback`, `fontFamily`) in
+ `mount` and on the `terminal` card; other keys are ignored. Server-built cards set it through the new
+ `TerminalDef.terminal(Map)` builder method or the card body, and the server rejects unknown keys and bad values.
+
+See the new [Terminal Region](/docs/topics/TerminalRegion) topic page.
+
+### `juneau-run` (new module)
+
+New build tooling: `juneau_run.py`, a stdlib-only Python wrapper that runs Maven, pytest, Playwright, Gradle, npm and
+Bazel commands, parses their output into steps and test counts, and draws a board console view. Juneau's own
+`test.py`, `push.py` and `release.py` run on it. It is attached to `org.apache.juneau:juneau-run` as a `py` (the
+engine), a `zip` (`bootstrap.py`, the `juneau_scripts` toolkit, the pytest plugin and the Playwright reporter) and a
+`py` with classifier `pytest`. It is build tooling, so `juneau-bom` does not manage it.
+
+- **Console view:** `--console full|board|none` (or `JUNEAU_RUN_CONSOLE`), one row per step with a progress bar,
+ counts and a summary line, and `JUNEAU_RUN_DETAIL` (`summary`, `actionable`, `modules`, `all`) for how much a
+ finished step keeps. `--full-log` and `--board-log` write the raw output and the rows.
+- **Run history:** per-step timings and test counts under `~/.cache/juneau-run/history`, shared by branches and
+ worktrees of one repository, give percents, estimates and an Overall line (`JUNEAU_RUN_HISTORY`,
+ `JUNEAU_RUN_HISTORY_DIR`, `JUNEAU_RUN_HISTORY_KEY`).
+- **Run-protocol markers:** `RUN_MARKERS=1` prints `##run <json>` events for a runner UI, and `RUN_ARTIFACTS` keeps
+ copies of the test reports.
+- **Maven rows:** one block per module. A single-project build (a one-module tree, or `-pl` without
+ `--also-make`) draws one module and counts `1/1` on success. A build that stops before `test` (goals up to
+ `test-compile`, or `-DskipTests`) still draws its module bar, counting modules as they compile, with no test totals.
+- **Pytest plugin:** `-p juneau_run_pytest` is added to pytest runs (opt out with `JUNEAU_RUN_PYTEST_PLUGIN=0`), so
+ the bar has one block per test file and shows the running file. Without the plugin, the bar keeps one block per test.
+- **Playwright reporter:** added through `PW_TEST_REPORTER` beside the config's reporters (opt out with
+ `JUNEAU_RUN_PLAYWRIGHT_REPORTER=0`). It gives one block per test file, counts a retried test once, writes
+ `<step>-playwright.json`, and off a terminal sets `PLAYWRIGHT_HTML_OPEN=never`.
+- **PTY mode:** `--pty`, `--size`, `--events`, `--input stdin` and `--prompts stdio`; this is what `TerminalProcess`
+ runs (see the Terminal region section above).
+- **Cancelling:** each tool runs in its own process group. Ctrl-C or SIGTERM sends SIGTERM to the group, then SIGKILL
+ after a grace period, so forked JVMs and browsers don't outlive the run; the run ends as `cancelled` (exit 130).
+- **Script toolkit:** `juneau_scripts` gives push and release scripts git helpers, a session with exit codes, tool
+ runners, resumable steps, locks, notifications and prompts, loaded through the vendored `bootstrap.py`.
+- **Juneau build rows:** `test.py` draws Compile, Java tests, Python tests and Browser tests (always shown, marked
+ skipped with the reason when not run); `push.py` adds Prechecks, Tags, BOM, Timing, Install, Commit and Push.
+- **Wrapping:** every line of a wrapped bar holds the same number of cells as the first.
+- **Expected tests:** a running row shows the expected test total from previous runs, e.g. `41,200 / ~83,000 tests`.
+- **Steadier estimates:** the estimated total time changes only when a new estimate differs by more than 15 seconds
+ or 10%, whichever is larger, and is rounded to 5, 15 or 30 seconds by size. The overall `~X left` figure behaves the same way.
+
+See the new [juneau-run](/docs/topics/JuneauRun) topic page.
+
+### Console chart and job-log cards
+
+Two new console card types. Both load their script on demand through a toolkit pack, so pages that don't use
+them load nothing new.
+
+- **`chart` card:** line, bar and donut charts drawn as inline SVG by `juneau-charts.js`, from a `src=` endpoint
+ or inline `data`, with optional `refreshMs`, click-through, a data-table view and an open kind registry
+ (`JuneauCharts.register`). Built from Java with the new `ChartDef` builder.
+- **`job-log` card:** a form that starts an `AsyncJobsMixin` job, streams its progress lines, shows the outcome,
+ offers Cancel, and reconnects after a reload. A dropped stream resumes after the last line shown, using the
+ server's `Last-Event-ID` support (see "Async job streams resume from `Last-Event-ID`" above). Built from Java with `CardSpec.of("job-log", id)`.
+- **Reserved:** `job-log` is now a reserved card type.
+
+See the new [Chart Cards](/docs/topics/ChartCards) and [Job-Log Card](/docs/topics/JobLogCard) topic pages.
+
+### Row jobs and the row-aware job-log card (`juneau-rest-server-views`, `juneau-rest-server-console-ui`)
+
+A job can now be started for one table row, from the row or from its detail panel, with a key the server binds to
+that row.
+
+- **`JuneauViews.jobs`:** `run` (submit, stream from the start, settle, cancel, with `onStart` / `onProgress` /
+ `onResult` / `onEnd` hooks and an optional `paint: "none"`), `fetchKey`, `active`, `pending` and `cancel`. It allows one job
+ per row, takes the row id only from the row's stamp, and substitutes `{id}` only. `"jobs"` joins
+ `NS.HOOK_NAMESPACES`.
+- **`helpers.jobButton`:** a button that runs a row job through `JuneauViews.jobs`.
+- **`JobKeyLedger` and `RowJobSubmit`:** server-side keys bound to an action, a row id and the caller, issued by a
+ key endpoint, verified on submit, and spent once their job exists. A key for one row is refused for another with
+ `409` `write-guard:idempotency-binding-mismatch`; a spent key replays its job or answers
+ `write-guard:idempotency-key-spent`, so a replay never starts a second job.
+- **Job-log region:** the job-log card now runs inside a row-detail region (`populate: 'job-log'`, built from Java
+ with `JobLogRegion`). It gains `submit.envelope` / `submit.action`, `hidden` fields, `source: 'row.<field>'` presets
+ and `output`, which writes a result value into the card or panel. A datatables card with a job-log region loads the
+ `joblog` pack by itself, through the new `CardTypeHandler.requiredPacks(JsonMap)`. `window.JuneauJobLog.mount` mounts
+ one programmatically. `JobLogChecks` holds the validation both paths share. New errors E-JS-109 to E-JS-113. A
+ panel re-attached after a collapse can cancel its job.
+- **`RegionDef.config`:** populator-owned configuration, emitted only when set and delivered to the populator as
+ `ctx.declared.config`, including from a datatables card's `detail.region`.
+- **A busy row refuses ordinary row actions:** a row action activated on a row that already has a pending or running
+ job (a `JuneauViews.jobs` run or another row action's job) is refused with `row-job-running` and sends nothing,
+ instead of starting a second concurrent job for the row. It runs normally once the job ends.
+- **Marker re-applied after redraws:** a row's `data-juneau-job` marker, row-action jobs included, is put back on the
+ new `<tr>` after every table draw, and cleared from whichever `<tr>` is drawn when the job ends.
+- **Poll held while a panel runs a job:** while an expanded detail panel hosts a running row job, the table's timer
+ reload is held and the staleness pill shows Paused, whether or not the view sets `pausePollingWhileEditing`. User
+ reloads, sorts, filters and page changes still draw.
+- **Fix: in-panel elements resolve their parent row.** `hookContextOf`, and with it `NS.rowActions.contextOf`, now
+ resolves an element inside a detail panel to the panel's parent row and its data, instead of a child row with empty
+ data. A row-detail region's `visibleWhen` therefore tests the parent row's data, as documented. An element in a
+ nested table's own row still resolves that row.
+- **Fix: console-card detail regions project `lazy`.** A datatables card's `detail.region` now carries the same
+ `lazy` value as the builder path (`false` unless the region is a tab body), so the two produce the same sidecar.
+- **Petstore:** the Jobs page gains a species table whose detail panel restocks that species, and a "Groom again"
+ button on each groom run.
+
+See the new [Row Jobs](/docs/topics/RowJobs) topic page and
+[Job-Log Card](/docs/topics/JobLogCard#in-a-row-detail-region).
+
+### Console views cleanup and ribbon parity corpus (BREAKING)
+
+Three Java copies of logic that the views JS owns are removed, and the JS is the single implementation:
+
+- **`CalendarLayout` removed.** Calendar lane and chip layout is computed by `juneau-calendar.js` only. `CalendarDef` and the calendar card are unchanged.
+- **`RegionDef.serializeParams(...)` / `resolvedDataUrl()` removed.** Region data URLs are built client-side by `juneau-regions.js`.
+- **Saved views removed.** `SavedViewsMixin`, `SavedViewStore`, `InMemorySavedViewStore` and `SavedViewQuotaException` are gone, along with the server persistence provider in `juneau-config.js` (`createServerProvider`, `data-juneau-saved-views`). Column configuration persists to `localStorage` only.
+- **Built-in operator help text moved to JS.** Built-in `SearchOperator`s no longer carry `help` on the wire. The column-search popover reads built-in help from `JuneauViews.search.SearchOperators.help(name)`. Custom operators still supply their own `help`.
+
+Ribbon behavior:
+
+- **Client-mode tables now apply column-scoped ribbon options.** Previously only server-mode tables did, so client-mode adopters needed their own bridge script.
+- **Ribbon values are BeanQuery `$`-expressions** (`$eq(DROPPED)`, `$in(Waiting,"Partially reviewed")`), never regular expressions. A regex-looking value is passed to the search engine as written and matches nothing useful.
+- **New `default` setting.** Use `default: true` on an `option`, or `default: '<memberId>'` on an `optionGroup`, to choose the initial selection when nothing is persisted. A stored choice always wins. Nothing auto-selects without `default`.
+- **`opt` is no longer comma-joined** across ribbon contributions. The last contribution wins.
+- **Parity corpus.** `ribbon-corpus.json` (views test resources) is the contract between the JS encoder and the Java DataTables request decoder. `RibbonCorpus_Test` (Java) and `RibbonCorpus_Parity_Test` (JS) both bind to it. Any change to ribbon encoding must update the corpus in the same change.
+
+Migration: replace regex ribbon values with the equivalent `$eq`/`$in`. Replace any page script that auto-applied a ribbon's first option with `default`.
+
+### Console views API gap fixes (`juneau-rest-server-views`, `juneau-rest-server-console-ui-freemarker`)
+
+- **`RibbonItem.id(String)`:** a ribbon item, including a `refresh()` item, can now carry an id. The id is emitted on the item's ribbon entry and is rejected on `divider` and `export` items.
+- **Ribbon `optionGroup` title:** the group `title` is now rendered as `role="group"` with `aria-label`, so assistive technology announces it. No visible caption is drawn.
+- **`Render.meta(String, Object)`:** the value may now be a `Number` or `Boolean`, which stays a JSON number or boolean (for example `meta("places", 2)`). It was `String`-only.
+- **`Column.href(...)` implies `render("linked")`:** a column with an `href` and no `render` now emits the `linked` renderer, in both Java (`Column.toCatalogMap()`) and FreeMarker (`DatatablesCardType`). An explicit `render` always wins. Migration: `Column.toCatalogMap()` used to throw `IllegalArgumentException` for an `href` without `render("linked")`. That throw is gone, so a test that expected it must change, and `render("linked")` may be dropped.
+- **`RowDetail` region is optional:** `RowDetail.toMap()` no longer requires a region. A detail with none is a header-only panel whose `title` and `icon` are filled from the endpoint's `fields`, and its entry carries no `region` key.
+- **`openConfirmOnlyDialog` passes the row to a confirm renderer:** a per-row action's `confirmRenderer` now receives `rows` (the one row), `labelField` and `rowIdField`, the same way the bulk path passes them, so it can name the row it guards. A ribbon-hosted dialog has no row, so `rows` is empty. Migration: a custom `confirmRenderer` that assumed `rows` was always empty or absent for a single-row action must handle a one-element `rows`.
+- **Same-origin path checks (stricter):** a card `src` must be a relative or root-relative same-origin path, so an absolute `https://this-host/...` `src` is now rejected at page build. Same-origin path checks now reject a backslash anywhere (including in the query, for example `/search?q=C:\x`), a leading or trailing space or control character, any C0 control character, and DEL. Badge `src` and `href` must be same-origin paths. Migration: change an absolute card `src` to a root-relative path.
+- **`PageSpec.card(...)` runs the card type's handler (behaviour change):** a builder-API card of an adopter (non-reserved) type is now built the way `<@card>` builds it. Its body map is the authored body, the type's registered `CardTypeHandler` validates it and returns the fragment the card carries, and an invalid body fails the render with the `<@card>` message. Markup passed to `card(spec, markup)` is the card's body, as the content of `<@card>` is: JSON5 markup is parsed by the handl [...]
+
+## juneau-rest-common
+
+### HTTP Annotations Moved from `juneau-marshall`
+
+The HTTP parameter annotations (`@Header`, `@Query`, `@Path`, `@Content`, `@FormData`, `@Request`, `@Response`, `@StatusCode`, `@PathRemainder`, `@HasQuery`, `@HasFormData`, `@Contact`, `@License`, `@Tag`) along with the constant classes (`CollectionFormatType`, `FormatType`, `ParameterType`) and the `httppart.bean` package (`RequestBeanMeta`, `RequestBeanPropertyMeta`, `ResponseBeanMeta`, `ResponseBeanPropertyMeta`, `MethodInfoUtils`) have been moved from `juneau-marshall` to `juneau-re [...]
+
+**Package names are unchanged** (`org.apache.juneau.http.annotation`, `org.apache.juneau.httppart.bean`), so import statements do not need updating. Maven resolves the classes from the new module location automatically. Existing compiled code will need recompilation.
+
+#### Removed Features
+
+- **`serializer()` and `parser()` attributes** removed from `@Query`, `@Header`, `@FormData`, `@Path`, `@PathRemainder`, `@Request`, `@Response`. Use the new `@HttpPartMarshalling` annotation (in `juneau-marshall`) to specify custom `HttpPartSerializer`/`HttpPartParser` classes.
+- **`@Repeatable`** removed from all HTTP annotations. Dynamic annotation stacking via `on`/`onClass` is no longer supported.
+- **`on()` and `onClass()` attributes** removed from all HTTP annotations.
+- **`@ContextApply`** removed from all HTTP annotations.
+- **14 XAnnotation companion classes** deleted (`ContentAnnotation`, `FormDataAnnotation`, `HasFormDataAnnotation`, `HasQueryAnnotation`, `HeaderAnnotation`, `PathAnnotation`, `PathRemainderAnnotation`, `QueryAnnotation`, `RequestAnnotation`, `StatusCodeAnnotation`). `ContactAnnotation`, `LicenseAnnotation`, `TagAnnotation`, and `ResponseAnnotation` remain for Swagger generation utilities.
+
+##### New `@HttpPartMarshalling` Annotation
+
+A new `@HttpPartMarshalling` annotation in `juneau-marshall` (`org.apache.juneau.httppart`) replaces the `serializer()` and `parser()` attributes that were removed from HTTP annotations. This decouples the HTTP annotations from `HttpPartSerializer`/`HttpPartParser` types.
+
+```java
+@HttpPartMarshalling(serializer=UonSerializer.class, parser=UonParser.class)
+@Query("name")
+String name
+```
+
+### `RestSharedConstants`
+
+- **`RestSharedConstants`** — Home for static literals shared across REST modules (server, client, mock) so wire names and similar strings stay aligned. Serializer/parser session option wire names are **`HEADER_JuneauSerializerOptions`**, **`HEADER_JuneauParserOptions`**, **`QUERY_juneauSerializerOptions`**, and **`QUERY_juneauParserOptions`**. Add more `public static final` fields when other cross-module constants are needed. (This supersedes the earlier `RestSessionOptionWire` type.)
+
+## juneau-rest-server
+
+### REST class family architecture — naming triad, `RestMixin`, op-mixins, flavor fill-in
+A coordinated, breaking restructuring of the `@Rest` resource family establishes one consistent
+architecture across servlets, child resources, and mixins. All changes below are hard breaks with no
+deprecation bridges (acceptable for a new major version); they supersede the pre-rename class
+names used elsewhere in these notes.
+
+**Naming triad — `*Servlet` / `*Resource` / `*Mixin`.** Every reusable capability now follows a
+self-describing `<Capability><Flavor>` naming scheme, and the vestigial `Basic` prefix is dropped from
+capability classes (it is retained only on the convenience-bundle bases that have a barer sibling, e.g.
+`BasicRestServlet`):
+
+- `RestObject` → **`RestResource`** (the non-`HttpServlet` child-resource base), cascading to
+ `BasicRestObject` → **`BasicRestResource`** and `BasicRestObjectGroup` → **`BasicRestResourceGroup`**.
+- The mixin pack drops `Basic` and standardizes on the `Mixin` suffix:
+ `BasicStaticFilesResource` → `StaticFilesMixin`, `BasicSwaggerResource` → `SwaggerMixin`,
+ `BasicSwaggerUiResource` → `SwaggerUiMixin`, `BasicOpenApiResource` → `OpenApiMixin`,
+ `BasicRedocResource` → `RedocMixin`, `BasicRouteIndexResource` → `RouteIndexMixin`,
+ `BasicEchoResource` → `EchoMixin`, `BasicAdminResource` → `AdminMixin`,
+ `BasicVersionResource` → `VersionMixin`, `BasicWellKnownResource` → `WellKnownMixin`,
+ `BasicSeoResource` → `SeoMixin`, `BasicFaviconResource` → `FaviconMixin`, and the four view mixins
+ `BasicJspResource`/`BasicMustacheResource`/`BasicThymeleafResource`/`BasicFreemarkerResource` →
+ `JspMixin`/`MustacheMixin`/`ThymeleafMixin`/`FreemarkerMixin`.
+- The servlet companions drop `Basic` too: `BasicStaticFilesServlet` → `StaticFilesServlet`,
+ `BasicAdminServlet` → `AdminServlet`, `BasicVersionServlet` → `VersionServlet`, and the abstract
+ `BasicViewServlet` → `ViewServlet` (with `Basic*Servlet` view companions → `<Engine>Servlet`).
+
+**New `RestMixin` base (opt-in).** A third base class `org.apache.juneau.rest.servlet.RestMixin` joins
+`RestServlet` and `RestResource` to complete the triad. It is **opt-in** — a plain `@Rest` POJO remains
+fully valid as a mixin or child via annotation-only configuration; extending `RestMixin` is a
+convenience and does not change composition semantics. Beyond the fluent builder surface it shares with
+`RestServlet` / `RestResource` (see the builder-API entry below), `RestMixin` adds **`getHostContext()`**,
+which returns the host (mixed-into) resource's `RestContext` — backed by the mixin sub-context's parent
+linkage, so it is usable at config time and does not depend on an in-flight request — letting a mixin op
+introspect the host (e.g. enumerate the host's child resources for a navigation page) rather than its own
+childless sub-context. It returns `null` when a `RestMixin` subclass is instantiated standalone rather than
+composed via `@Rest(mixins=...)`.
+
+**Op-interfaces deleted; replaced by single-responsibility op-mixins.** `BasicRestOperations` and
+`BasicGroupOperations` are **removed**. Their endpoints are now true `@Rest(mixins=...)` mixins —
+`ErrorMixin` (`/error`), `HtdocMixin` (`/htdoc`), `StatsMixin` (`/stats`), and `FaviconMixin`
+(`/favicon.ico`) — added to the `@Rest(mixins=...)` lists of `BasicRestServlet` / `BasicRestResource`
+and the Spring `BasicSpringRestServlet`. The host-wide nav-links / JSON-schema configuration that lived
+on `BasicRestOperations` is re-homed as class-level `@HtmlDocConfig` / `@JsonSchemaConfig` on
+`BasicRestServlet`; the per-method `@HtmlDocConfig` page decoration moves onto `StatsMixin.getStats`
+(OQ-8). `getChildren` stays a concrete method on the group bases
+(`BasicRestServletGroup` / `BasicRestResourceGroup` / `BasicSpringRestServletGroup`) so the host's main
+navigation page retains its `@HtmlDocConfig` chrome (a mixin op runs in its own sub-context and would
+lose the host's page decoration).
+
+**Favicon reconciliation.** The legacy `getFavIcon` op (which served `REST/favicon` → `images/juneau.png`
+through static files) is dropped in favor of a `FaviconProvider`-backed flavor set: the flavor-neutral
+`FaviconProvider` worker bean holds the icon bytes + `Cache-Control` value and the serve logic, and the
+independent `FaviconMixin` (mixin) / `FaviconResource` (child) / `FaviconServlet` (servlet) flavors each
+hold a `FaviconProvider` and delegate to it. The default serves a classpath ICO (`/juneau-favicon.ico`)
+with a 30-day `Cache-Control` (`max-age=2592000, public`). This is a behavior change for both the default
+favicon bytes and the configuration key.
+
+**Per-capability flavor fill-in (flavor-neutral worker beans).** Capabilities where standalone or
+routed-child deployment is a real use case gain their missing flavors as **independent classes** that each
+delegate to a **flavor-neutral worker bean** — never to another flavor — so the forms cannot drift. The
+new worker beans are `FaviconProvider` / `VersionProvider` (in `rest.convention`), `AdminProvider` (in
+`rest.ops`), `HealthAggregator` (in `rest.health`), and the four view-engine raw-template dispatchers
+`JspDispatcher` / `MustacheDispatcher` / `ThymeleafDispatcher` / `FreemarkerDispatcher`; the static-files
+flavors resolve the existing `StaticFiles` worker bean from the request context
+(`RestContext.getStaticFiles()`) instead of holding a mixin instance. New child-resource flavors:
+`StaticFilesResource`, `VersionResource`, `AdminResource`, `HealthResource`, plus a `ViewResource`
+abstract base and per-engine `JspResource` / `MustacheResource` / `ThymeleafResource` /
+`FreemarkerResource`; new favicon flavors `FaviconServlet` and `FaviconResource`. `HealthServlet` /
+`HealthMixin` / `HealthResource` all delegate to a shared `HealthAggregator` (OQ-5 split of the former
+`HealthServlet`). All other capabilities (Swagger, SwaggerUi, OpenApi, Redoc, RouteIndex, Error,
+Stats, Htdoc, WellKnown, Seo) remain mixin-only.
+
+**`RestRequest.getStaticFiles()` removed.** The request-level forwarder is deleted; obtain the active
+`StaticFiles` bean via `RestContext.getStaticFiles()` (e.g. `req.getContext().getStaticFiles()`) or
+bean-store injection. `FileVar`, `Widget`, and the static-files flavors are repointed accordingly.
+`RestContext.getStaticFiles()` is unchanged.
+
+**Route-index endpoint now content-negotiated.** `RouteIndexMixin` (`/options`) returns a
+`RouteDescriptions` POJO instead of writing JSON directly, so the listing is now content-negotiated
+through the host's configured serializers: `Accept: text/html` renders a browsable table with clickable
+route links, while `application/json` / `text/xml` clients receive the same entries in their requested
+format (mirroring `NavigationMixin.getChildren(...)`). Because it now relies on the host's serializers,
+the host **must have serializers configured** (e.g. via `BasicUniversalConfig` or a `Basic*` servlet
+base) — a bare `@Rest` host with no serializers can no longer content-negotiate the listing, whereas the
+former JSON-only op did not require them. The historical `/routes` alias remains reachable via
+`-Djuneau.routeindex.path=routes`.
+
+### Fluent programmatic builder API — `RestBuilder` / `AbstractRestBuilder` self-type CRTP
+A public, fluent builder surface lets a `@Rest` resource, child resource, or mixin be configured
+**programmatically** — the primary use case being instantiating a configured resource bean in a Spring
+`@Bean` method — instead of (or layered over) the `@Rest` annotation. Builder-supplied values take
+**precedence** over the class's own `@Rest` annotation members.
+
+**`RestBuilder<SELF>` config surface + `AbstractRestBuilder<R, SELF>` base (full self-type CRTP).** The
+public interface `org.apache.juneau.rest.RestBuilder<SELF extends RestBuilder<SELF>>` declares one fluent
+setter per `@Rest` member, plus the programmatic-only `mdcAsyncPropagation(boolean)` knob and a
+`set(String, Object)` forward-compat escape hatch. The shared abstract base
+`AbstractRestBuilder<R, SELF extends AbstractRestBuilder<R, SELF>>` implements every setter once — recording
+into a backing `RestAnnotation.Builder` override bag and returning `self()` — and leaves the `SELF` self-type
+**open** so a builder subclass's bespoke setters chain with true covariant returns (Option B / CRTP, aligned
+with the project-wide `SELF` / `self()` convention).
+
+**Per-flavor builders with a concrete `DefaultBuilder<R>` leaf.** Each base class exposes its own self-typed
+flavor builder — `RestServlet.Builder<R, SELF>`, `RestResource.Builder<R, SELF>`, and
+`RestMixin.Builder<R, SELF>` — together with a concrete `DefaultBuilder<R>` leaf. The static factory
+`RestServlet.builder(MyRest.class)` (and the `RestResource` / `RestMixin` equivalents) returns the
+`DefaultBuilder<R>`, so the common, non-subclassed path never has to spell two type parameters:
+
+```java
+@Bean
+public MyRest myRest() {
+ return RestServlet.builder(MyRest.class)
+ .path("/foo")
+ .allowedHeaderParams("foo")
+ .build();
+}
+```
+
+A resource may also add a one-line `static Builder<MyRest> builder() { return RestServlet.builder(MyRest.class); }`
+sugar so callers can write `MyRest.builder().path("/foo")....build()`.
+
+**Constructor trio + builder-beats-annotation precedence.** Every family base provides the constructor trio:
+a no-arg constructor, a `Foo(RestBuilder)` builder-injection constructor, and a `Foo.Builder`. `build()`
+stashes the builder onto the produced instance (a package-private `restBuilder` field read non-reflectively
+via `getRestBuilder()`). At `RestContext` construction the stashed builder's override bag is turned into a
+**synthetic `@Rest` annotation prepended as the most-derived entry** of the resource's annotation chain, so
+builder-set values win over the class's own `@Rest` members (rung 1 of the existing override-resolution
+chain). Collection-valued members **replace** the annotation value rather than merging.
+
+**Mirror-and-forward builders for worker-backed capabilities (OQ-11).** For a multi-flavor capability whose
+logic lives in a flavor-neutral worker bean, the flavor builder **mirrors the worker bean's builder methods on
+its own surface and forwards** into the worker's builder, while inheriting the full `RestBuilder` surface from
+`RestMixin.Builder`. For example `FaviconMixin.create()` mirrors `bytes` / `classpath` / `cacheControl` into
+`FaviconProvider.Builder` and inherits `path` / `roleGuard` / etc.; `VersionMixin` mirrors `VersionProvider`
+the same way. This is how the three flavors share capability config without a shared flavor-builder base class
+(Java single inheritance forbids one — each flavor builder already extends its REST builder base):
+
+```java
+@Bean
+FaviconMixin favicon() {
+ return FaviconMixin.create()
+ .classpath("/myapp/icon.ico") // mirrored → FaviconProvider.Builder
+ .path("/branding") // inherited RestBuilder surface
+ .build();
+}
+```
+
+**Bean-instantiation preference (internal).** So the new generic `builder(Class)` factories don't hijack
+framework default instantiation, `BeanInstantiator` now prefers a builder that builds the **exact requested
+type** (or a usable direct constructor) over one that only promises a supertype. A flavor's own `create()` /
+nested `Builder` therefore wins over the inherited generic `builder(Class)` (whose `build()` erases to a base
+class), and a plain resource/mixin carrying only the inherited builder falls through to its no-arg constructor.
+
+### Opt-in lazy `@Rest(children=...)` materialization
+Parent resources annotated with `@Rest(lazyChildren=true)` now defer the construction of each child
+`RestContext` until the first HTTP request routed to that child. Default behavior (eager at startup)
+is unchanged for all existing code.
+
+- **`@Rest(lazyChildren="true")`** on the parent resource opts all declared `@Rest(children=...)` into
+ deferred (first-invocation) construction. The value is a `String` member with full SVL variable
+ support (e.g. `@Rest(lazyChildren="$E{LAZY_CHILDREN,false}")`).
+- **`RestContext.Builder.lazyChildInit(boolean)`** is the host-wide programmatic knob with the highest
+ resolution priority (overrides both annotation and `${RestContext.lazyChildren:false}` env default).
+- **Shell-context routing:** At parent startup the route table is fully populated — each lazy child
+ contributes its path prefix (resolved from `@Rest(path=...)` without instantiating a full context)
+ to the parent's `RestChildren` matcher list. URL matching works immediately; the full `RestContext`
+ is built only on the first matching request.
+- **Concurrency safety:** Exactly one materialization per child even under concurrent first-requests.
+ `RestChildren.LazyChildEntry.materialize()` uses a double-checked lock on a per-entry `Object lock`
+ with a `volatile RestContext materialized` field. Two simultaneous first-requests serialize on the
+ lock; the second thread reads the already-built context without re-constructing. A materialization
+ that throws leaves the `materialized` field `null` — the next request retries.
+- **Observability:** Materialization is logged at `INFO` level:
+ `Lazy REST child materializing: com.example.ChildResource at path '/child'` / `materialized`.
+- **Lifecycle:** `RestContext.destroy()` (via `RestChildren.destroy()`) only calls the child's
+ shutdown hooks if the child was actually materialized. Never-invoked children are silently skipped.
+- **Interaction with `@Rest(observability="true")`:** Startup-fail is also deferred —
+ the observability-backend wiring check fires when the lazy child is first materialized, not at
+ parent startup.
+
+```java
+@Rest(path="/api", children={AdminResource.class, MonitorResource.class}, lazyChildren="true")
+public class ApiGroupResource extends BasicRestServletGroup {
+ // AdminResource and MonitorResource are built only on first request
+}
+```
+
+### Outgoing-response W3C trace-context headers
+When a non-no-op `TracerHook` is active on a request, the server now writes the W3C `traceparent` — and, when present, the `tracestate` — header back onto the HTTP **response**, so a client calling the server can read the resulting trace id off the response and correlate against it. The emitted `traceparent` reflects the **server-started span's** context (a freshly-minted span id parented to the inbound trace), not the inbound header verbatim. `tracestate` is written **only when the acti [...]
+
+- **`org.apache.juneau.rest.server.tracing.TraceContextResponseProcessor`** (new) — a `ResponseProcessor` prepended to the default `ResponseProcessorList`. It reads the rendered `traceparent` / `tracestate` strings that a `TracerHook` bridge stashed as request attributes (`juneau.traceparent` / `juneau.tracestate`) and writes them as response headers. Its first action is a single request-attribute read that short-circuits with zero allocations when no tracer ran, preserving the off-by-de [...]
+- **`RestContext.responseTraceparent`** (new env-driven default, `${RestContext.responseTraceparent:true}`) — controls whether `TraceContextResponseProcessor` is registered. Defaults to `true` (on-when-tracer); set it to `false` to keep the processor out of the chain entirely even when a tracer is active. Exposed via `RestContext.isResponseTraceparent()`. A per-resource `@Rest(observability="false")` opt-out is now available vian earlier change — see below.
+
+The companion OTel bridge change lives in `juneau-rest-server-tracing-otel`: `OtelTracerHook.startSpan(...)` now renders the W3C header values from the server-started span context at span-start time (the only point where it is reliably active) and stashes them as the request attributes the processor reads. See [REST Server — Observability](/docs/topics/RestServerObservability) for the full topic.
+
+### Per-resource / per-method observability opt-in attributes
+`@Rest`, `@RestOp`, and all HTTP verb annotations (`@RestGet`, `@RestPost`, `@RestPut`, `@RestDelete`, `@RestPatch`, `@RestOptions`) now carry three new `String` members for observability control:
+
+- **`observability`** (default `""`) — tri-state opt-in attribute with SVL variable support:
+ - `""` (empty, default) — inherit from parent / preserve today's behavior (observability fires when a backend `@Bean` is wired, is silent otherwise).
+ - `"true"` — strict mode: the `RestContext` / `RestOpContext` constructor throws `InternalServerError` at startup if no non-NoOp `MetricsRecorder` or `TracerHook` `@Bean` is wired on the resource. Use this to make a missing-backend wiring mistake a hard deployment error rather than a silent no-op.
+ - `"false"` — explicit opt-out: the observability block in `RestOpInvoker` is short-circuited for that resource or op, even when a backend is wired. Use this on health-probe and admin endpoints that should not contribute to metrics cardinality.
+ - Verb-level setting overrides the resource-level setting symmetrically (can both enable and disable).
+ - SVL variables (e.g. `@Rest(observability="$S{my.env.obs,}")`) are resolved at `RestContext` build time, so the startup-fail check fires correctly against the environment's resolved value.
+
+- **`metricName`** (default `""`) — per-op timer / metric name override. When non-empty, passed to `MetricsRecorder.record(...)` as the `metricName` parameter; `MicrometerMetricsRecorder` uses it as the Micrometer `Timer` name instead of the default `http.server.requests`.
+
+- **`metricTags`** (default `""`) — per-op additional metric tags in comma-separated `key=value` format (e.g. `"team=payments,region=us-east"`). Passed to `MetricsRecorder.record(...)` as the `metricTags` parameter; `MicrometerMetricsRecorder` adds these as extra `Timer` tags alongside the standard `method` / `uri` / `status` / `exception` tags.
+
+Example:
+
+```java fragment
+@Rest(path="/api", observability="true") // startup-fail if no @Bean MetricsRecorder / TracerHook
+public class ApiResource extends RestServlet {
+
+ @RestGet("/orders/{id}")
+ @RestGet(path="/orders/{id}", metricName="orders.lookup", metricTags="team=payments")
+ public Order getOrder(@Path String id) { ... }
+
+ @RestGet(path="/health", observability="false") // health probe: never metered
+ public HealthStatus health() { return HealthStatus.OK; }
+}
+```
+
+**`MetricsRecorder.record(...)` signature change (migration required if you implement `MetricsRecorder`):** The method now takes two additional trailing parameters — `String metricName` and `String metricTags` — after the existing `Throwable error` parameter. Update any custom `MetricsRecorder` implementations to add these two parameters (they may be safely ignored if not needed). The `NoOpMetricsRecorder` and `MicrometerMetricsRecorder` bundled with Juneau have been updated.
+
+**Interaction with `TraceContextResponseProcessor`:** When `observability="false"` is effective for an op, `RestOpInvoker` skips the `TracerHook.startSpan(...)` call. Since `OtelTracerHook` stashes the `juneau.traceparent` / `juneau.tracestate` request attributes inside `startSpan(...)`, those attributes are never set, and `TraceContextResponseProcessor` naturally emits no trace-context response headers for that op — no additional wiring is needed.
+
+### `View`-returning `@RestOp` methods now reach their renderer automatically
+Response processors implementing the new `ViewRenderer` marker interface are automatically
+positioned before catch-all processors (`CatchAllResponseProcessor`) in the
+`ResponseProcessorList`. The four built-in renderers (`JspViewRenderer`,
+`ThymeleafViewRenderer`, `MustacheViewRenderer`, `FreemarkerViewRenderer`) implement
+`ViewRenderer`. Third-party renderers gain the same ordering guarantee by implementing the
+interface.
+
+Previously, `SerializedPojoProcessor` (the catch-all) ran before mixin-registered view
+renderers, causing `@RestOp` methods returning `JspView`, `ThymeleafView`, `MustacheView`, or
+`FreemarkerView` to emit Juneau-bean HTML instead of invoking the template engine.
+
+**Before (workaround required):**
+
+```java fragment
+// User had to list JspViewRenderer explicitly ahead of SerializedPojoProcessor.
+@Rest(responseProcessors={JspViewRenderer.class, SerializedPojoProcessor.class, ...})
+```
+
+**After (zero config, automatic):**
+
+```java
+@Rest(path="/app", mixins=JspMixin.class)
+public class AppResource extends RestServlet {
+ @RestGet("/hello/{name}")
+ public View hello(@Path String name) {
+ return JspView.of("hello.jsp").attr("name", name);
+ }
+}
+// GET /app/hello/Bob → rendered hello.jsp (not a bean-HTML dump of JspView)
+```
+
+This is the foundational fix that unblocks the typed-handler View-return path for all four
+view modules — the per-engine typed-handler integration test matrices landed alongside
+this change (see the `juneau-rest-server-view-*` entries below).
+
+### Per-`RestContext` `@Value` resolution against `@Rest(config=...)` Configs
+- `RestContext`'s constructor now installs a `PropertySource` named `"rest.config"` on the
+ resource's `BeanStore` — **not** on the process-wide `Settings.get()` singleton. The source
+ wraps the resource's `@Rest(config=...)` `Config`(s) and walks them in child-to-parent order so
+ the child's keys win on collision and the parent's keys fill the gaps.
+- `@Value("${section/key}")` injection sites on the resource bean (and on request-scoped beans
+ whose `BeanStore` parent-walks to the resource's) consult the scoped `PropertySource` between
+ the `Settings` local/global override stores and the global `Settings` sources chain. Resolution
+ order for these sites is now:
+ 1. `Settings.setLocal(...)` / `Settings.setGlobal(...)` overrides (still win — test
+ override semantics are preserved).
+ 2. The resource's `@Rest(config=...)` `Config`(s).
+ 3. The global `Settings` sources list (system properties, env vars, classpath
+ `ConfigPropertySourceProvider`, microservice `Config`, Spring `Environment`, …).
+ 4. The `@Value("${key:default}")` literal default branch.
+- The `BeanStore`-scoped registration means `MockRestClient`'s static `RestContext` cache (and
+ any other long-lived cache) cannot leak `ConfigPropertySource`s into the global `Settings`
+ singleton — the 6.7× full-suite regression seen during an earlier prototype is
+ structurally impossible. A new `Settings_NoLeakedSources_Test` (200× `MockRestClient.build`
+ cycle) asserts the source count stays flat across the suite.
+- `@Value Supplier<String>` field types compose with the scoped chain: the `BeanStore` reference
+ is captured (by-reference, not snapshotted) by the supplier so every `.get()` re-evaluates
+ against the current state of the resource's `PropertySource`s. Hot-reload of the underlying
+ `Config` (or `Settings.set(...)` of an override key) takes effect on the next `.get()` without
+ rebuilding the resource bean.
+- Cross-link: [@Value Annotation Basics — Per-resource `@Rest(config=...)` integration](/docs/topics/ValueAnnotation#per-resource-restconfig-integration).
+
+### Test-time Bean Injection
+A new JUnit 5 extension and `@TestBean` annotation enable Spring-style test-time bean substitution for `MockRestClient`, `Microservice`, `SerializerSet`, `ParserSet`, and `EncoderSet` — without CGLIB, AOT bytecode generation, or any Mockito dependency. See [REST Server — Test-time Bean Injection](/docs/topics/RestServerTestBeanInjection) for the full reference.
+
+- New module **`juneau-test` (`org.apache.juneau.test.junit`)** with `JuneauBeanStoreExtension`, `@TestBean(name, type, scope, mode)`, `Scope`, `Mode`, and `TestBeanStore` (a fluent `BasicBeanStore` overlay-builder).
+- New `BeanStoreOverridable<B>` marker interface in `juneau-commons` plus `overridingBeanStore(BeanStore)` setters on `MockRestClient.Builder`, `Microservice.Builder`, `SerializerSet.Builder`, `ParserSet.Builder`, and `EncoderSet.Builder`. The overlay is installed in the `overridingParent` slot of the builder's bean store, putting overrides at tier 1 of the resolution chain.
+- New `Args.overridingParent` slot on `RestContext.Args` for threading the overlay into the bean store the `RestContext` constructs internally.
+- **Two wiring modes:**
+ - **Mode INJECT** (default) — construction-time wiring with overrides. The overlay is layered into the SUT's bean store at construction time. Universal — every bean type is eligible for replacement.
+ - **Mode OVERLAY** (opt-in via `@TestBean(mode = Mode.OVERLAY)`) — push/pop overlays on a live SUT. The extension is `attach(...)`-ed to a long-lived SUT's bean store; per-test overlays are push'd at `@BeforeEach` / `@BeforeAll` and pop'd at `@AfterEach` / `@AfterAll`. Backed by new `WritableBeanStore.pushOverlay(BeanStore)` / `popOverlay(Snapshot)` primitives.
+- **Per-test and per-class scopes** via `@TestBean(scope = METHOD)` (default) and `@TestBean(scope = CLASS)` (must be on `static` members). Method-scope overlays chain on top of class-scope overlays.
+- **Named-bean qualifier** via `@TestBean(name = "...")` matching the framework's existing `@Bean(name = "...")` parameter resolution.
+
+### AuthN Guards — Bearer / API-Key / `@Auth Principal`
+`juneau-rest-server` now ships two opt-in AuthN guards plus an `@Auth Principal` argument resolver. The core jar stays JWT-free — a separate `juneau-rest-server-auth-jwt` sub-module (see below) carries the nimbus-jose-jwt integration. See [REST Server — AuthN Guards](/docs/topics/RestServerAuthGuards) for the full reference.
+
+- **`org.apache.juneau.rest.auth.BearerTokenGuard`** — RFC 6750 bearer-token guard with a fluent builder:
+ `realm(String)` (default: `"api"`), `validator(TokenValidator)` (required). Stashes the resolved `Principal` on `RequestAttributes` under `RestServerConstants.PRINCIPAL_ATTR`. Throws 401 with `WWW-Authenticate: Bearer realm="<realm>"` on missing / malformed / rejected tokens — preserves richer challenges supplied by the validator.
+- **`org.apache.juneau.rest.auth.ApiKeyGuard`** — opaque API-key guard with a fluent builder:
+ `store(ApiKeyStore)` (required), `fromHeader(String)` (default: `X-API-Key`), `fromQuery(String)`, `fromCookie(String)`, `realm(String)` (default: `"api"`). Falls back to manual `Cookie` header parsing when the underlying container (e.g. `MockRestRequest`) doesn't populate `getCookies()`.
+- **`org.apache.juneau.rest.auth.TokenValidator`** SPI — single-method functional interface (`Principal validate(String token) throws AuthenticationException`) for plugging in opaque-token, JWT, or custom validators.
+- **`org.apache.juneau.rest.auth.ApiKeyStore`** SPI — single-method functional interface (`Optional<Principal> lookup(String key)`) for plugging in in-memory, database, or Vault-backed key stores.
+- **`@org.apache.juneau.rest.auth.Auth`** annotation — marks an `@RestOp`-method parameter for principal injection. The new **`AuthArg`** `RestOpArg` resolver also supports type-driven resolution: a bare `Principal` (or `ClaimsPrincipal`) parameter is injected with no annotation needed.
+- **`org.apache.juneau.rest.auth.ClaimsPrincipal`** — `Principal` subclass with typed claim access (`getClaim(String, Class<T>)`, `getClaims()`). Used by JWT validators; lives in core so the API surface is reachable without the JWT module.
+- **`org.apache.juneau.rest.auth.AuthenticationException`** — `Unauthorized` (401) subclass with fluent `wwwAuthenticate(String)` setter (RFC 7235 §4.1).
+- **`RestServerConstants`** additions: `PRINCIPAL_ATTR` (request-attribute key for the stashed principal), `API_KEY_HEADER` (default header name `"X-API-Key"`).
+- **`AuthArg` registered in `DefaultConfig.restOpArgs`** so type-driven `Principal` injection works out of the box.
+- **Composition with `AdminMixin`.** Swapping the mixin's default `DenyAllGuard` for a `BearerTokenGuard` is a zero-mixin-source-change migration — a single `@Bean RestGuardList adminGuards(...)` on the host wins via the existing guard-override seam (see `BasicAdminResource_AuthIntegration_Test`).
+
+### Auth Filter Framework — Servlet-Layer AuthN
+`juneau-rest-server` now ships a pluggable servlet-filter authentication framework in the new `org.apache.juneau.rest.auth` package. Filters run at the **container layer** — before `RestServlet` routing — and expose the resolved identity via standard `HttpServletRequest` overrides so downstream `RoleBasedRestGuard` and `@Auth Principal` injection work with zero changes. See [AuthN Filter Framework](/docs/topics/AuthFilterFramework) for the full reference.
+
+- **`org.apache.juneau.rest.auth.AuthFilter`** — abstract base class implementing `jakarta.servlet.Filter`. Concrete subclasses implement `Optional<AuthResult> authenticate(HttpServletRequest req) throws AuthenticationException`. Three states: `Optional.empty()` = no credentials for this filter (pass through), throw `AuthenticationException` = invalid credentials (abort with 401), `Optional.of(AuthResult)` = success.
+- **`org.apache.juneau.rest.auth.AuthResult`** — immutable value type holding the resolved `Principal` and an unmodifiable `Set<String>` of roles. Factory methods: `AuthResult.of(Principal, String...)` and `AuthResult.of(Principal, Set<String>)`.
+- **`org.apache.juneau.rest.auth.AuthenticatedRequestWrapper`** (public) — `HttpServletRequestWrapper` that overrides `getUserPrincipal()`, `isUserInRole(String)`, `getRemoteUser()`, and `getAttribute(PRINCIPAL_ATTR)` to reflect the filter-resolved identity. Roles are the union of all successful filters in chain mode.
+- **`org.apache.juneau.rest.auth.AuthFilterChain`** — `jakarta.servlet.Filter` orchestrator. Each entry carries an `AuthFilter` and an optional `UrlPathMatcher` pattern. `doFilter(...)` semantics: select matching filters, iterate in declaration order, first-success principal wins, all-success roles aggregate, all-failure exceptions aggregate into a single `401` with combined `WWW-Authenticate`. If no filter matches the path, or all matching filters return `Optional.empty()`, the request [...]
+ - Builder: `AuthFilterChain.create(BeanStore).append(AuthFilter).append(AuthFilter, String pattern).build()`.
+- **`org.apache.juneau.rest.auth.BearerTokenAuthFilter`** — concrete `AuthFilter` that extracts `Authorization: Bearer <token>`, delegates to a `TokenValidator`, and extracts roles from a `ClaimsPrincipal` claim (default: `"roles"`). Builder: `BearerTokenAuthFilter.create().validator(TokenValidator).realm(String).rolesClaim(String).build()`.
+- **`org.apache.juneau.rest.auth.ApiKeyAuthFilter`** — concrete `AuthFilter` that reads an API key from a header / query param / cookie (configurable; default: `X-API-Key` header), delegates to an `ApiKeyStore`, and extracts roles from a `ClaimsPrincipal` claim. Builder: `ApiKeyAuthFilter.create().store(ApiKeyStore).fromHeader(String).fromQuery(String).fromCookie(String).realm(String).rolesClaim(String).build()`.
+- **`@Bean AuthFilterChain` auto-mount** — `JettyServerComponent.onStart(...)` scans the `BeanStore` for an `AuthFilterChain` bean and registers it at `/*` before any servlet is mounted. No explicit `addFilter(...)` call required for the common case.
+- **Spring Boot note** — Spring Security's `SecurityFilterChain` is the recommended Spring path. `BearerTokenAuthFilter` / `ApiKeyAuthFilter` can be registered via `FilterRegistrationBean` if desired; the `@Bean` auto-mount is Jetty-only.
+
+### Rate-Limit Guard + Request-Id Filter
+`juneau-rest-server` now ships two opt-in operational primitives — both purely additive, both wired through existing `@Bean` / `@RestStartCall` extension points. See [REST Server — Rate-Limiting and Request-Id Propagation](/docs/topics/RestServerRateLimitAndRequestId) for the full reference.
+
+- **`org.apache.juneau.rest.server.guard.RateLimitGuard`** — token-bucket `RestGuard` with a fluent builder:
+ `permitsPerSecond(int)` / `permitsPerMinute(int)` / `permitsPerHour(int)`, `burst(int)`,
+ `keyBy(Function<RestRequest,String>)` (default: client IP from `req.getRemoteAddr()`),
+ `xForwardedForAware(boolean)` (default: `false` — only enable behind a trusted proxy that re-writes the header),
+ `exemptPaths(String...)` (default: `/healthz`, `/readyz`, `/livez` to match `HealthMixin`),
+ `whenLimitExceeded(BiConsumer<RestRequest,RateLimitInfo>)` (logging / metrics hook),
+ `storage(Storage)` (default: in-memory with a 100k key cap and LRU eviction).
+- **Rejection shape.** `429 Too Many Requests` with `Retry-After` in seconds (`Math.max(1, secondsUntilNextToken)`).
+- **Advisory headers.** Every passing request gets `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (documented as advisory — no IETF standard yet).
+- **`RateLimitGuard.Storage` SPI.** Pluggable backend for distributed deployments; v1 ships only the in-memory default. The token-bucket math uses `System.nanoTime()` deltas so it is immune to wall-clock drift.
+
+- **`org.apache.juneau.rest.server.filter.RequestIdFilter`** — `X-Request-Id` mint / honor / echo filter:
+ `idSupplier(Supplier<String>)` (default: `UUID.randomUUID().toString()`),
+ `validator(Predicate<String>)` (default: `^[A-Za-z0-9-_]{1,128}$`),
+ `attributeKey(String)` (default: `RestServerConstants.REQUEST_ID`).
+- **Mint / honor / reject contract.** Absent → mint; present + valid → honor; present + invalid → discard + mint fresh. The minted/honored id is stashed on the request attributes and echoed on the response.
+- **Re-entrancy safe.** A second invocation on the same request short-circuits on the existing attribute and re-echoes the same id.
+
+- **New constant.** `RestServerConstants.REQUEST_ID` (`"requestId"`) — the canonical request-attribute key used by `RequestIdFilter`.
+
+### Observability — Micrometer + OpenTelemetry hooks
+**Metrics and tracing are opt-in and disabled by default.** Same non-negotiable contract as Bean Validation: a fresh `RestContext` built with no `MetricsRecorder` / `TracerHook` bean registered never records a sample, never opens a span, and never adds a per-request cost — even when `io.micrometer:micrometer-core` and `io.opentelemetry:opentelemetry-api` are on the classpath. Opt in by registering a `@Bean MetricsRecorder` / `@Bean TracerHook` on the resource.
+
+#### New SPIs in `juneau-rest-server`
+
+- **`org.apache.juneau.rest.metrics.MetricsRecorder`** — interface with a single method `record(String opName, String httpMethod, String uriTemplate, int statusCode, Duration elapsed, Throwable error, String metricName, String metricTags)`. Called once per `@RestOp` invocation (happy path and exception path). The `uriTemplate` is the `@RestOp` path template (`/users/{id}`) — not the raw concrete URI — so tag cardinality stays bounded. The `metricName` and `metricTags` parameters (added i [...]
+- **`org.apache.juneau.rest.metrics.NoOpMetricsRecorder`** — process-wide singleton `INSTANCE`; resolved when no bean is registered. Drops events on the floor with zero allocations.
+- **`org.apache.juneau.rest.server.tracing.TracerHook`** — `@FunctionalInterface` with `Scope startSpan(RestRequest request)`. The framework opens the scope before parameter binding and always closes it in a `finally` block; the resolved HTTP status and any thrown throwable are delivered via `Scope.setStatusCode(int)` / `Scope.setError(Throwable)` before `Scope.close()`.
+- **`org.apache.juneau.rest.server.tracing.Scope`** — `AutoCloseable` with `setStatusCode(int)`, `setError(Throwable)`, and `close()`. The framework guarantees `close()` runs exactly once per call (including parameter-resolution failures and 5xx paths).
+- **`org.apache.juneau.rest.server.tracing.NoOpTracerHook`** — process-wide singleton `INSTANCE` (and `NoOpTracerHook.NoOpScope.INSTANCE`); resolved when no bean is registered.
+
+##### Where the hooks fire
+
+The hooks wrap the `@RestOp` handler invocation in `RestOpInvoker.invokeOp(...)` — they cover parameter binding, the user method body, and the framework's standard exception-mapping path. Pre/post-call lifecycle methods (`@RestPreCall` / `@RestPostCall`) are intentionally outside the observability boundary so the metric / span describes the user-facing operation only. Resolution goes through the `RestContext` bean store: the first `MetricsRecorder` / `TracerHook` bean wins; absent any re [...]
+
+##### Wiring
+
+```java fragment
+@Rest
+public class MyResource extends BasicRestServlet {
+
+ @Bean
+ public MetricsRecorder metrics(MeterRegistry r) {
+ return new MicrometerMetricsRecorder(r); // see juneau-rest-server-metrics-micrometer below
+ }
+
+ @Bean
+ public TracerHook tracer(OpenTelemetry otel) {
+ return new OtelTracerHook(otel); // see juneau-rest-server-tracing-otel below
+ }
+
+ @RestGet("/users/{id}") public User get(@Path String id) { ... }
+}
+```
+
+See [REST Server — Observability (Micrometer + OpenTelemetry)](/docs/topics/RestServerObservability) for the full topic.
+
+### Async returns + virtual-thread dispatch
+`@RestOp` handler methods may now return `CompletableFuture<T>` (or any `CompletionStage<T>`); the framework unwraps the value through the standard response-processor chain when the future resolves. A separate, opt-in `@Rest(virtualThreads=true)` flag dispatches handler invocation onto Java 21+ virtual threads with graceful degradation on Java 17. The two features compose: a `CompletableFuture` return on a `virtualThreads=true` resource gives you the high-throughput "no-thread-blocked" p [...]
+
+#### `CompletableFuture<T>` / `CompletionStage<T>` return support
+
+- New `org.apache.juneau.rest.server.processor.AsyncResponseProcessor` slotted ahead of `SerializedPojoProcessor` in the default `ResponseProcessorList`. Detects `CompletionStage<T>` return values, calls `req.startAsync()` to obtain an `AsyncContext`, and registers a `whenComplete((value, error) -> ...)` callback that re-feeds the unwrapped value (or throwable) through the rest of the response-processor chain when the future resolves.
+- **Failed-future propagation**: the throwable routes through the existing exception pipeline (`ThrowableProcessor` / `ProblemDetailsProcessor`); RFC 7807 / 9457 problem-details rendering works unchanged for failed futures when `@Rest(problemDetails="true")` is set.
+- **Bare `Future<T>` is rejected** with HTTP 500 and a clear message: *"Bare java.util.concurrent.Future is not supported as a return type from @RestOp methods. Return CompletableFuture or CompletionStage instead, or block on the result yourself before returning."* (Resolved OQA #4 — silently blocking on `Future.get()` would be a footgun.)
+- **Synchronous-fallback path** for unit-test mock environments: when the underlying servlet container does not support async dispatch (e.g. `MockRestRequest`), the processor falls back to a bounded `CompletableFuture.get(timeout)` and re-feeds the unwrapped value through the chain on the calling thread. Production servlet containers (Jetty, Tomcat, Undertow) take the true async path.
+- **`AsyncContext.complete()` runs exactly once** behind an `AtomicBoolean` state machine, even when both the timeout listener and the future-resolution callback fire concurrently.
+
+##### Async timeout
+
+- New `@Rest(asyncTimeoutMillis)` and `@RestOp(asyncTimeoutMillis)` attributes. Default is **30 seconds** (resolved OQA #2 — matches typical proxy timeouts). On timeout, the response is `504 Gateway Timeout` and the `AsyncContext` is aborted.
+- `0` disables the timeout for that scope. Negative values are treated as "use the default 30s". SVL is supported (`asyncTimeoutMillis = "${TIMEOUT_MS:30000}"`).
+
+##### Virtual-thread per-request dispatch (Java 21+, opt-in, off by default)
+
+**Same non-negotiable off-by-default contract as Bean Validation and Observability.** A fresh `RestContext` built with no `virtualThreads` attribute set never instantiates a virtual-thread executor, even on Java 21+.
+
+- New `@Rest(virtualThreads)` and `@RestOp(virtualThreads)` attributes — `"true"` opts in for every op on the resource (or just the annotated op); `"false"` at op level opts out of a resource-level enablement; `""` (default) inherits.
+- When enabled (and the runtime is Java 21+), `RestOpInvoker` dispatches the entire handler call — parameter binding, the user method body, and the standard exception-mapping path — onto a virtual thread submitted to a per-resource `Executors.newVirtualThreadPerTaskExecutor()`. The platform "carrier" thread that received the request is parked on `CompletableFuture.get()` until the virtual thread finishes.
+- **Java 17 graceful degradation**: on Java 17 / 18 / 19 / 20, the flag is detected at first use, a one-shot `WARNING` is logged via `Logger.getLogger("org.apache.juneau.rest.RestContext.async")`, and the framework falls back to caller-thread dispatch. No runtime error.
+- **Reflective executor creation**: `Executors.class.getMethod("newVirtualThreadPerTaskExecutor").invoke(null)` is gated behind `Runtime.version().feature() >= 21`, so the framework compiles cleanly under the project's Java 17 source level (no `<source>21</source>` bump needed).
+
+##### Composition
+
+The two features are independent but compose naturally — a `CompletableFuture` return on a `virtualThreads=true` resource gives both structured-concurrency benefits AND a non-blocking response path. **Virtual-thread dispatch sits ABOVE the observability hooks**: when `virtualThreads=true`, the entire `invoke(...)` body — including `TracerHook.startSpan(...)` and `MetricsRecorder.record(...)` — runs on the virtual thread, so OTel's `Context.makeCurrent()` works as expected. **Async return [...]
+
+##### Migration / backward compatibility
+
+Synchronous handlers (no `CompletableFuture`, no `virtualThreads=true`) have **zero behavioral change**. Async opt-in is purely by changing the return type; virtual-thread opt-in is purely by setting the attribute.
+
+##### Thread-local caveats
+
+`RequestAttributes`, `VarResolverSession`, `Locale` are request-scoped, not `ThreadLocal`-backed — they survive the async hop. **MDC and security contexts** that rely on `ThreadLocal` (SLF4J MDC, classic `SecurityContextHolder`-style patterns) **do not** survive the async hop. If you need MDC across an async boundary, copy the relevant keys into `RequestAttributes` before returning the future, or enable the new SLF4J MDC bridge described below.
+
+See [REST Server — Async Returns + Virtual-Thread Dispatch](/docs/topics/RestServerAsyncDispatch) for the full reference.
+
+### SLF4J MDC async propagation bridge
+When a `@RestOp` handler returns a `CompletableFuture`, the `whenComplete` callback typically runs on a different thread (a virtual thread, pool thread, or fork-join worker). Log statements emitted inside that callback previously had no SLF4J MDC context — any `requestId`, `userId`, or trace-correlation keys set by an upstream filter were invisible to the logger running on the completion thread.
+
+**New behavior (default-on when SLF4J is on the classpath):** `AsyncResponseProcessor` now snapshots the request thread's MDC map immediately before registering the `whenComplete` callback, then wraps the callback so the completion thread sees the same diagnostic context.
+
+```java
+// No code change required — MDC propagates automatically:
+@RestGet("/orders/{id}")
+public CompletableFuture<Order> getOrder(@Path String id) {
+ // MDC.get("requestId") is non-null here (set by upstream filter).
+ return orderService.fetchAsync(id);
+ // ... and is also non-null inside the whenComplete callback, even on a virtual thread.
+}
+```
+
+**Key properties:**
+
+- **Zero-cost when unused.** If the request thread's MDC is empty at dispatch time (the common case for services that don't set MDC), the snapshot is `null` and the callback is registered unchanged — no extra allocations.
+- **Clean-up on the completion thread.** After the callback returns (or throws), the completion thread's MDC is restored to exactly the state it was in before the callback. The listener removes only the keys it installed — any MDC state the completion thread already had is preserved. No thread-pool contamination.
+- **Exceptional-completion path.** If the future completes with an error, the `finally` block still runs — MDC is cleared even when the callback throws.
+- **SLF4J-only.** The bridge covers SLF4J MDC (`org.slf4j.MDC`). Log4j2 `ThreadContext` direct users (without the SLF4J facade) are not covered in v1. Users on the SLF4J ↔ Log4j2 bridge get the correct behavior automatically.
+- **Opt out.** Set the system / environment variable `RestContext.mdcAsyncPropagation=false` to disable globally, or call `RestContext.Builder.mdcAsyncPropagation(false)` to disable on a specific resource.
+
+**New API surface:**
+
+- **`org.apache.juneau.rest.server.processor.MdcAsyncListener`** (new) — static utility: `snapshot()` captures the current thread's MDC; `wrap(BiConsumer, Map)` decorates a completion callback with snapshot-restore-clear logic. No SLF4J compile-time dependency; detected reflectively at class-load time.
+- **`RestContext.isMdcAsyncPropagation()`** (new) — returns `true` when MDC propagation is enabled. Defaults to `true`; overridden by the `RestContext.mdcAsyncPropagation` env-driven property.
+- **`RestContext.Builder.mdcAsyncPropagation(boolean)`** (new) — per-resource programmatic override.
+
+### Per-resource async completion executor
+Production deployments need to isolate `CompletableFuture` completion callbacks on a dedicated thread pool — the future's natural completion thread is often an I/O-driver or virtual thread that should not be blocked by response-handler work. A new `asyncCompletionExecutor` attribute lets you route callbacks to any named `Executor` bean registered with the resource.
+
+```java
+@Rest(path="/api", asyncCompletionExecutor="myCompletionPool")
+public class ApiResource extends RestServlet {
+
+ @Bean(name="myCompletionPool")
+ public Executor completionPool() {
+ return Executors.newFixedThreadPool(8, r -> new Thread(r, "response-handler-"));
+ }
+
+ @RestGet("/orders/{id}")
+ public CompletableFuture<Order> getOrder(@Path String id) {
+ return orderService.fetchAsync(id);
+ // whenComplete callback runs on myCompletionPool, not the I/O thread
+ }
+}
+```
+
+Key details:
+
+- **`@Rest(asyncCompletionExecutor="beanName")`** — resource-level default; resolves a `java.util.concurrent.Executor` bean by name from `BeanStore`. Empty string (default) = no override; natural completion thread is used.
+- **`@RestOp(asyncCompletionExecutor="beanName")`** and all verb annotations (`@RestGet`, `@RestPost`, …) — per-operation override; wins over the resource-level setting.
+- **`RestContext.Builder.asyncCompletionExecutor(String beanName)`** — programmatic knob for the same configuration.
+- **Startup-fail** — if the configured bean name does not resolve to an `Executor` in `BeanStore`, `RestContext` construction throws immediately (misconfiguration is never silently ignored).
+- **MDC propagation compatibility** — the `MdcAsyncListener` wraps the callback *before* executor routing; the MDC snapshot is taken on the dispatching thread and restored on whichever thread the executor selects.
+- **Virtual-thread executor** — `Executors.newVirtualThreadPerTaskExecutor()` (Java 21+) is a valid value; each callback runs on a fresh virtual thread.
+- **Default behavior unchanged** — when the annotation is absent, `whenComplete(callback)` is used (natural thread, identical to 9.4.x behavior).
+
+New API:
+
+- **`@Rest(asyncCompletionExecutor=...)`** / **`@RestOp(asyncCompletionExecutor=...)`** / all verb annotations — new `String` annotation member.
+- **`RestContext.Builder.asyncCompletionExecutor(String)`** — programmatic setter.
+- **`RestContext.getAsyncCompletionExecutor()`** — resource-level resolved executor (or `null`).
+- **`RestOpContext.getAsyncCompletionExecutor()`** — op-level resolved executor with resource-level fallback.
+- **`RestServerConstants.PROPERTY_asyncCompletionExecutor`** — annotation property key constant.
+
+### Reactive-Streams return types — `Flow.Publisher` core + Reactor / RxJava bridge
+`@RestOp` handlers may now return reactive-streams values. **The feature is fully opt-in by adding a module to the classpath** — a bare `juneau-rest-server` has zero reactive behavior and `DefaultConfig` wires no reactive processor. The JDK-native `java.util.concurrent.Flow.Publisher<T>` is supported by the new dependency-free, opt-in `juneau-rest-server-reactive` module; Project Reactor (`Mono` / `Flux`), RxJava 3 (`Single` / `Maybe` / `Completable` / `Flowable` / `Observable`), and the [...]
+
+```java
+@Rest(path="/feed")
+public class FeedResource extends BasicRestServlet {
+
+ // Single-value reactive type — collapses onto the existing async (CompletableFuture) path.
+ @RestGet("/{id}")
+ public Mono<Article> get(@Path String id) {
+ return articleService.findById(id); // Reactor Mono → CompletionStage
+ }
+
+ // Bounded stream, default shape — collected into a JSON array.
+ @RestGet("/recent")
+ public Flux<Article> recent() {
+ return articleService.recent(20); // buffered to a List, serialized as JSON
+ }
+
+ // Unbounded stream as Server-Sent Events — opt in via the response content type.
+ @RestGet("/live")
+ public Flux<SseEvent> live(RestResponse res) {
+ res.setContentType("text/event-stream"); // → SSE frames, one per element
+ return articleService.liveEvents();
+ }
+
+ // Same stream as newline-delimited JSON.
+ @RestGet("/ndjson")
+ public Flux<Article> ndjson(RestResponse res) {
+ res.setContentType("application/x-ndjson"); // → one JSON object per line
+ return articleService.recent(1000);
+ }
+}
+```
+
+Unified architecture (the two promoted plan items ship as one coherent feature):
+
+- **Opt-in by classpath presence.** `ReactiveResponseProcessor` is **not** wired into `DefaultConfig`. It ships in `juneau-rest-server-reactive` with a `META-INF/services/org.apache.juneau.rest.server.processor.ResponseProcessor` provider file; `RestContext` discovers module-contributed response processors via `ServiceLoader` and front-loads them ahead of `AsyncResponseProcessor`. On a bare `juneau-rest-server` the discovery finds nothing and the processor chain is byte-for-byte the pre- [...]
+- **`ReactiveResponseProcessor`** (`org.apache.juneau.rest.reactive`, in the opt-in `juneau-rest-server-reactive` module) is the single shared spine. Once on the classpath it is front-loaded ahead of `AsyncResponseProcessor` and natively understands `Flow.Publisher`. Synchronous returns (`String`, beans, `byte[]`, …) are untouched.
+- **Single-value reactive types** (`Mono`, `Single`, `Maybe`, `Completable`) are adapted to a `CompletionStage` and collapse onto the existing `AsyncResponseProcessor` path — inheriting its timeout, `@Rest(asyncCompletionExecutor)`, and SLF4J MDC bridge behavior for free.
+- **Multi-value streams** are rendered in one of three shapes, selected by the negotiated response media type (handler `Content-Type`, then request `Accept`):
+ - **Buffer** (default) — all elements collected into a `List` and serialized through the normal chain (e.g. a JSON array). Wrapped in a `CompletableFuture` so a slow producer never blocks the request thread.
+ - **SSE** (`text/event-stream`) — each element emitted as a Server-Sent-Events frame; `SseEvent` elements are written verbatim, any other element type is JSON-encoded into the `data:` field.
+- **Behavior change:** reactive NDJSON responses now echo the negotiated `application/jsonl` /
+ `application/json5l` type (aliases canonicalised) instead of always `application/x-ndjson`; an explicit
+ Content-Type still wins.
+- **Backpressure** — streaming subscribers request one element at a time (`request(1)` on subscribe and again after each frame is flushed). Because the servlet output stream blocks until the socket drains, the producer is paced by the client and the server-side buffer stays bounded.
+- **Third-party libraries are `provided`-scoped** — `reactive-streams`, `reactor-core`, and `rxjava` are all declared `provided` in `juneau-rest-server-reactive-reactor`. A `dependency:tree` on `juneau-rest-server` never surfaces them. Adapters are discovered lazily via `ServiceLoader` and skipped at runtime if their backing library is absent, so a consumer adds `juneau-rest-server-reactive-reactor` plus only the reactive library (and version) they actually use.
+- **Extensibility** — the `ReactiveStreamsAdapter` SPI + `Adaptation` value type let third parties bridge any other reactive library to the core `Flow.Publisher` / `CompletionStage` plumbing without modifying `juneau-rest-server`.
+
+New API (all in the opt-in `juneau-rest-server-reactive` module):
+
+- **`org.apache.juneau.rest.reactive.ReactiveResponseProcessor`** — the response processor, auto-registered via `ServiceLoader` when the `juneau-rest-server-reactive` module is on the classpath (no `DefaultConfig` wiring).
+- **`org.apache.juneau.rest.reactive.ReactiveStreamsAdapter`** — SPI for bridging third-party reactive types (`boolean canAdapt(Object)` + `Adaptation adapt(Object)`).
+- **`org.apache.juneau.rest.reactive.Adaptation`** — single-value (`CompletionStage`) or streaming (`Flow.Publisher`) adaptation result.
+
+`RestContext` now discovers module-contributed `ResponseProcessor`s via `ServiceLoader` (inert on a bare classpath) — this is the seam that auto-activates the reactive processor.
+
+See [Reactive-Streams Returns](/docs/topics/RestServerReactive) for the full topic.
+
+### New module: `juneau-rest-server-reactive`
+A new dependency-free, opt-in module that holds the JDK-native `Flow.Publisher` spine (`ReactiveResponseProcessor`), the `ReactiveStreamsAdapter` SPI, the `Adaptation` value type, and the `META-INF/services/...ResponseProcessor` auto-registration file. Adding it alone enables `Flow.Publisher<T>` returns with no third-party dependency. A bare `juneau-rest-server` without it has zero reactive behavior.
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-reactive</artifactId>
+ <version>10.0.0</version>
+</dependency>
+```
+
+### New module: `juneau-rest-server-reactive-reactor`
+A further opt-in bridge module (depends on `juneau-rest-server-reactive`) providing `ReactiveStreamsAdapter` implementations for the popular reactive libraries. Server-side only — there is no client-side counterpart. All three backing libraries are `provided`-scope.
+
+| Adapter | Handles | Adapts to |
+|---|---|---|
+| `ReactorReactiveAdapter` | `Mono<T>` / `Flux<T>` | `Mono` → `CompletionStage` (via `toFuture()`); `Flux` → `Flow.Publisher` (via `FlowAdapters`) |
+| `RxJavaReactiveAdapter` | `Single` / `Maybe` / `Completable` / `Flowable` / `Observable` | single types → `CompletionStage`; `Flowable` → `Flow.Publisher`; `Observable` → `Flowable(BackpressureStrategy.BUFFER)` → `Flow.Publisher` |
+| `ReactiveStreamsPublisherAdapter` | generic `org.reactivestreams.Publisher<T>` | `Flow.Publisher` (via `FlowAdapters`); registered last so `Mono`/`Flux` are matched as single/stream first |
+
+Maven coordinate:
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-reactive-reactor</artifactId>
+ <version>10.0.0</version>
+</dependency>
+<!-- plus ONLY the reactive library you use, e.g.: -->
+<dependency>
+ <groupId>io.projectreactor</groupId>
+ <artifactId>reactor-core</artifactId>
+ <version>3.6.11</version>
+</dependency>
+```
+
+### Health Probe SPI + Resource
+`juneau-rest-server` now includes a built-in probe SPI and aggregation resource under
+`org.apache.juneau.rest.server.health`:
+
+- `HealthIndicator` functional SPI (`Health check()`).
+- `Health` value type with static builders: `Health.up(name)`, `Health.down(name, throwable)`,
+ `Health.unknown(name)`, plus structured detail entries.
+- `HealthProbe` / `HealthStatus` enums for probe categorization and aggregate state.
+- `HealthMixin` exposing `/healthz`, `/readyz`, and `/livez` with aggregate body format:
+ `{status, components:{name:{status,details}}}`.
+- HTTP status mapping follows Kubernetes conventions: `503` if any component is `DOWN`, else `200`.
+- `HealthProbeSettings` introduces per-indicator timeout control (default `1s`); exceptions/timeouts
+ are converted to `DOWN` with `details.error`.
+
+### `@Rest(mixins=...)` Composition + `@Rest(paths=...)` Multi-Mount
+
+See [REST Server — Mixins and Multi-Mount Paths](/docs/topics/RestServerComposition) for the full reference.
+
+The health-probe follow-up adds two new `@Rest` composition/mounting primitives:
+
+- **`mixins`** (`Class<?>[]`) — graft `@RestOp`-group methods from mixin classes into a resource's
+ operation tree. Local methods on the importing resource win on path collisions.
+- **`paths`** (`String[]`) — optional multi-mount path specs for top-level servlet registration.
+ Jetty mounts a single servlet instance at each declared path.
+
+`HealthMixin` now declares `@Rest(paths={"/healthz","/readyz","/livez"})` instead of
+`@Rest(path="/")`, eliminating startup collisions with apps whose root resource already owns `/*`.
+Users can now choose either:
+
+- **Recommended**: `@Rest(mixins=HealthMixin.class)` on the root resource (single servlet).
+- **Fallback**: standalone `HealthProbeConfiguration` auto-mount (separate servlet, explicit paths).
+
+### Mixin Sub-Context Inheritance
+
+`@Rest(mixins=…)` mixin classes are now each elevated into a dedicated `RestContext` sub-context
+rather than being treated as bare method libraries grafted into the host's context. Each mixin
+gets its own bean store, hook chain, and contribution lists; the host's `RestContext` becomes the
+mixin's `parentContext`, and the mixin inherits configuration via the same parent-walk used for
+the rest of the `@Rest` annotation chain — with `@Rest(noInherit={…})` available as the per-property
+opt-out (semantics aligned with the rest of the framework).
+
+- **Inheritance walk.** `RestContext.getRestAnnotationsForProperty(name)` prepends the host's
+ `@Rest` chain onto the mixin's own so the reduce-last / append-all walks all the contribution
+ lists transparently. Verified by a per-list regression matrix covering serializers, parsers,
+ encoders, converters, response processors, REST op args, guards (default inherit + `noInherit`
+ opt-out), callLogger, debugEnablement, debugDefault, partSerializer, and partParser. None of
+ these lists needed bespoke wiring — the walk drives them all.
+- **Hook dual-firing.** `@RestStartCall` / `@RestEndCall` / `@RestPreCall` / `@RestPostCall` /
+ `@RestDestroy` now fire host-first-then-mixin for mixin-endpoint requests and host-only for
+ host-endpoint requests. Hook execution order is the standard host→mixin chain regardless of
+ which mixin class declared the operation that's being dispatched, so per-request
+ observability (logging, metrics, request-id injection) stays uniform across host and mixin
+ endpoints.
+- **Messages chaining.** `Messages` doesn't ride the `@Rest` walk (it has its own
+ parent-resource-bundle chain semantics), so the new `Messages.chain(child, parent)` static
+ factory composes two pre-built bundles into a single parent-chained bundle without mutating
+ either input. The `messages` memoizer uses it to inherit the host's bundle on mixin sub-contexts
+ (mixin-keys-first, host-keys-as-fallback). `@Rest(noInherit={"messages"})` cuts the chain off
+ for callers that want isolation.
+- **`DefaultConfig` synthesis is host-only.** The framework's synthesized `DefaultConfig` entry on
+ a bare `@Rest` resource is now skipped for mixin sub-contexts, because the host's chain already
+ supplies it. Without this, a mixin without an explicit per-property declaration would silently
+ pick up the framework's `DefaultConfig` default *after* the host's explicit value in the
+ combined parent-walk — i.e. mixins would shadow host overrides for any property they didn't
+ declare themselves. The skip is invisible to user code; it just makes "no declaration on the
+ mixin" actually inherit from the host.
+- **New canonical `RestContext.Args` constructor.** The legacy 8-arg back-compat constructor was
+ removed; the canonical 9-arg form now takes an explicit `mixinContext` flag (host=`false`,
+ mixin sub-context=`true`). No external callers exist; all internal call sites in
+ `juneau-rest-server`, `juneau-rest-mock`, and `juneau-commons` were updated.
+
+### Runtime-Overridable `@Rest(paths=...)` Resolution Chain
+The `paths` array on `@Rest` is now a *default* in a three-rung **runtime-override resolution
+chain**, walked once during `RestContext` construction and exposed via `RestContext.getPaths()`.
+Apps can substitute the top-level mount paths at deploy time without recompiling the resource.
+
+Precedence (highest first):
+
+1. **Programmatic** — `RestContext.Args.paths()` / `RestContext.Builder.paths(String...)`.
+ `null` means "inherit from lower rungs"; `new String[0]` is the explicit-clear sentinel.
+2. **`getPaths()` virtual getter** — added to both `RestServlet` and `RestResource` (default
+ `null`). Returning `null` inherits the lower rung; `new String[0]` is the explicit-clear
+ sentinel; non-empty wins.
+3. **`@Rest(paths={"…"})` annotation default** — *new behavior:* each element runs through
+ [SVL](/docs/topics/RestServerSvlVariables) substitution and the resolved value is then
+ split on `,` (trim each piece, drop empties). A single template element can therefore
+ expand to zero, one, or many mount paths.
+
+The annotation-default rung now supports the same SVL surface as any other `@Rest` string member:
+
+```java
+@Rest(paths={"$C{health.paths}"}) // single key, multi-value
+@Rest(paths={"/api", "$C{extra.paths}"}) // mix literal + resolved
+@Rest(paths={"$E{HEALTH_PATHS,/healthz,/readyz}"}) // env var w/ comma-separated default
+```
+
+SVL `$C{key}` consults whatever Juneau `Config` is registered on the bean store (test overlay,
+`@Bean` factory, or the framework's runtime `Config`); `$E{NAME,default}` and
+`$S{prop,default}` use the bootstrap variable catalog without needing any beans. An SVL failure
+or missing key with no default substitutes empty, which the comma-split then drops — same
+semantics as every other SVL-bearing `@Rest` member.
+
+The same chain runs whether the app is wired via Juneau auto-discovery (`JettyServerComponent`)
+or Spring Boot's manual registration. A new public mount-time helper
+`RestContext.resolveTopLevelPaths(Class, Object, BeanStore)` lets Spring Boot users compute the
+resolved paths inside their `@Bean ServletRegistrationBean<MyServlet>(...)` factories — the
+programmatic Builder rung is N/A for that flow (no `RestContext` exists at registration time;
+substitute via the getter or by registering a `Config` bean that the annotation's `$C{...}`
+elements can resolve through).
+
+> **Spring Boot `application.yaml` integration is planned under a separate work item.** Once
+> that lands, `$C{key}` in `@Rest(paths=...)` elements will transparently consult Spring's
+> `Environment` under Spring Boot via a Juneau-`Config`-to-`Environment` bridge — no API change
+> here, just an additional resolution source registered with the `Config` bean.
+
+See [REST Server — Mixins and Multi-Mount Paths § Runtime-overridable paths](/docs/topics/RestServerComposition#runtime-overridable-paths)
+for the full reference, including the precedence table, worked examples for each rung, the
+Spring Boot integration walkthrough, and the `null`-vs-empty-array semantic table.
+
+This change is purely additive — resources that don't override `getPaths()`, aren't constructed
+with a programmatic `Args.paths()`, and don't include SVL markers in their `@Rest(paths=...)`
+literals continue to behave exactly as they did under an earlier change (the annotation default wins
+on its own, element-for-element). Pre-10.0.0 users who already adopted `@Rest(paths=...)` see no
+behavior change unless they introduce an SVL marker or an embedded comma in an element.
+
+### SVL `${...}` resolution in `@RestOp(path)` / `@RestGet(path)` / `@RestOp(value)`
+SVL variables (`${var:default}`, `$S{key,default}`, `$E{NAME,default}`, `$C{key}`, etc.) are now
+resolved in op-level path declarations — `@RestOp(path)`, `@RestGet(path)`, `@RestPost(path)`,
+`@RestPut(path)`, `@RestDelete(path)`, and the `@RestOp(value="METHOD path")` verb-on-value form
+— closing the asymmetry with class-level `@Rest(path)` / `@Rest(paths)`, which were already
+SVL-resolved.
+
+This enables configurable mount paths for mixin-style resources without subclassing:
+
+```java
+@Rest(responseProcessors=JspViewRenderer.class)
+public static class JspMixin {
+ // Mounts at "/jsp/*" by default; deployers can override at runtime via system property,
+ // env var (after Settings normalization), or a registered Config bean.
+ @RestGet(path="/${jsp.path:jsp}/*")
+ public View hello() { return new JspView("hello.jsp"); }
+}
+```
+
+The shorthand `${name:default}` uses Juneau's dollar-brace shortcut (registered as `PropertyVar`,
+which reads from `Settings.get()`); the longer SVL forms — `$S{key,default}`, `$E{NAME,default}`,
+`$C{key}` — also work and are preferred when the source is explicit. SVL resolution happens at
+`RestContext` construction time, so deployer overrides must be in place before the resource
+context is built.
+
+The auto-detected fallback path (derived from the Java method name when no `path()` / `value()`
+is declared) is intentionally **not** SVL-resolved — it is framework-derived, not user input.
+Path strings whose SVL resolution yields an empty string are dropped (matches how class-level
+`@Rest(paths)` drops empty pieces in the post-comma-split pipeline). Unresolved variables with
+no default pass through to `UrlPathMatcher.of(...)` as the literal `${...}` placeholder, which
+then fails predictably at routing time without crashing at startup.
+
+This change is purely additive — annotation strings that contain no SVL markers are passed
+through unchanged.
+
+### Bundled mixin op-paths now SVL-configurable
+Building on an earlier change (SVL resolution in op-level `path` declarations), every Juneau-bundled
+mixin resource whose mount path is a Juneau organizational/conventional default — not fixed by an
+external specification — now declares its op-level `path` as `/${juneau.<role>.path:<default>}`
+so deployers can relocate the mount via system property, environment variable, or `Config` key
+without subclassing.
+
+The variable map:
+
+| Mixin | SVL variable | Default mount |
+|---|---|---|
+| `SwaggerMixin` | `${juneau.swagger.path:api}` | `/api/*` |
+| `SwaggerUiMixin` | `${juneau.swaggerui.path:swagger}` | `/swagger/*` |
+| `OpenApiMixin` | `${juneau.openapi.path:openapi}` | `/openapi/*`, `/openapi.json`, `/openapi.yaml` |
+| `RedocMixin` | `${juneau.redoc.path:redoc}` | `/redoc/*` |
+| `StaticFilesMixin` | `${juneau.staticfiles.path:static}` | `/static/*` |
+| `VersionMixin` | `${juneau.version.path:version}` | `/version` |
+| `EchoMixin` | `${juneau.echo.path:echo}` | `/echo/*` |
+| `AdminMixin` | `${juneau.admin.path:admin}` | `/admin/threads`, `/admin/heap`, `/admin/cache/flush`, `/admin/ratelimit` |
+| `RouteIndexMixin` | `${juneau.routeindex.path:options}` | `/options` |
+| `JspMixin` | `${juneau.jsp.path:jsp}` | `/jsp/*` |
+
+Example — relocate the Swagger UI mount to `/docs/swagger-ui`:
+
+```bash
+java -Djuneau.swaggerui.path=docs/swagger-ui -jar my-app.jar
+```
+
+Three mixins remain hardcoded because their mount paths are specification-fixed and a runtime
+override would break spec-compliant clients:
+
+- `FaviconMixin` — `/favicon.ico` (browser convention + WHATWG HTML `rel="icon"` default).
+- `SeoMixin` — `/robots.txt` (RFC 9309 Robots Exclusion Protocol) and `/sitemap.xml`
+ (sitemaps.org protocol). The two endpoints sit on separate handler methods, not a multi-path
+ array, so they remain individually pinned.
+- `WellKnownMixin` — `/.well-known/security.txt` (RFC 8615 well-known URIs + RFC 9116
+ security.txt).
+
+**Migration note — multi-path collapse:** four mixins that previously declared multiple paths
+in a single op annotation's `path={...}` array — historical "convenience alias" defaults from
+before the SVL mechanism existed — have been collapsed to a single SVL-configurable path. The
+secondary aliases are now reached by overriding the SVL variable instead of being mounted by
+default:
+
+| Mixin | Primary default | Removed secondary alias(es) | Migration |
+|---|---|---|---|
+| `StaticFilesMixin` | `/static/*` | `/htdocs/*` | `-Djuneau.staticfiles.path=htdocs` |
+| `VersionMixin` | `/version` | `/info`, `/about` | `-Djuneau.version.path=info` (or `about`) |
+| `EchoMixin` | `/echo/*` | `/debug/echo/*` | `-Djuneau.echo.path=debug/echo` |
+| `RouteIndexMixin` | `/options` | `/routes` | `-Djuneau.routeindex.path=routes` |
+
+Deployers who relied on a secondary default must either set the SVL override at startup or
+compose a second mixin instance with the override. The default `BasicStaticFiles` classpath
+search root still walks both the `static/` and `htdocs/` directories at the JAR-resource layer
+— only the URL-side mount alias has been removed.
+
+SVL resolution happens once at `RestContext` construction time (see the earlier section above for the full
+resolution chain). Each configurable mixin ships with a `*_SvlPathOverride_Test` in
+`juneau-utest` that exercises the override end-to-end via `MockRestClient`.
+
+### Mixin path overrides now accept liberal input forms
+Building on an earlier change (SVL-configurable mixin paths) and an earlier change (`pathToken()` in the SVL
+scripting catalog), all 10 SVL-configurable mixin op-paths now wrap their override token in
+`#{pathToken(...)}` so users can supply any of the following forms and the framework always
+produces a clean mount:
+
+| Input form | Example | Effective mount |
+|---|---|---|
+| Bare token | `jsp` | `/jsp/*` |
+| Leading slash | `/jsp` | `/jsp/*` |
+| Trailing slash | `jsp/` | `/jsp/*` |
+| Both slashes | `/jsp/` | `/jsp/*` |
+| Wildcard suffix | `/jsp/*` | `/jsp/*` |
+| Multi-segment | `/api/v1/jsp/*` | `/api/v1/jsp/*` |
+
+Before this change, setting `juneau.jsp.path=/jsp` resulted in a double-slash path `//jsp/*`
+that silently failed to match any request. After this change, all of the above input forms
+resolve identically to the canonical mount.
+
+The `pathToken()` function (added in an earlier Phase D string catalog) strips leading/trailing
+slashes and a trailing `/*` wildcard suffix while preserving multi-segment paths. It is
+registered on `VarResolver.DEFAULT` via `defaultFunctions()` and is available to any SVL
+template. See the SVL scripting section above for the full function catalog.
+
+Each mixin's `*_SvlPathOverride_Test` has been extended with 5–6 new equivalence test cases
+(bare token, leading slash, trailing slash, both slashes, wildcard suffix, and multi-segment)
+covering all input forms per class.
+
+### OpenAPI 3.1 Emission + API-docs Mixin Pack
+`juneau-rest-server` now ships first-class OpenAPI 3.1 document generation alongside the existing Swagger v2 path, with the public surface composed via four small `@Rest(mixins=...)` mixins instead of a `apiFormat` string knob:
+
+**OpenAPI 3.1 emission core:**
+
+- New `org.apache.juneau.rest.server.openapi` package with `OpenApiProvider` SPI, `BasicOpenApiProvider`, and `BasicOpenApiProviderSession`. The session generates OpenAPI 3.1 via a JSON-level transformation of the existing Swagger 2.0 emission, so every existing Swagger-aware annotation surface (`@Schema`, `@Content`, `@StatusCode`, etc.) round-trips to the OpenAPI document with no source changes.
+- New `RestRequest.getOpenApi()` accessor and `RestContext.getOpenApiProvider()` / `RestContext.getOpenApi(Locale)` parallel the Swagger family.
+- New `RedocUI` swap (in `juneau-bean-openapi-v3`) renders OpenAPI 3.1 documents as a two-column Redoc-style HTML view for `text/html` requests.
+- New SVL variable `$OS{path}` mirrors `$SS{path}` for OpenAPI document lookups.
+- New `@Rest(openApiProvider=X.class)` attribute lets resources select a custom provider.
+- **`components.schemas` reuse.** `BasicOpenApiProviderSession` runs an explicit dedup pass after the Swagger-2.0-to-OpenAPI-3.1 transform: any inline schema that appears two or more times under operation parameter, request-body, or response-content slots is lifted into `components.schemas` and each occurrence is rewritten to a `{"$ref":"#/components/schemas/<name>"}` pointer. Names are derived from the schema's `title` (when present and unique) and fall back to synthesized `Schema<N>` k [...]
+- **YAML round-trip coverage.** `OpenApiYamlRoundTrip_Test` exercises `YamlSerializer.DEFAULT_READABLE` → `YamlParser.DEFAULT` over (a) a hand-built `OpenApi` bean, (b) the live document produced by a `BasicRestServlet`-based resource (mounted via the api-docs mixin pack), and (c) the `/openapi` endpoint served with `Accept: application/yaml`. Each path asserts structural equality across `openapi`, `info`, `servers`, `paths`, and `components.schemas`.
+
+**API-docs mixin pack:**
+
+The api-docs surface is now four sibling `@Rest(mixins=...)` mixins in the new `org.apache.juneau.rest.server.docs` package — composition replaces the `apiFormat` string knob:
+
+| Mixin | Default `paths` | Owns |
+|---|---|---|
+| `SwaggerMixin` | `/api` | Swagger v2 spec + `SwaggerUI` swap. Standard content negotiation. |
+| `SwaggerUiMixin` | `/swagger` | HTML-first mount; `@Rest(defaultAccept="text/html")` so bare browser requests render the Swagger-UI view. Transitively pulls in `SwaggerMixin`. |
+| `OpenApiMixin` | `/openapi`, `/openapi.json`, `/openapi.yaml` | OpenAPI 3.1 spec + `RedocUI` swap on `/openapi`. The `.json` and `.yaml` paths are **format-pinned** — they ignore the request `Accept` header and always emit the file extension's wire format (implemented via `RestResponse.getDirectWriter(...)`, not content negotiation, so the pin survives `Accept: text/html`). Mixin declares `@Rest(serializers={YamlSerializer.class})` so YAML is available for the `/openapi` content-negoti [...]
+| `RedocMixin` | `/redoc` | HTML-first mount; `@Rest(defaultAccept="text/html")`. Transitively pulls in `OpenApiMixin`. |
+
+Pick the mixins you want and the URL surface follows. Three common compositions:
+
+```java fragment
+// OpenAPI 3.1 spec only — /openapi, /openapi.json, /openapi.yaml.
+@Rest(path="/api", mixins=OpenApiMixin.class)
+public class ApiResource extends RestServlet { ... }
+
+// OpenAPI 3.1 + Redoc UI — pulls OpenApiMixin in transitively.
+@Rest(path="/api", mixins=RedocMixin.class)
+public class ApiResource extends RestServlet { ... }
+
+// Everything (post-10.0.0 default for BasicRestServlet / BasicRestResource).
+@Rest(path="/api", mixins={SwaggerUiMixin.class, RedocMixin.class})
+public class ApiResource extends RestServlet { ... }
+// Serves: /api, /swagger, /openapi, /openapi.json, /openapi.yaml, /redoc
+```
+
+Transitive resolution dedupes by class identity (`LinkedHashSet` in the framework's `collectRestMixins(...)` walk), so listing both UI mixins doesn't double-instantiate the spec mixins. Parent-chain aggregation also works — a child class's `@Rest(mixins=...)` unions with the parent's.
+
+`BasicRestServlet` and `BasicRestResource` migrated to declare `@Rest(mixins={SwaggerUiMixin.class, RedocMixin.class})` so subclasses get all six api-docs URLs by default. Apps that want to opt out (e.g. to mount only OpenAPI) can use `@Rest(noInherit={"mixins"}, mixins=...)` to cut off the inherited docs mixins.
+
+**Breaking changes:**
+
+| Removed | Replacement |
+|---|---|
+| `@Rest(apiFormat="swagger" \| "openapi" \| "both")` | Pick mixins. Both `/api` and `/openapi` mount when both spec mixins are present. |
+| `juneau.rest.apiFormat` system property | Removed. No replacement — apps that toggled this at runtime should pick mixins at compile time. |
+| `RestContext.getApiFormat()` | Removed. |
+| `RestServerConstants.API_FORMAT_SWAGGER` / `_OPENAPI` / `_BOTH` / `SYSPROP_apiFormat` / `PROPERTY_apiFormat` | Removed. |
+| `BasicRestOperations.getSwagger(...)` / `getOpenApi(...)` | Removed from the interface. `BasicRestServlet` / `BasicRestResource` mount these via `SwaggerMixin` / `OpenApiMixin` instead. |
+| `BasicGroupOperations.getChildrenSwagger(...)` / `getChildrenOpenApi(...)` (the `?Swagger` / `?OpenApi` query mirrors on group resources) | Removed. Hit `/api`, `/swagger`, `/openapi`, or `/redoc` directly — they are always mounted on `BasicRestServletGroup` / `BasicRestResourceGroup`. |
+| `BasicGroupOperations.HasSwaggerQueryParam` / `HasOpenApiQueryParam` matcher inner classes | Removed (no consumers left). |
+
+Migration sketch:
+
+```java fragment
+// 9.4.x:
+@Rest(apiFormat="openapi")
+public class MyApi extends BasicRestServlet { ... }
+
+// 10.0.0:
+@Rest(noInherit={"mixins"}, mixins=RedocMixin.class) // opt out of Swagger
+public class MyApi extends BasicRestServlet { ... }
+// or extend RestServlet directly and pick what you want:
+@Rest(mixins=RedocMixin.class)
+public class MyApi extends RestServlet implements BasicUniversalConfig { ... }
+```
+
+Apps that subclass `BasicRestServlet` / `BasicRestResource` without setting `apiFormat` or relying on the query mirrors get the new behavior (six api-docs URLs instead of one) automatically.
+
+### Static-Files Mixin
+`juneau-rest-server` now ships a `StaticFilesMixin` mixin in the new
+`org.apache.juneau.rest.server.staticfile` package that wraps the existing `BasicStaticFiles` plumbing
+in a servlet-level mixin. Any `@Rest`-annotated resource can opt into static-file serving via
+`@Rest(mixins = StaticFilesMixin.class)` without subclassing `BasicRestServlet` just to
+inherit `getHtdoc(...)`. Default mounts are `/static/*` and `/htdocs/*` (both routes share the
+same `@RestGet` handler) and the resolved `StaticFiles` bean is read from the importer's bean
+store at request time, so `@Bean StaticFiles` factory methods, headers, and classpath search
+roots flow through unchanged. See
+[Static-Files Mixin](/docs/topics/StaticFilesMixin) for the full reference, including
+multi-mount semantics, HEAD support, Cache-Control behavior, the OpenAPI-hidden mechanism, and
+the path-override constraint documented in the topic page's *Path-override constraint* section.
+
+### Convention-Endpoints Mixin Pack
+`juneau-rest-server` now ships a four-class **convention-endpoints mixin pack** in the new
+`org.apache.juneau.rest.server.convention` package covering the de-facto-standard URLs every
+public-facing service eventually wires up by hand. Each mixin is a single-purpose
+`@Rest`-annotated resource designed to be grafted into any servlet via `@Rest(mixins=...)` —
+configure via a `@Bean MixinClass` factory method on the host, leave the rest unmounted.
+
+- **`FaviconMixin`** — serves `/favicon.ico`. Default body is the Juneau-branded
+ `juneau-favicon.ico` shipped on the framework classpath; `Cache-Control: max-age=2592000, public`
+ (30 days). Builder methods: `bytes(byte[])`, `classpath(String)`, `cacheControl(String)`.
+- **`SeoMixin`** — serves `/robots.txt` and `/sitemap.xml`. Default robots body is the
+ RFC 9309 deny-all `User-agent: *\nDisallow: /\n`; default sitemap is an empty `<urlset>`.
+ Builder methods: `robotsAllow(...)` / `robotsDisallow(...)` / `robotsTxt(String)` for rule-based
+ or fully-formed bodies; `sitemapEntry(String url[, lastmod, changefreq, priority])` for
+ per-URL entries.
+- **`VersionMixin`** — serves `/version`, `/info`, and `/about` (synonyms in v1) as a
+ format-pinned JSON map sourced from `META-INF/MANIFEST.MF`, `git.properties`, and the running
+ JVM version. Default builder chain is `fromManifest().fromGitProperties().fromJavaVersion()`;
+ programmatic overrides via `entry(String, String)` / `entries(Map)` suppress the defaults so
+ the importer opts into each `fromXxx()` call explicitly. Spring Boot fat-jar tip:
+ pass `Builder.fromManifest(importerClassLoader)` to read the app's manifest instead of the
+ framework's.
+- **`WellKnownMixin`** — reserves `/.well-known/security.txt`. Returns `404 Not Found`
+ by default (RFC 9116 makes the absence of `security.txt` itself meaningful); set a body via
+ `Builder.securityTxt(String)`. The class reserves room for future `/.well-known/*` entries
+ (OIDC discovery, change-password, etc.) without committing v1 to a placeholder.
+
+All four endpoints carry `@OpSwagger(ignore = true)` so they're excluded from any Swagger /
+OpenAPI spec generated alongside them — convention URLs aren't API-meaningful and would just
+clutter the contract. See [Convention-Endpoints Mixin Pack](/docs/topics/ConventionEndpointsMixins)
+for the full reference, including per-mixin builder semantics, default-vs-override behavior, and
+real-container parity tests under both Jetty microservice and Spring Boot.
+
+**Framework alignment:** `RestContext.buildMixinContext(...)` was retuned to first consult the
+host's `BeanStore` for a pre-registered mixin instance (`beanStore.getBean(MixinClass)`) before
+falling back to `beanStore.instantiate(MixinClass)`. This makes `@Bean MixinClass` factory
+methods on the host first-class for **every** `@Rest(mixins=...)` mixin (not just the
+convention-endpoints pack), so a host can configure a mixin's builder once and let the framework
+use that exact instance instead of constructing a fresh one. The previous behavior — always
+instantiating via the mixin's own `Builder` — meant `@Bean` factories were silently bypassed
+for any mixin that exposed a `create()` static method. Existing mixins without `@Bean` factory
+methods on their hosts continue to instantiate via their builders unchanged.
+
+- **`@OpSwagger(ignore = true)` (new annotation member).** New boolean member on
+ `org.apache.juneau.rest.server.OpSwagger` that excludes an operation from the published
+ Swagger / OpenAPI specification. Honored by `BasicSwaggerProviderSession` (and therefore by
+ the OpenAPI 3.1 emission path, which transforms from the Swagger 2.0 output). Introduced to
+ hide the static-files mixin's greedy `/*` handlers, but reusable on any `@RestOp`-annotated
+ method that should not surface in the published API contract (favicon handlers, internal
+ probes, blob handlers). The legacy `BasicRestOperations.getHtdoc(...)` method now also
+ carries `@OpSwagger(ignore = true)` so `BasicRestServlet`-hosted apps get a clean spec for
+ free, with no need to subclass-and-suppress.
+- **HEAD body-suppression in `HttpResourceProcessor`.** The shared response processor for any
+ `HttpResource` return value now suppresses the response body when the request method is
+ `HEAD`, per [RFC 7231 §4.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.2):
+ headers (Content-Type, Content-Length, Cache-Control, plus any resource-supplied headers)
+ are emitted identically to the equivalent `GET`, but the body is not written. This benefits
+ any `@RestOp`-annotated handler that returns an `HttpResource`, not just the static-files
+ mixin.
+
+### Ops / Introspection Mixin Pack
+`juneau-rest-server` now ships a three-class **ops / introspection mixin pack** in the new
+`org.apache.juneau.rest.server.ops` package covering the operational surface every long-running service
+eventually grows. Each mixin is a single-purpose `@Rest`-annotated resource designed to be
+grafted into any servlet via `@Rest(mixins=...)` — configure via a `@Bean MixinClass` factory
+method on the host, leave the rest unmounted.
+
+- **`EchoMixin`** — serves `/echo/*` and `/debug/echo/*` request-echo / round-trip
+ introspection. Default `404 Not Found` until the host's
+ `@Rest(debug=...)` chain unlocks it (`@Rest(debug="conditional")` paired with a `Debug: true`
+ request header is the recommended posture). When debug is on, returns a JSON body reflecting
+ the inbound method, path, query string, headers (with `Authorization` / `Cookie` /
+ `Set-Cookie` / `Proxy-Authorization` / `X-API-Key` redacted to `[REDACTED]` by default), query
+ params, attributes, and bounded body capture (1 MB cap, configurable via
+ `Builder.bodyLimit(long)`). Builder methods: `bodyLimit(long)`, `redactedHeaders(String...)`
+ (replace), `redactHeader(String)` (additive). The handler dispatches on
+ `@RestOp(method="*")` so every HTTP verb hits the same code path.
+- **`AdminMixin`** — serves `/admin/threads` (JSON thread dump), `/admin/heap`
+ (`Runtime` + `MemoryMXBean` stats), `POST /admin/cache/flush` (run registered hooks),
+ and `/admin/ratelimit` (registered `RateLimitGuard` beans — both static configuration and
+ live per-key bucket snapshot; see the
+ [`RateLimitGuard.Storage.snapshot()` SPI extension](#ratelimitguardstoragesnapshot-spi--adminmixin-enrichment)
+ below). Default-deny via the new `org.apache.juneau.rest.server.guard.DenyAllGuard` until the host
+ registers a `@Bean RestGuardList` factory; the framework's bean-store override seam
+ **replaces** the entire annotation-derived guard list (including the deny-all) with the
+ user-supplied chain. Builder methods: `cacheFlush(String, Runnable)`,
+ `cacheFlushAll(Map)`, `threadNamePrefixExclude(String...)`. Returns `404 Not Found` when no
+ `RateLimitGuard` bean is registered.
+- **`RouteIndexMixin`** — serves `/options` and `/routes` (synonyms) returning a JSON
+ list of every `@RestOp`-annotated method on the host (and on every mixin on the host),
+ ordered by path. Each entry has `path`, `methods` (single-element list of the HTTP method),
+ `summary`, `description`, and `deprecated`. The route-index endpoint omits itself and every
+ `@OpSwagger(ignore=true)` operation, mirroring the audience separation between the OpenAPI
+ surface and the operator surface.
+
+All endpoints carry `@OpSwagger(ignore = true)` so they're excluded from any Swagger / OpenAPI
+spec generated alongside them — ops endpoints are not API-meaningful and shipping them in the
+spec leaks operator surface to consumers. See
+[Ops / Introspection Mixin Pack](/docs/topics/OpsIntrospectionMixins) for the full reference,
+including per-mixin builder semantics, default-vs-override behavior, the deny-all override seam
+for `AdminMixin`, sensitive-header-redaction guarantees, and real-container parity
+tests under both Jetty microservice and Spring Boot.
+
+- **`DenyAllGuard` (new class).** Companion `RestGuard` in the existing
+ `org.apache.juneau.rest.server.guard` package that rejects every request with `403 Forbidden`. Used
+ as the secure-by-default placeholder on operations or resources that require an
+ authentication / authorization chain the importer must explicitly opt into. Reusable on any
+ `@Rest(guards=...)` or `@RestOp(guards=...)` site that wants the same default-deny posture
+ with a `@Bean RestGuardList` override seam.
+
+### `RateLimitGuard.Storage.snapshot()` SPI + `AdminMixin` enrichment
+`org.apache.juneau.rest.server.guard.RateLimitGuard.Storage` gained an optional read-side SPI for
+operator-facing tooling, and `AdminMixin`'s `/admin/ratelimit` endpoint now uses it to
+emit live per-bucket state alongside the existing static configuration. Purely additive — the
+new method is a `default` returning `Map.of()` so external storage backends (Redis-backed,
+DynamoDB-backed, etc.) keep compiling unchanged.
+
+- **New `default Map<String, RateLimitGuard.BucketState> snapshot()` on `RateLimitGuard.Storage`.**
+ Returns a point-in-time view of every per-key bucket the storage backend currently tracks.
+ The default empty-map implementation is the backwards-compatible opt-out for storages that
+ can't cheaply enumerate buckets. In-memory implementations SHOULD override.
+- **New `RateLimitGuard.BucketState` record.** Token-bucket-vocabulary
+ per-bucket snapshot: `String key`, `double tokens` (current fractional fill level),
+ `int remaining` (`floor(tokens)` — mirrors the `X-RateLimit-Remaining` advisory header),
+ `boolean throttled` (convenience flag for `tokens < 1.0`), `Instant lastRequest`
+ (wall-clock timestamp of the bucket's last activity).
+- **Bundled `Storage.inMemory()` overrides `snapshot()`.** Walks the internal
+ `ConcurrentHashMap<String,Bucket>` and emits one `BucketState` per entry. Each per-bucket
+ read is consistent (synchronized on the bucket); the snapshot as a whole is not a global
+ point-in-time consistent view (consistent with the `ConcurrentHashMap` weakly-consistent
+ iteration semantics).
+- **`Bucket` gained a `lastWallMillis` field** alongside the existing `lastNanos` so
+ `BucketState.lastRequest` is wall-clock accurate (and not skewed by JVM suspension /
+ container pause). Costs 8 bytes per bucket (~800 KB worst-case at the default 100k-key cap).
+- **New public accessors on `RateLimitGuard`.** `getCapacity()`, `getPermitsPerSecond()`,
+ `isXForwardedForAware()`, `getExemptPaths()`, `getStorage()`, and the convenience
+ `snapshot()` that delegates to `getStorage().snapshot()`. Expose existing builder state for
+ operator-facing dashboards.
+- **`/admin/ratelimit` response shape — `buckets` renamed to `snapshot`, `config` enriched.**
+ The 10.0.0 ops-pack mixin emitted
+ `{ "guards": { "<bean-name>": { "config": {"class": "..."}, "buckets": [] } } }` with the
+ empty `buckets` array as an explicit placeholder. Now the response is
+ `{ "guards": { "<bean-name>": { "config": {"class": "...", "limit": N,
+ "permitsPerSecond": X, "xForwardedForAware": bool, "exemptPaths": [...] },
+ "snapshot": [ {"key": "...", "tokens": N.N, "remaining": N, "throttled": bool,
+ "lastRequest": "..."} ] } } }` — `snapshot` entries are sorted ascending by `key` for
+ stable operator-tooling output. Storage backends that don't override
+ `Storage.snapshot()` (e.g. Redis-backed impls) emit `"snapshot": []` for their bean entry.
+ The `buckets` field name was a placeholder that only ever emitted `[]`; the
+ rename to `snapshot` matches the SPI method name and is unlikely to affect any real
+ consumer.
+
+### Server-side SSE Helpers
+`juneau-rest-server` now includes an SSE helper layer for streaming endpoints:
+
+- `RestResponse.sse()` returns `SseResponseSupport` for fluent event/comment writes (`sendEvent(...)`, `comment(...)`, `flush()`, `sendFrom(...)`).
+- `SseBroadcaster` / `SseSubscription` provide in-memory fan-out for per-connection subscriptions with bounded subscriber queues.
+- `SseHeartbeat` provides optional scheduled `: ping` comments (when a `ScheduledExecutorService` bean is available).
+- New `RestOpArg` resolvers allow direct `SseBroadcaster` and `SseSubscription` method-parameter injection in `@RestOp` handlers.
+
+### Dynamic Child Resources
+Parent resources that extend `BasicRestServletGroup` or `BasicRestResourceGroup` can now mount and unmount children at runtime, in addition to declaring them statically via `@Rest(children=…)`.
+
+```java
+@Rest(path="/root")
+public class MyServer extends BasicRestServletGroup {}
+
+var server = new MyServer();
+// ... bootstrap into Jetty / Spring Boot / MockRestClient ...
+
+server.addChild(AlphaChild.class); // by class, path from @Rest(path)
+server.addChild(new BetaChild()); // by instance, path from @Rest(path)
+server.addChild("/gamma", new AlphaChild()); // explicit path override
+server.removeChild("/alpha");
+server.removeChild(AlphaChild.class);
+var keys = server.getChildResources().asMap().keySet();
+```
+
+The underlying state lives on `RestChildren`, which now holds children in a `volatile` copy-on-write snapshot. Route matching reads the snapshot without locks (preserving hot-path performance), and mutations are serialized through an internal write lock. `@RestInit` / `@RestPostInit` / `@RestPostInitChildFirst` run before `addChild` returns; `@RestDestroy` and `Servlet.destroy()` run on `removeChild` (recursively for grandchildren). Adding to an occupied path without `replace=true` throw [...]
+
+See [Child Resources](/docs/topics/ChildResources#dynamic-child-resources) for the full API and usage notes.
+
+### `@RestInject` Renamed to `@Bean` (moved to `juneau-commons`)
+
+`@RestInject` has been renamed to `@Bean` and moved from `org.apache.juneau.rest.annotation` to
+`org.apache.juneau.commons.inject`. Update imports accordingly:
+
+```java fragment
+// Before
+import org.apache.juneau.rest.annotation.RestInject;
+@RestInject
+public EncoderSet myEncoders(BeanStore bs) { ... }
+
+// After
+import org.apache.juneau.commons.inject.Bean;
+@Bean
+public EncoderSet myEncoders(BeanStore bs) { ... }
+```
+
+The semantics are identical; only the package and annotation name changed.
+
+### `SpringBeanStore` Renamed to `SpringBeanStore`
+
+`SpringBeanStore` has been renamed to `SpringBeanStore` for consistency with the `BasicBeanStore` → `BasicBeanStore` rename completed in this release. The old name `SpringBeanStore` is removed; update any import or type reference to `org.apache.juneau.rest.springboot.SpringBeanStore`.
+
+### `RestServerConstants`
+
+- **`RestServerConstants`** — Central place for **juneau-rest-server** static literals, starting with `Settings` keys. Serializer/parser session-option settings (`juneau.rest.sessionOptions.*`) use fields such as **`SETTING_sessionOptions_rejectWhenAllowlistEmpty`** and **`SETTING_sessionOptions_failOnInvalidAllowlistEntry`**. HTTP wire names for those options are fields on **`RestSharedConstants`** in **juneau-rest-common**. This supersedes the **`RestSessionOptionsSettings`** type.
+
+### `RestOpContext`: RestOp-group annotations
+
+- **`RestOpContext.getRestOpAnnotations()`** / **`findRestOpAnnotations()`** — Returns every annotation in the **`@RestOp` group** (including **`@RestGet`**, **`@RestPost`**, etc.) on the operation method and matching superclass/interface methods, in **child-to-parent** order; results are memoized until **`reset()`**. Typed **`AnnotationProvider.find(RestOp.class, MethodInfo)`** matches only a literal **`@RestOp`**, not grouped specializations such as **`@RestGet`**; use **`AnnotationInf [...]
+- **`RestOpContext.getNoInheritKeys()`** / **`findNoInherit()`** — Aggregates non-empty **`noInherit`** tokens from **every** **`RestOp`-group** annotation on the override chain (after SVL resolution and comma expansion), not only the nearest declaration. **`isInherited(String)`** on **`RestOpContext`** uses this combined set when deciding whether to merge **`allowedParserOptions`** / **`allowedSerializerOptions`** keys from the enclosing **`RestContext`**.
+- **`RestOpContext.getAllowedParserOptionsKeys()`** / **`findAllowedParserOptionsKeys()`** — Annotation-only parser allowlist keys: merges from **`RestContext`** when **`isInherited(PROPERTY_allowedParserOptions)`**, then aggregates **`allowedParserOptions`** from every **`RestOp`-group** annotation on the method override chain (parallel to **`RestContext`**, but **`RestContext`** uses only the nearest **`@Rest`**).
+- **`RestOpContext.getAllowedSerializerOptionsKeys()`** / **`findAllowedSerializerOptionsKeys()`** — Same for serializer keys (**`allowedSerializerOptions`**, **`PROPERTY_allowedSerializerOptions`**).
+
+### Request Attributes and Session Properties Separation
+
+Request attributes and parser/serializer session properties are now completely separate concepts:
+
+- **`defaultRequestAttributes`** from `@Rest` and `@RestOp` annotations are stored in `RequestAttributes` and accessed via `RestRequest.getAttributes()`
+- **Session properties** control parser/serializer behavior and must be set explicitly via the new programmatic API on `RestRequest`
+- Previously, request attributes were automatically merged into session properties, which conflated two distinct concerns
+
+#### New API
+
+**`RestRequest`** now provides explicit methods for setting session properties:
+
+```java
+// Set individual session properties
+req.setSerializerSessionProperty("maxDepth", 10);
+req.setParserSessionProperty("trimStrings", true);
+
+// Set multiple session properties at once
+req.setSerializerSessionProperties(Map.of("maxDepth", 10, "addBeanTypes", true));
+req.setParserSessionProperties(Map.of("trimStrings", true, "strict", true));
+
+// Bridge request attributes to session properties if needed
+req.setSerializerSessionProperties(req.getAttributes().asMap());
+```
+
+All methods support method chaining and are typically called from `@RestPreCall`, `@RestPostCall`, or within REST methods before parsing/serialization occurs.
+
+##### Migration
+
+Code that relied on `defaultRequestAttributes` being automatically available in serializer/parser sessions must now explicitly pass them:
+
+```java
+// Before (9.2.0 and earlier) - automatic merge
+@Rest(defaultRequestAttributes={"key: value"})
+public class MyResource {
+ @RestGet
+ public String get() {
+ // Serializer automatically received "key=value" in session properties
+ return null;
+ }
+}
+
+// After (10.0.0+) - explicit bridging
+@Rest(defaultRequestAttributes={"key: value"})
+public class MyResource {
+ @RestGet
+ public String get(RestRequest req) {
+ // Explicitly pass attributes to serializer if needed
+ req.setSerializerSessionProperties(req.getAttributes().asMap());
+ return null;
+ }
+}
+```
+
+This separation provides clearer semantics: request attributes are metadata about the request, while session properties are configuration for parsers and serializers.
+
+### Session option allowlists and `noInherit`
+
+Serializer and parser session-option allowlists are built from annotation contributions, merged with validation in **`RestSessionOptionAllowlistMerge`**, and exposed lazily via **`Memoizer`** on **`RestContext`** and **`RestOpContext`**. Effective keys are returned as unmodifiable **`SortedSet<String>`** (natural order). **`RestContext.reset()`** clears cached allowlists and cascades **`reset()`** to operation contexts.
+
+**`noInherit`** is an attribute on **`@Rest`**, **`@RestOp`**, and HTTP operation annotations (**`@RestGet`**, **`@RestPost`**, **`@RestPut`**, **`@RestPatch`**, **`@RestDelete`**, **`@RestOptions`**). It lists which inherited allowlist attributes should **not** be merged from less-derived declarations. Supported names include **`allowedParserOptions`** and **`allowedSerializerOptions`** (case-insensitive; SVL and comma-separated tokens are expanded like allowlist values). The standalone [...]
+
+#### `noInherit` Inheritance Semantics
+
+**`noInherit`** on method-level `@RestOp` annotations controls **class-level context inheritance only**, not method-level annotation inheritance:
+
+- **Class-level inheritance**: When `noInherit="allowedSerializerOptions"` is set on a `@RestOp` annotation, the operation will **not** inherit `allowedSerializerOptions` from the enclosing `@Rest` class annotation
+- **Method-level inheritance**: `allowedSerializerOptions` from parent method `@RestOp` annotations (on overridden methods in parent classes) are **always** inherited, regardless of `noInherit` settings
+
+```java
+@Rest(allowedSerializerOptions="classOpt")
+public class Parent {
+ @RestGet(allowedSerializerOptions="parentMethodOpt")
+ public void get() {}
+}
+
+public class Child extends Parent {
+ // noInherit blocks "classOpt" from @Rest, but NOT "parentMethodOpt" from parent method
+ @RestGet(allowedSerializerOptions="childMethodOpt", noInherit="allowedSerializerOptions")
+ @Override
+ public void get() {}
+ // Effective allowlist: ["childMethodOpt", "parentMethodOpt"]
+ // Does NOT include "classOpt" due to noInherit
+}
+```
+
+This distinction allows fine-grained control: you can block class-level options while still inheriting from overridden methods, which is typically the desired behavior for method-level configuration.
+
+Programmatic **`RestContext.Builder`** / **`RestOpContext.Builder`** no longer expose public **`appendAllowed*`** or **`allowedParserOptions` / `allowedSerializerOptions`** list setters for these allowlists; framework code applies contributions through package-visible **`applyFramework*AllowlistContribution`** methods during annotation processing.
+
+### Swagger: serializer/parser session option parameters
+
+Generated Swagger (OpenAPI 2) for REST operations now includes optional documentation parameters for Juneau serializer/parser session options on the wire: headers `X-Juneau-Serializer-Options` and `X-Juneau-Parser-Options` (JSON5 object strings) and query parameters `juneauSerializerOptions` / `juneauParserOptions` (UON-encoded maps). Canonical names are the `HEADER_*` / `QUERY_*` fields on `RestSharedConstants` in **juneau-rest-common**. Existing explicit parameters with the same `in` + [...]
+
+### `RestContext.Builder` and `RestOpContext.Builder` removed
+
+`RestContext.Builder` and `RestOpContext.Builder` are no longer part of the public API. All resource-level and operation-level configuration that was previously expressed through fluent builder calls now flows exclusively through `@Rest(...)` / `@RestOp(...)` annotation attributes or `@Bean`-annotated methods and fields.
+
+Key changes:
+
+- **`RestContext.Builder`** — entirely removed from public surface; the framework no longer publishes a builder instance to `@RestInit` hooks or the resource constructor. Replace each `builder.xxx(...)` call with the equivalent `@Rest(xxx=...)` attribute or a `@Bean(name="xxx")`-annotated bean-supplier method.
+- **`RestOpContext.Builder`** — same removal. Replace `builder.guards(...)`, `builder.converters(...)`, etc. with `@RestOp(guards=...)`, `@RestOp(converters=...)`, etc.
+- **`RestContextInit` record** replaces the old `RestContext.create(resourceClass, parent, cfg).init(supplier).path(p).children(c).build()` factory chain. The six bootstrap fields (resource class, parent context, servlet config, resource supplier, path, children) plus an optional `Consumer<BasicBeanStore>` hook are bundled in one immutable record. The common top-level case becomes `new RestContext(new RestContextInit(MyResource.class, () -> new MyResource()))`.
+- **`@RestInit(RestContext.Builder b)`** / **`@RestInit(RestOpContext.Builder b)`** injection hooks — removed. The supported `@RestInit` parameter shapes are now `ServletConfig`, `ServletContext`, the resource instance, `@Bean`-supplied beans, and zero-arg.
+- **Annotation memoizers** — every setting previously set by the builder is now computed lazily by a `findXxx()` method on `RestContext` / `RestOpContext` that walks the `@Rest` / `@RestOp` annotation chain, system properties, and the `@Bean` bean store. Results are cached in a `Memoizer<T>` and invalidated by `RestContext.reset()`.
+
+See the [V10.0 Migration Guide](/docs/topics/V10MigrationGuide) for a per-setting replacement table.
+
+### Bean precedence: Spring > `@Bean` (inject) > default (breaking)
+
+The precedence order used by `RestContext` to resolve framework-managed beans (`EncoderSet`, `SerializerSet`, `ParserSet`, `ThrownStore`, `Config`, `VarResolver`, `HttpPartSerializer`, `HttpPartParser`, `Messages`, `MethodExecStore`, `JsonSchemaGenerator`, `StaticFiles`, `SwaggerProvider`, `RestOperations`, `RestChildren`, named `HeaderList` / `NamedAttributeMap` slots, etc.) has been flipped:
+
+| Tier | 9.4 and earlier | 10.0+ |
+| ---- | --------------- | ---- |
+| 1 (highest) | `@Bean` (inject) method on the resource | **Spring `@Bean`** (or any bean reachable through the bootstrap / overriding-parent bean store) |
+| 2 | Spring `@Bean` (via `SpringBeanStore`) | **`@Bean` (inject) method on the resource** |
+| 3 (lowest) | Memoizer-backed framework default | **Memoizer-backed framework default** |
+
+`@Bean` (inject) is now documented as a *programmable default*, analogous to Spring's `@ConditionalOnMissingBean`: if a Spring bean of the same type (and name, where named) is available, it wins; otherwise the `@Bean` method runs and its result is cached. Resolution short-circuits on the first hit, so non-Spring deployments collapse to the familiar `@Bean > default` chain.
+
+Mechanically, the change is implemented through three new affordances on `BasicBeanStore`:
+
+- An **overriding parent** bean store (constructor / `Builder.overridingParent(...)`) that is consulted *before* local entries during `getBean(...)` / `getBeanSupplier(...)`. The Spring boot integration installs the application context bridge here.
+- **`addDefaultSupplier(Class, Supplier)`** (and a named variant) that registers a fallback supplier consulted *after* local entries and the regular parent. `RestContext` registers every memoizer-backed framework bean as a default supplier, so they only fire when nothing higher up in the chain has bound the type.
+- `RestContext.getBootstrapBeanStore()` continues to return a `BasicBeanStore`, but the resolution order on that store now reflects the new precedence model.
+
+Side effects of the new model:
+
+- The legacy `DELAYED_INJECTION` / `DELAYED_INJECTION_NAMES` skip lists in `RestContext` are gone. The `@Bean` (inject) method walk now runs for every type with no hand-maintained filter; default-supplier presence is what auto-derives "skip this for now" behavior.
+- Inside each framework-bean memoizer body, redundant `bs.getBean(X).ifPresent(impl::override)` lookups have been removed. The bean store is the precedence engine — defaults no longer probe the store on the way out.
+- All `RestContext.getX()` accessor methods (`getConfig()`, `getVarResolver()`, etc.) now route through `beanStore.getBean(X)`, so internal callers see Spring overrides without needing to hit the bean store directly.
+
+#### Migration
+
+If you previously relied on `@Bean` (inject) overriding a Spring `@Bean`, you have a few options in 10.0+:
+
+- **Preferred — let Spring win.** Remove the `@Bean` (inject) method and lean on the Spring bean. The `@Bean` (inject) method was effectively a "default", which Spring already provides via `@ConditionalOnMissingBean` semantics on the bean factory.
+- **Skip the Spring `@Bean`.** Don't declare the type as a Spring `@Bean` and the `@Bean` (inject) method will continue to win over the framework default.
+- **Use Spring-native overrides.** Mark the relevant Spring bean with `@Primary`, `@MockBean`, or `@ConditionalOnProperty` so Spring itself picks the right candidate. The bean store will then surface that candidate to `RestContext`.
+- **Programmatic registrations** that flow through `args.beanStoreConfigurer()` (the `Consumer<BasicBeanStore>` hook on `RestContextInit`) land as *regular* entries — the same tier as `@Bean` (inject). They no longer beat Spring; if you need that, register the bean as a Spring `@Bean` (or contribute it through the overriding-parent layer in a custom `BasicBeanStore` subclass).
+
+`SpringBeanStore` keeps backward-compatible behavior at the API level: as a `BasicBeanStore` subclass it picks up the new precedence model automatically when used through `RestContext`. Custom `BasicBeanStore` subclasses that want Spring-equivalent precedence can wire themselves in via `Builder.overridingParent(...)`; the existing `parent(...)` slot continues to behave as a regular fallback (consulted after local entries).
+
+### `RestResponse.setSerializer(Serializer)`
+
+- **`RestResponse.setSerializer(Serializer)`** — Forces the serializer used for the response body (via `setContent(Object)` or equivalent), bypassing `Accept` header negotiation. Pass `null` to clear the override. `Content-Type` is still set by the response processor from the serializer when appropriate; you can call `setContentType(String)` first if needed.
+
+### Download / `Content-Disposition: attachment`
+
+- **`RestResponse.downloadAs(String filename)`** — Sets `Content-Disposition: attachment` with a quoted `filename` parameter so browsers typically download the response body (e.g. `res.downloadAs("example.pdf").setContent(bytes)`).
+- **`ContentDisposition.attachment(String)`** and **`HttpHeaders.contentDispositionAttachment(String)`** (juneau-rest-common) — Build a safe header value (escapes `\` and `"` in the filename; rejects null/blank names and CR/LF). RFC 5987 `filename*` is not set; use a custom header value if you need full Unicode filenames.
+
+### RFC 7807 / 9457 Problem-Details server-side wiring
+
+Juneau REST resources can now emit `application/problem+json` (RFC 7807 / 9457) responses end-to-end. The wiring complements the `juneau-bean-rfc7807` module shipped earlier in this release (the typed `Problem` bean) by adding a response processor, an opt-in annotation flag, a `ProblemException` throw seam, and a `ProblemMapper` SPI for custom exception-to-`Problem` translation.
+
+#### Opt-in flag — `@Rest(problemDetails)` / per-op fanout
+
+- **`@Rest(problemDetails)`** — `String` attribute, tri-state (`"true"` / `"false"` / `""` inherits). When `"true"`, uncaught `BasicHttpException`s on the resource are rendered as `application/problem+json` regardless of the client `Accept` header (RFC 7807 §3 — the spec encourages this on errors).
+- **`@RestGet(problemDetails)` / `@RestPost(problemDetails)` / `@RestPut(problemDetails)` / `@RestPatch(problemDetails)` / `@RestDelete(problemDetails)` / `@RestOptions(problemDetails)` / `@RestOp(problemDetails)`** — same tri-state attribute on every method-level annotation. Per-op `"true"` opts an op in on a non-opted-in resource; per-op `"false"` opts an op out of an opted-in resource; `""` inherits the resource-level value.
+
+```java
+@Rest(path="/orders", problemDetails="true")
+public class OrderResource {
+
+ @RestGet("/{id}")
+ public Order get(@Path long id) {
+ throw new NotFound("Order {0} not found", id); // → 404 application/problem+json
+ }
+
+ @RestPost
+ public Problem create(Order in) {
+ if (in.balance < in.amount)
+ return Problem.fromStatus(403, "Insufficient credit", "Balance " + in.balance + " < amount " + in.amount)
+ .setType(URI.create("https://example.com/probs/out-of-credit"))
+ .set("balance", in.balance);
+ return null;
+ }
+}
+```
+
+##### `ProblemDetailsProcessor` (response processor)
+
+- **`org.apache.juneau.rest.server.processor.ProblemDetailsProcessor`** — slotted into the default `responseProcessors` chain after `ThrowableProcessor`. Handles four content shapes:
+ - A returned `Problem` bean — serialized directly. Honors client `Accept` strictly.
+ - A returned or thrown `ProblemException` — unwrapped via `ProblemException.getProblem()`. Honors `Accept` on non-opted-in ops (back-compat); flips to error-path semantics (ignores `Accept`) on opted-in ops.
+ - A thrown `BasicHttpException` — gated by `isProblemDetails()` (per-op resolution); on error path ignores `Accept` and adapts via the registered `ProblemMapper` (if any matches) or `ProblemAdapters.fromException(...)` otherwise.
+ - Any other thrown `Throwable` — only when an explicit `ProblemMapper` matches its class hierarchy. Mappers that return `null` are skipped and the chain continues; if no mapper matches, the processor returns `NEXT`.
+- Status precedence — when `Problem.getStatus()` is non-`null`, the processor calls `RestResponse.setStatus(int)` with that value. Otherwise it leaves the existing status alone (`RestSession.run()` normalizes `0` → `200` so per-op default codes still flow through).
+- `type` field policy — serialized as-is. A `null` `type` is omitted from the JSON output rather than synthesized as `"type":"about:blank"` (preserves the absent-vs-explicit distinction).
+
+##### `ProblemException` throw seam
+
+- **`org.apache.juneau.bean.rfc7807.ProblemException`** — `RuntimeException` carrying a `Problem` payload. Lets handlers throw a custom problem without manually building a `BasicHttpException`:
+
+```java
+throw new ProblemException(
+ Problem.fromStatus(403, "Insufficient credit", "Balance 30 < cost 50")
+ .setType(URI.create("https://example.com/probs/out-of-credit"))
+ .set("balance", 30)
+ .set("accounts", List.of("/account/12345", "/account/67890")));
+```
+
+ Lives in `juneau-bean-rfc7807` (the bean module stays free of any `juneau-rest-server` dep). The processor unwraps it via `instanceof ProblemException`.
+
+##### `ProblemAdapters` static helper
+
+- **`org.apache.juneau.bean.rfc7807.adapter.ProblemAdapters#fromException(BasicHttpException)`** — lives in `juneau-rest-common`. Adapts a `BasicHttpException` (status code, reason phrase, `getMessage()`) into a `Problem` bean. Used by the processor's default error-path fallback when no `ProblemMapper` matches. Available for direct caller use as well.
+
+##### `ProblemMapper` SPI — declarative exception translation
+
+- **`org.apache.juneau.bean.rfc7807.ProblemMapper<T extends Throwable>`** — pluggable interface lets users map arbitrary throwables into custom `Problem` shapes without writing a processor. Two methods: `Class<T> getExceptionType()` (used for hierarchy-aware dispatch) and `Problem map(T exception)`. Returning `null` defers to the next-most-specific mapper, and ultimately to the built-in `ProblemAdapters.fromException(...)` fallback for `BasicHttpException` subclasses.
+- **`org.apache.juneau.bean.rfc7807.ProblemMapperList`** — aggregator bean for registering more than one mapper on the same resource. The Juneau `@Bean` walk pairs each `@Bean` factory with its declared return type, so multiple `@Bean public ProblemMapper foo()` methods on one class collapse onto the single `ProblemMapper.class` slot; wrap them in a `ProblemMapperList` to keep them all reachable. The single-mapper case (`@Bean public ProblemMapper<MyException> mapper()`) keeps working un [...]
+
+```java
+public class InsufficientCreditMapper implements ProblemMapper<InsufficientCreditException> {
+ public Class<InsufficientCreditException> getExceptionType() { return InsufficientCreditException.class; }
+ public Problem map(InsufficientCreditException e) {
+ return Problem.fromStatus(403, "Insufficient credit", e.getMessage())
+ .setType(URI.create("https://example.com/probs/out-of-credit"))
+ .set("balance", e.getBalance())
+ .set("cost", e.getCost());
+ }
+}
+
+@Rest(problemDetails="true")
+public class AccountResource {
+ // Single-mapper case.
+ @Bean public ProblemMapper<InsufficientCreditException> creditMapper() { return new InsufficientCreditMapper(); }
+
+ // Multi-mapper case.
+ @Bean public ProblemMapperList problemMappers() {
+ return ProblemMapperList.of(new InsufficientCreditMapper(), new OrderNotFoundMapper());
+ }
+}
+```
+
+##### `ProblemLocalizationStrategy` — future-work seam
+
+- **`org.apache.juneau.bean.rfc7807.ProblemLocalizationStrategy`** — functional-interface SPI seam for locale-aware translation of `Problem.title` / `Problem.detail`. The default in-tree implementation is `ProblemLocalizationStrategy.IDENTITY` (pass-through); the processor consults the bean store for a registered strategy on every emission and falls back to `IDENTITY` when none is present, so the seam is invisible at zero cost on the hot path until a deliberate strategy is contributed.
+- A reference `Messages`-driven (resource-bundle) implementation is intentionally **out of scope for this release**. The `IDENTITY` default keeps the call-site contract stable so a future implementation can be dropped in without changing the processor.
+
+##### `Accept` negotiation policy
+
+- **Error path** (thrown `BasicHttpException` / thrown `ProblemException` / mapped `Throwable` on opted-in ops): emits `application/problem+json` regardless of the client `Accept` — the spec encourages this on errors.
+- **Success path** (returned `Problem` / returned `ProblemException`): honors the client `Accept` strictly. The processor only emits `application/problem+json` if `Accept` matches it (or `*/*`); otherwise it passes through to the next processor in the chain unchanged.
+
+See [REST Server — RFC 7807 Problem Details](/docs/topics/RestServerProblemDetails) for the full topic, including worked examples for the resource-level opt-in, the per-op fanout, the `ProblemException` throw path, and `ProblemMapper` registration.
+
+### Jakarta Bean Validation 3.x integration
+**Validation is opt-in and disabled by default.** This is a non-negotiable contract: a fresh `RestContext` built with no validation-related markers anywhere on the resource never instantiates a `Validator`, never invokes a constraint check, and never adds a per-request cost — even when `jakarta.validation-api` and a concrete provider (e.g. Hibernate Validator) are on the classpath. Spring devs migrating in should be aware that, unlike Spring MVC where `@Valid` is automatic, Juneau requir [...]
+
+#### Opt-in marker
+
+- **`@jakarta.validation.Valid`** on a `@Content` / `@FormData` / `@Request`-bound parameter — turns on validation for that one parameter on that one handler. Per-parameter granularity: marking one parameter never opts in a sibling parameter, an unrelated handler, or the resource as a whole. Spring's `@org.springframework.validation.annotation.Validated` and the legacy `@javax.validation.Valid` are also recognized via fully-qualified-name (FQN) detection, so no compile-time dependency on [...]
+
+```java
+@RestPost("/orders")
+public Order create(@Content @Valid OrderRequest in) {
+ // If any of OrderRequest's @NotBlank / @Size / @Min / etc. constraints fail,
+ // the handler is never called. Response is 400 with the violations.
+ return orderService.create(in);
+}
+```
+
+##### Failure response shape
+
+- **Without `@Rest(problemDetails="true")`** (or when problem-details is off for the op): `400 Bad Request` with `application/json` body of the form `{ "status": 400, "errors": [ { "path": "...", "message": "...", "constraint": "..." }, ... ] }`. The shape mirrors Spring's `MethodArgumentNotValidException` payload at the top level for migration friendliness.
+- **With `@Rest(problemDetails="true")`** (the RFC 7807 / 9457 path documented above): `400 application/problem+json` with the standard problem fields (`status`, `title`, `detail`) plus an `errors[]` extension carrying the same `ValidationViolation` list.
+
+The `invalidValue` field of each violation is **omitted by default** for privacy reasons. Handlers that need to surface the offending value (e.g. an internal debugging endpoint) can include it explicitly by constructing the violation with the `invalidValue` setter; the renderer honors a non-`null` value verbatim.
+
+##### Dependency stance — optional, `provided` scope
+
+`juneau-rest-server`'s `pom.xml` declares `jakarta.validation-api:3.0.2` in `provided` scope. Consumers who never write `@Valid` pay no transitive cost — no Jakarta Validation, no Hibernate Validator, no Jakarta EL pulled in automatically. Consumers who do opt in supply the runtime provider on their own classpath:
+
+```xml
+<!-- in the consumer's pom.xml -->
+<dependency>
+ <groupId>org.hibernate.validator</groupId>
+ <artifactId>hibernate-validator</artifactId>
+ <version>8.0.3.Final</version>
+</dependency>
+<dependency>
+ <groupId>org.glassfish.expressly</groupId>
+ <artifactId>expressly</artifactId>
+ <version>5.0.0</version>
+</dependency>
+```
+
+##### Graceful degradation
+
+If a parameter carries `@Valid` but no Jakarta Validation provider is reachable at runtime (the API JAR is on the classpath but the engine isn't), the integration silently skips validation and emits a one-shot `WARNING` log per JVM:
+
+```text
+Jakarta Bean Validation was requested via @Valid on a REST argument, but no
+jakarta.validation.Validator bean is registered and no runtime provider
+(e.g. org.hibernate.validator:hibernate-validator + jakarta.el implementation)
+is on the classpath. Validation will be silently skipped for this and
+subsequent requests. Add a provider dependency or register a Validator bean
+to enable validation.
+```
+
+The request continues through the handler with the bean unmodified — the off-by-default contract is preserved even in misconfigured deployments. This matches the existing FQN-detection precedent: feature presence is signaled by the consumer (here via `@Valid`) but the engine is a runtime resolution that fails open, not a startup-time hard requirement.
+
+##### `Validator` resolution order
+
+The integration consults, in order:
+
+1. A user-supplied `jakarta.validation.Validator` bean visible from the resource's `BeanStore` (typically contributed via `@Bean public Validator validator() { ... }` on the resource). Use this when the deployment needs custom message interpolators, group sequences, or a non-default `ConstraintValidatorFactory`.
+2. A lazily-built JVM-wide default obtained from `Validation.buildDefaultValidatorFactory()`. Cached after the first successful build so the factory cost is paid at most once per JVM.
+3. `null` — the graceful-degradation path described above.
+
+##### New API surface
+
+- **`org.apache.juneau.rest.validation.ValidationException`** — `BadRequest` (400) subclass carrying an immutable `List<ValidationViolation>`. Thrown from `ContentArg` / `FormDataArg` / `RequestBeanArg` after the bean is bound but before the handler is called.
+- **`org.apache.juneau.rest.validation.ValidationViolation`** — POJO with `path`, `message`, `constraint`, and optional `invalidValue` fields. Serializable to JSON via Juneau's default serializers.
+- **`org.apache.juneau.rest.validation.BeanValidator`** — static dispatcher that performs FQN-based opt-in detection and runs the validator. Acts as the seam between the arg resolvers and the underlying `jakarta.validation.Validator`.
+
+##### What's out of scope (v1)
+
+- **Method-level validation** (`@Validated` on the resource class, constraints on `@RestOp` return values). The integration validates parameter beans only.
+- **Schema-driven validation** (JSON-Schema constraints on the request body). That lives in `JsonSchemaGenerator` territory.
+- **`Messages.properties` bridge** between Juneau's `Messages` and Jakarta's `ValidationMessages.properties`. The two bundles don't compose automatically; document and configure separately.
+
+See [REST Server — Jakarta Bean Validation](/docs/topics/RestServerValidation) for the full topic, including worked examples for the opt-in marker, the problem-details and non-problem-details response shapes, custom `Validator` registration, custom `Constraint` annotations, and the graceful-degradation contract.
+
+### Conditional-GET / ETag helpers
+
+`juneau-rest-server` now ships an RFC 7232 conditional-request layer so handlers no longer need to hand-roll `ETag` / `Last-Modified` / `If-None-Match` / `If-Match` plumbing. The wiring is purely additive — existing handlers continue to work unchanged.
+
+#### Response-side setters on `RestResponse`
+
+Six fluent setters on <a href="/site/apidocs/org/apache/juneau/rest/server/RestResponse.html" target="_blank">RestResponse</a>:
+
+- `eTag(String)` / `eTag(EntityTag)` — sets the `ETag` response header. The string form delegates to `EntityTag.of(...)` so quoting (strong: `"v1"`; weak: `W/"v1"`) is validated up front.
+- `lastModified(Instant)` / `lastModified(ZonedDateTime)` — sets the `Last-Modified` response header in RFC 7231 IMF-fixdate format. Values are truncated to second precision and converted to UTC.
+- `cacheControl(String)` / `cacheControl(CacheControl.Builder)` — sets the `Cache-Control` response header. The builder form gives a typed API for the standard directives.
+
+##### Typed `Cache-Control` builder
+
+New <a href="/site/apidocs/org/apache/juneau/http/header/CacheControl.Builder.html" target="_blank">CacheControl.Builder</a> in `juneau-rest-common` offers a typo-resistant fluent API over the directive set: `publicCache()` / `privateCache()`, `noCache()` / `noStore()` / `noTransform()`, `mustRevalidate()` / `proxyRevalidate()`, `immutable()`, `maxAge(long)` / `maxAge(Duration)`, `sMaxAge(...)`, `staleWhileRevalidate(...)`, `staleIfError(...)`, and `extension(String)` for forward-compat [...]
+
+##### Request-side `checkPreconditions(RestResponse)`
+
+New <a href="/site/apidocs/org/apache/juneau/rest/server/RestRequest.html#checkPreconditions-org.apache.juneau.rest.server.RestResponse-" target="_blank">RestRequest.checkPreconditions(RestResponse)</a> evaluates the four conditional request headers against the `ETag` / `Last-Modified` already set on the response, and returns an `Optional<BasicHttpException>`:
+
+- Empty → no precondition matched and the handler may proceed.
+- Non-empty → the contained exception (a `BasicHttpException` carrying `304 Not Modified` or `412 Precondition Failed`) should be thrown to short-circuit.
+
+The implementation follows RFC 7232 §6 ordering exactly: `If-Match` → `If-Unmodified-Since` → `If-None-Match` → `If-Modified-Since`. ETag matching honors RFC 7232 §2.3.2 (strong comparison for `If-Match`; weak comparison for `If-None-Match`). `If-Modified-Since` is only consulted on `GET` / `HEAD`. Malformed dates are silently ignored per Postel's law.
+
+##### Typical handler shape
+
+```java
+@RestGet("/orders/{id}")
+public Order get(@Path long id, RestRequest req, RestResponse res) {
+ var order = repo.find(id);
+ res.eTag("\"" + order.version() + "\"")
+ .lastModified(order.updated())
+ .cacheControl(CacheControl.create().publicCache().maxAge(60).build());
+ req.checkPreconditions(res).ifPresent(e -> { throw e; });
+ return order;
+}
+```
+
+##### Notes
+
+- The response's `ETag` / `Last-Modified` headers **must be set before** `checkPreconditions(...)` is called — the check reads from the already-set response headers so it can compare against the values the handler intends to send.
+- `If-Match` always uses strong comparison: a `W/"v1"` response ETag never matches an `If-Match`, even if the opaque tags are identical.
+- `If-None-Match` uses weak comparison: `"v1"` and `W/"v1"` both match. The wildcard `*` matches when any ETag is set on the response.
+
+See [REST Server — Conditional-GET / ETag Helpers](/docs/topics/RestServerConditionalGet) for the full topic, including the 16-cell precedence matrix, weak/strong ETag rules, and a worked round-trip example.
+
+### Standalone servlet companions for dual-use resources
+
+The bundled `Basic*Resource` mixins are designed for composition into a host via `@Rest(mixins=...)`,
+where each pins its operations at an op-level sub-path (e.g. `@RestGet(path="/jsp/*")`). That shape
+is awkward when a resource needs to be deployed *on its own* as a top-level servlet: mounting a
+mixin-shaped resource at its own path doubles the prefix (`/jsp/jsp/...`).
+
+This release adds thin **standalone `Basic*Servlet` companions** for the dual-use resources. Each
+companion declares its mount at the class level via `@Rest(paths="/<token>/*")` (sibling top-level
+registration) and pins its op at `/*`, so it serves at a clean sibling path (e.g. JSPs at `/jsp/*`)
+without prefix-doubling. The mixin `Basic*Resource` forms are unchanged and keep their op-pinned
+sub-path behavior.
+
+| Standalone servlet | Mounts at | Delegates to (mixin) |
+| --- | --- | --- |
+| `JspServlet` | `/jsp/*` | `JspMixin` |
+| `FreemarkerServlet` | `/freemarker/*` | `FreemarkerMixin` |
+| `MustacheServlet` | `/mustache/*` | `MustacheMixin` |
+| `ThymeleafServlet` | `/thymeleaf/*` | `ThymeleafMixin` |
+| `StaticFilesServlet` | `/static/*` | `StaticFilesMixin` |
+| `VersionServlet` | `/version/*` | `VersionMixin` |
+| `AdminServlet` | `/admin/*` | `AdminMixin` |
+
+- **Shared logic, no drift.** The four view servlets share an abstract base, `ViewServlet`
+ (in `org.apache.juneau.rest.server.view`), whose single `@RestGet(path="/*")` op delegates to a new
+ engine-agnostic `RawTemplateDispatcher` interface. The four `Basic*Resource` view mixins implement
+ `RawTemplateDispatcher`, so the standalone and mixin forms route raw `.jsp` / `.ftlh` / `.mustache`
+ / `.html` dispatch through the *same* code and cannot diverge. The non-view companions
+ (`StaticFilesServlet`, `VersionServlet`, `AdminServlet`) hold their matching
+ `Basic*Resource` as a delegate and forward each op to it.
+- **`AdminServlet`** carries the same `@Rest(guards=DenyAllGuard.class)` default-deny posture as
+ the mixin — every admin path returns `403` until an auth `RestGuardList` is registered.
+- **`HealthMixin`** is intentionally *not* given a companion: it already extends
+ `BasicRestServlet` (it is itself a standalone-capable servlet) and mounts at fixed absolute paths
+ (`/healthz`, `/readyz`, `/livez`) that do not suffer the prefix-doubling problem.
+
+See [REST Server — Standalone vs Mixin Resources](/docs/topics/RestServerStandaloneVsMixin) for the
+full convention.
+
+### Servlet self-registration (top-level paths)
+
+Three mechanisms register a servlet at the top-level paths it self-declares (`@Rest(paths)` /
+`getPaths()`), all reading the same resolution chain via
+`RestContext.resolveTopLevelPaths(Class, Object, BeanStore)` — the same resolver the Jetty
+microservice auto-discovery loop already uses, so mount paths are identical across runtimes:
+
+- **Non-Spring (`web.xml` / external WAR):** a `jakarta.servlet.ServletContainerInitializer`,
+ `JuneauRestServletContainerInitializer` (in `juneau-rest-server`), auto-mounts concrete
+ `@Rest`-annotated `RestServlet` subclasses that self-declare top-level paths. It is opt-in via the
+ `juneau.rest.auto-register=true` context init parameter, so it never collides with servlets
+ declared manually in `web.xml`.
+
+See [REST Server — Self-Registration](/docs/topics/RestServerSelfRegistration) for the full topic.
+
+## juneau-rest-server-springboot
+
+### Servlet self-registration (Spring Boot)
+
+Two Spring Boot mechanisms register a Juneau servlet at the top-level paths it self-declares
+(`@Rest(paths)` / `getPaths()`), both deriving the mapping from
+`RestContext.resolveTopLevelPaths(Class, Object, BeanStore)` — no hard-coded path strings:
+
+- **`JuneauRestAutoConfiguration`** — an opt-in `@AutoConfiguration` (registered via
+ `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`) gated behind
+ the property `juneau.rest.auto-register=true` (`matchIfMissing=false`). When active, it contributes
+ a single `ServletContextInitializer` that walks every `RestServlet` bean and mounts each at its
+ self-declared path, **skipping** any servlet instance already wired into a manual
+ `ServletRegistrationBean` (explicit registrations win) and any servlet that resolves to no
+ top-level paths.
+- **`JuneauServletRegistrations.forServlet(servlet, beanStore)`** — an always-available factory
+ helper that returns a `ServletRegistrationBean` whose URL mappings are derived from the servlet's
+ own `@Rest(paths)` (or `getPaths()`). Use it from an explicit `@Bean` method when you want the
+ mount path to come from the servlet rather than a hard-coded string. A servlet that resolves to no
+ top-level paths is rejected with an `IllegalArgumentException`.
+
+See [REST Server — Self-Registration](/docs/topics/RestServerSelfRegistration) for the full topic.
+
+### Spring `Environment` bridge — `@Value` reads `application.yaml`
+- Added `org.apache.juneau.rest.springboot.SpringEnvironmentPropertySource` — a `PropertySource`
+ that reads from a Spring `Environment` so `@Value("${spring.app.key}")` injections on
+ Juneau-instantiated beans resolve against the same property sources Spring Boot itself uses
+ (`application.yaml` / `application.properties`, command-line args, env vars, `@PropertySource`,
+ etc.).
+- `SpringBeanStore`'s constructor now auto-registers a lazy bridge against the
+ `ApplicationContext`'s `Environment`, and `SpringBeanStore.clear()` removes it. The bridge is
+ resolved lazily — the constructor doesn't actually call `appContext.getEnvironment()` until the
+ first lookup — so callers that mock the `ApplicationContext` without stubbing `getEnvironment()`
+ (e.g. unit tests that only exercise bean-lookup paths) are unaffected.
+- The bridge sits *above* the classpath-default `juneau.cfg` bridge in the `Settings` source stack
+ (later insertion wins), so `application.yaml` shadows `juneau.cfg` when both define the same
+ key — matching the typical Spring Boot precedence intuition.
+
+## juneau-rest-server-auth-jwt (new module)
+
+### `jwksCacheTtl` migrated to `@Value`
+- `JwtTokenValidator.Builder` now resolves its default `jwksCacheTtl` (5 minutes) through
+ `@Value("${juneau.jwt.jwksCacheTtl:PT5M}") Duration jwksCacheTtl` instead of a hand-rolled
+ initializer. The static factory now routes through `BeanInstantiator`, allowing the value
+ to flow through the standard `Settings` precedence chain (system property → env var →
+ registered `PropertySource` → `Settings.setGlobal`). Behavior is identical when no
+ override is set. See [@Value Framework-Internal Adoption](/docs/topics/ValueFrameworkInternal).
+
+A new opt-in REST module, `juneau-rest-server-auth-jwt`, adds JWT bearer-token verification to `juneau-rest-server` without bleeding the [nimbus-jose-jwt](https://connect2id.com/products/nimbus-jose-jwt) dependency into the core. The nimbus dep is declared in `provided` scope on the module's POM, so consumers explicitly pick the nimbus version they want.
+
+`mvn -pl juneau-rest/juneau-rest-server dependency:tree | grep -i nimbus` returns nothing — the containment requirement is verified at build time. See [REST Server — AuthN Guards § JWT verification](/docs/topics/RestServerAuthGuards) for the full reference.
+
+#### New Classes
+
+- **`org.apache.juneau.rest.server.auth.jwt.JwtTokenValidator`** — `TokenValidator` that fetches keys from a JWKS endpoint, verifies signatures, and enforces `iss` / `aud` / `exp` / `nbf` with secure defaults. Fluent builder: `jwksUrl(URI)` (or `jwkSource(JWKSource)`), `issuer(String)`, `audience(String)`, `algorithms(JWSAlgorithm...)` (default `[RS256, ES256]`), `clockSkew(Duration)` (default 60s, capped at 5m), `jwksCacheTtl(Duration)` (default 5m), `clock(Clock)` (test-time injection).
+- **`org.apache.juneau.rest.server.auth.jwt.JwksCache`** (package-private) — Single-slot TTL cache wrapping any `JWKSource<SecurityContext>`. Continues serving the last-known-good key set when the upstream fetch fails past TTL, logging a `WARNING` so transient JWKS-endpoint outages don't cascade into correlated auth outages.
+
+### JWKS-on-`kid`-miss eager refresh (behavior change)
+
+**`JwksCache` now performs a single eager JWKS refresh when a fresh-cache key selection returns no matching keys.** This eliminates the rotation window where an IdP publishes a new signing key mid-TTL and clients receive spurious 401 responses for perfectly valid tokens (up to the full cache TTL — 5 minutes by default — under the old behavior).
+
+The refresh is bounded by two guards to prevent abuse:
+
+- **Per-cache cooldown** (default 10 seconds, `@Value` key `juneau.jwt.jwksEagerRefreshCooldown`) — at most one eager refresh fires per cooldown window regardless of how many concurrent `kid`-miss requests arrive.
+- **Single in-flight `CompletableFuture`** — concurrent callers experiencing the same miss wait for (and share) the result of the single in-flight fetch; no thundering herd.
+
+On JWKS-endpoint outage during an eager refresh, the last-known-good key set is retained (valid tokens continue to verify) and the cooldown is still advanced so a down endpoint is not hammered.
+
+**This feature is enabled by default** (behavior change: fewer spurious 401s during key rotation). To opt out and restore pre-10.0.0 behavior:
+
+```java fragment
+JwtTokenValidator.create()
+ .jwksEagerRefreshOnKidMiss(false) // or -Djuneau.jwt.jwksEagerRefreshOnKidMiss=false
+ ...
+ .build();
+```
+
+New builder setters and `@Value` keys:
+
+| Builder setter | `@Value` key | Default | Cap / validation |
+|---|---|---|---|
+| `jwksEagerRefreshOnKidMiss(boolean)` | `juneau.jwt.jwksEagerRefreshOnKidMiss` | `true` | — |
+| `jwksEagerRefreshCooldown(Duration)` | `juneau.jwt.jwksEagerRefreshCooldown` | `PT10S` | positive; `≤ jwksCacheTtl`; `≤ PT60S` |
+
+Applies only to `jwksUrl(...)`-backed caches. A caller-supplied `jwkSource(...)` is unaffected.
+
+#### Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-auth-jwt</artifactId>
+ <version>10.0.0</version>
+</dependency>
+<dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ <version>10.3</version> <!-- consumer-supplied; provided scope on juneau-rest-server-auth-jwt -->
+</dependency>
+```
+
+## juneau-rest-server-auth-saml (new module)
+
+A new opt-in REST module, `juneau-rest-server-auth-saml`, adds SAML 2.0 single-sign-on (Web Browser SSO Profile) to `juneau-rest-server` by wrapping [OpenSAML 5.x](https://shibboleth.atlassian.net/wiki/spaces/OS30) behind the earlier change `AuthFilter` / `TokenValidator` SPIs. The `org.opensaml:opensaml-*` dependency cluster is declared in `provided` scope on the module's POM, so consumers explicitly pick the OpenSAML 5.x patch they want (default pin: `5.2.2`).
+
+`mvn -pl juneau-rest/juneau-rest-server dependency:tree | grep -i opensaml` returns nothing — the containment requirement is verified at build time. See [REST Server — SAML AuthN Support](/docs/topics/SamlAuthSupport) for the full reference.
+
+### New Classes
+
+- **`org.apache.juneau.rest.server.auth.saml.SamlAssertionValidator`** — Validates a SAML 2.0 `<samlp:Response>` document and returns a [`ClaimsPrincipal`](/docs/topics/RestServerAuthGuards) marked with `issuerType=SAML`. Builder: `spEntityId(String)`, `expectedIssuer(String)`, `metadataResolver(MetadataResolver)` or `signingCredential(Credential)` (mutually exclusive), `decryptionCredential(Credential)` (opt-in for encrypted assertions), `algorithms(String...)` (default `[rsa-sha2 [...]
+- **`org.apache.juneau.rest.server.auth.saml.SamlAuthFilter`** — `AuthFilter` that decodes the inbound `SAMLResponse` (base64 + DEFLATE for the Redirect binding) at a configurable consumer path and delegates validation. Builder: `validator(SamlAssertionValidator)`, `binding(SamlBinding)` (default `POST`), `consumerPath(String)` (default `/saml/acs`), `rolesClaim(String)` (default `"roles"`), `realm(String)` (default `"saml"`).
+- **`org.apache.juneau.rest.server.auth.saml.SamlBinding`** — Enum of supported HTTP bindings (`POST`, `REDIRECT`). Artifact + SOAP bindings deferred to a future iteration.
+- **`org.apache.juneau.rest.server.auth.saml.SamlMetadataResolvers`** — Convenience factories `file(File|Path)` and `url(String)` that produce initialized `MetadataResolver` instances ready to hand to `SamlAssertionValidator.Builder.metadataResolver(...)`. No bundled default singleton.
+
+#### Marker claim
+
+The returned `ClaimsPrincipal` carries `issuerType=SAML`, so downstream code can distinguish SAML-derived principals from JWT-derived principals without a subclass:
+
+```java
+if ("SAML".equals(principal.getClaim("issuerType", String.class).orElse(null))) {
+ // SAML-issued principal
+}
+```
+
+#### Maven note
+
+OpenSAML 5.x is published to the [Shibboleth Maven repository](https://build.shibboleth.net/maven/releases/) rather than Maven Central. The `juneau-rest-server-auth-saml` POM declares that repository locally so consumers don't need to configure it in their own settings. Pin the version via the `<opensaml.version>` POM property (default `5.2.2`; do not use `5.0.0`).
+
+#### Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-auth-saml</artifactId>
+ <version>10.0.0</version>
+</dependency>
+<dependency>
+ <groupId>org.opensaml</groupId>
+ <artifactId>opensaml-saml-impl</artifactId>
+ <version>5.2.2</version> <!-- consumer-supplied; provided scope on juneau-rest-server-auth-saml -->
+</dependency>
+```
+
+## juneau-rest-server-auth-oauth (new module)
+
+A new opt-in REST module, `juneau-rest-server-auth-oauth`, adds OAuth 2.0 / OIDC bearer-token validation, RFC 7662 token introspection, RFC 6749 client-side grant flows, and OIDC discovery to `juneau-rest-server` by wrapping the [Nimbus OAuth 2.0 SDK](https://connect2id.com/products/nimbus-oauth-openid-connect-sdk) (`com.nimbusds:oauth2-oidc-sdk`) behind the earlier change `AuthFilter` / `TokenValidator` SPIs. Same vendor as the `nimbus-jose-jwt` already used by `juneau-rest-server-auth- [...]
+
+`mvn -pl juneau-rest/juneau-rest-server dependency:tree | grep -iE "(nimbusds|oauth2-oidc)"` returns nothing — the containment requirement is verified at build time. See [REST Server — OAuth AuthN Support](/docs/topics/OAuthAuthSupport) for the full reference.
+
+### New Classes
+
+- **`org.apache.juneau.rest.server.auth.oauth.OAuthIntrospectionValidator`** — `TokenValidator` that validates opaque OAuth 2.0 tokens via RFC 7662 introspection. Wraps Nimbus's `TokenIntrospectionRequest` + `TokenIntrospectionResponse`. Builder: `introspectionEndpoint(URI)`, `clientId(String)`, `clientSecret(String)` / `clientSecretSupplier(Supplier<String>)`, `requiredScopes(String...)`, `tokenCache(TokenCache)` (default `BoundedLruTokenCache`), `cacheTtl(Duration)` (default 5m, [...]
+- **`org.apache.juneau.rest.server.auth.oauth.OAuthFilter`** — `AuthFilter` that authenticates RFC 6750 `Bearer` tokens, delegating to any `TokenValidator` (e.g. `OAuthIntrospectionValidator` for opaque tokens or `JwtTokenValidator` from `juneau-rest-server-auth-jwt` for JWT access tokens). Extracts roles from a configurable claim (default `"scope"`, split on whitespace per RFC 6749 §3.3).
+- **`org.apache.juneau.rest.server.auth.oauth.OAuthToken`** — Immutable record returned by every flow helper on a successful token acquisition.
+- **`org.apache.juneau.rest.server.auth.oauth.TokenCache`** + **`BoundedLruTokenCache`** — SPI for caching principals + tokens; default impl is a thread-safe bounded LRU (1000 entries, per-entry TTL).
+- **`org.apache.juneau.rest.server.auth.oauth.flow.OAuthClientCredentialsFlow`** — Wraps Nimbus's `ClientCredentialsGrant` (RFC 6749 §4.4) with optional `TokenCache` reuse keyed by `(clientId, scope)`.
+- **`org.apache.juneau.rest.server.auth.oauth.flow.OAuthAuthorizationCodeFlow`** — Wraps Nimbus's `AuthorizationRequest` + `AuthorizationCodeGrant` + `TokenRequest` (RFC 6749 §4.1) with mandatory PKCE per RFC 7636 (S256). Exposes `buildAuthorizationUrl(state, codeChallenge)` and `exchange(code, codeVerifier)`.
+- **`org.apache.juneau.rest.server.auth.oauth.flow.OAuthRefreshTokenFlow`** — Wraps Nimbus's `RefreshTokenGrant` (RFC 6749 §6).
+- **`org.apache.juneau.rest.server.auth.oauth.flow.OAuthResourceOwnerFlow`** — Wraps Nimbus's `ResourceOwnerPasswordCredentialsGrant` (RFC 6749 §4.3). **Filed `@Deprecated(since = "10.0.0")` from day-1** — the resource-owner password-credentials grant was removed from OAuth 2.1 due to long-standing security concerns; prefer `OAuthAuthorizationCodeFlow` (with PKCE).
+- **`org.apache.juneau.rest.server.auth.oauth.oidc.OidcDiscoveryClient`** + **`OidcMetadata`** — OIDC `.well-known/openid-configuration` discovery via Nimbus's `OIDCProviderMetadata.resolve(Issuer)`. Returns a Juneau-native immutable record.
+
+#### Deferred
+
+- **OIDC Relying Party login flow** — now shipped as the `juneau-rest-server-auth-oidc-rp` module (see below); the discovery client + auth-code helper here are the building blocks it composes on top of.
+- **Device-code grant** (RFC 8628) — deferred to a follow-on if/when needed.
+- **JWKS-on-`kid`-miss eager refresh** — now shipped; see the `juneau-rest-server-auth-jwt` section below.
+
+#### Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-auth-oauth</artifactId>
+ <version>10.0.0</version>
+</dependency>
+<dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>oauth2-oidc-sdk</artifactId>
+ <version>11.37.2</version> <!-- consumer-supplied; provided scope on juneau-rest-server-auth-oauth -->
+</dependency>
+```
+
+## juneau-rest-server-auth-oidc-rp (new module)
+
+A new opt-in REST module, `juneau-rest-server-auth-oidc-rp`, turns a Juneau REST server into a complete OpenID Connect **Relying Party** — the interactive "Log in with Google / Okta / Entra ID / Keycloak" browser flow — on top of `juneau-rest-server-auth-oauth`. It orchestrates the authorization redirect → callback → session dance (single-use `state`/`nonce`, PKCE S256, strict ID-token validation), wrapping the [Nimbus OAuth 2.0 SDK](https://connect2id.com/products/ [...]
+
+`mvn -pl juneau-rest/juneau-rest-server dependency:tree | grep -iE "(nimbusds|oauth2-oidc)"` returns nothing — the containment requirement is verified at build time. See [REST Server — OIDC Relying Party Login](/docs/topics/OidcRelyingParty) for the full reference.
+
+### New Classes
+
+- **`org.apache.juneau.rest.server.auth.oidc.rp.OidcRelyingParty`** — Builder + facade the application's `@Rest` resource calls: `startLogin` (generate + store `state`/`nonce`/PKCE verifier, 302 → IdP), `completeLogin` (parse callback, single-use-consume `state`, exchange code, validate ID token, create session, 302 → app), `logout` (invalidate session + clear cookie + redirect through the IdP `end_session_endpoint`), `refresh` (rotating-refresh-token grant), and `backChannelLogout [...]
+- **`org.apache.juneau.rest.server.auth.oidc.rp.SessionStore`** + **`OidcSession`** — Pluggable session-persistence SPI and the immutable session record (principal + roles + tokens + `sub`/`sid` + expiry). The SPI exposes `supportsServerSideRevocation()` / `invalidateBySubject(String)` / `invalidateBySessionId(String)` so a server-side-indexed store can satisfy back-channel logout.
+- **`org.apache.juneau.rest.server.auth.oidc.rp.SignedCookieSessionStore`** — **The documented default.** Stateless HMAC-signed (HS256) compact-JWT cookie carrying a minimal claim set (never the raw tokens); scales horizontally and survives restart, size-capped (~4 KB), not server-revocable.
+- **`org.apache.juneau.rest.server.auth.oidc.rp.InMemorySessionStore`** — Single-instance / dev option; bounded-LRU map indexed by `sub` and `sid`, so it is the bundled store that supports back-channel logout.
+- **`org.apache.juneau.rest.server.auth.oidc.rp.EphemeralStore`** — Single-use, TTL-bounded (default 5 min) `state` → `(nonce, codeVerifier, redirectTarget)` store with lazy + LRU sweep (the `BoundedLruTokenCache` eviction shape).
+- **`org.apache.juneau.rest.server.auth.oidc.rp.OidcSessionAuthFilter`** — `AuthFilter` that resolves the session cookie into a `ClaimsPrincipal` on each request; fail-open to unauthenticated. Roles come from a configurable claim (default `"scope"`), matching `OAuthFilter`'s convention.
+- **`org.apache.juneau.rest.server.auth.oidc.rp.IdTokenValidatorAdapter`** — Wraps Nimbus's `IDTokenValidator` for full OIDC ID-token validation (signature vs JWKS, `iss`, `aud`/`azp`, `exp`/`iat`, `nonce`) with a strict algorithm allowlist (default `[RS256, ES256]`; `none` rejected) and configurable JWKS source (URI / `JWKSet` / `JWKSource`).
+
+### Building-block extension (`juneau-rest-server-auth-oauth`)
+
+- **`OAuthAuthorizationCodeFlow.buildAuthenticationUrl(state, codeChallenge, nonce, customizer)`** — new method emitting an OIDC `AuthenticationRequest` (carrying the `nonce`) alongside the existing plain-OAuth `buildAuthorizationUrl(...)`. Required so the relying party can bind a `nonce` into the authorization request and verify it on the returned ID token.
+
+#### Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-auth-oidc-rp</artifactId>
+ <version>10.0.0</version>
+</dependency>
+<dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>oauth2-oidc-sdk</artifactId>
+ <version>11.37.2</version> <!-- consumer-supplied; provided scope on juneau-rest-server-auth-oidc-rp -->
+</dependency>
+```
+
+## juneau-rest-server-metrics-micrometer (new module)
+
+A new opt-in REST module, `juneau-rest-server-metrics-micrometer`, bridges the new `MetricsRecorder` SPI (see [juneau-rest-server](#juneau-rest-server)) into a Micrometer `MeterRegistry` so a Juneau REST service can drop into existing Prometheus / StatsD / JMX scrape pipelines with no hand-rolled instrumentation. Engine-agnostic POM stance: `io.micrometer:micrometer-core` is declared in `provided` scope on the module's POM, so consumers explicitly pick the Micrometer version they want.
+
+`mvn -pl juneau-rest/juneau-rest-server dependency:tree | grep -i micrometer` returns nothing — the containment requirement is verified at build time.
+
+### New Classes
+
+- **`org.apache.juneau.rest.metrics.micrometer.MicrometerMetricsRecorder`** — `MetricsRecorder` implementation. Builds a Micrometer `Timer` named `http.server.requests` (overridable via the two-arg constructor) with four tags per Spring Boot's `WebMvcMetricsFilter` convention: `method` (uppercased HTTP method), `uri` (the `@RestOp` path template — bounded cardinality), `status` (HTTP response status), `exception` (the thrown exception's simple-name, or `None` on the happy path). Records [...]
+
+#### Naming convention
+
+Spring Boot's `http.server.requests` + `{method, uri, status, exception}` was chosen over the OTel-native `http.server.duration` shape because the wider Prometheus / Grafana scrape-config and dashboard ecosystem already standardizes on it. The OTel-native attribute names live in the sibling `juneau-rest-server-tracing-otel` module (see below).
+
+#### Wiring
+
+```java
+@Configuration
+public class ObservabilityConfig {
+ @Bean MeterRegistry registry() { return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); }
+ @Bean MetricsRecorder recorder(MeterRegistry r) { return new MicrometerMetricsRecorder(r); }
+}
+```
+
+#### Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-metrics-micrometer</artifactId>
+ <version>10.0.0</version>
+</dependency>
+<dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-core</artifactId>
+ <version>1.13.6</version> <!-- consumer-supplied; provided scope on juneau-rest-server-metrics-micrometer -->
+</dependency>
+```
+
+See [REST Server — Observability (Micrometer + OpenTelemetry)](/docs/topics/RestServerObservability) for the full topic.
+
+## juneau-rest-server-tracing-otel (new module)
+
+A new opt-in REST module, `juneau-rest-server-tracing-otel`, bridges the new `TracerHook` SPI (see [juneau-rest-server](#juneau-rest-server)) into an OpenTelemetry `Tracer` so each `@RestOp` invocation becomes a server span with HTTP semantic-convention attributes and W3C trace-context propagation. Engine-agnostic POM stance: `io.opentelemetry:opentelemetry-api` is declared in `provided` scope on the module's POM, so consumers explicitly pick the OpenTelemetry version they want.
+
+`mvn -pl juneau-rest/juneau-rest-server dependency:tree | grep -i opentelemetry` returns nothing — the containment requirement is verified at build time.
+
+### New Classes
+
+- **`org.apache.juneau.rest.server.tracing.otel.OtelTracerHook`** — `TracerHook` implementation. Each request becomes a single `SpanKind.SERVER` span named after the HTTP method (`GET`, `POST`, …) per the OpenTelemetry HTTP semantic conventions, with attributes `http.request.method`, `http.response.status_code`, and `http.route` (the `@RestOp` path template, bounded cardinality). On the exception path, the throwable is recorded via `Span.recordException(...)`, the span status is set to ` [...]
+- **`org.apache.juneau.rest.server.tracing.otel.RestRequestTextMapGetter`** — `TextMapGetter<RestRequest>` for OpenTelemetry context extraction. Reads the standard W3C `traceparent` / `tracestate` headers (and any other propagator-defined headers, e.g. baggage) from the incoming request so the server span is created as a child of the caller's trace.
+
+#### Attribute naming convention
+
+OpenTelemetry HTTP semantic conventions (`http.request.method`, `http.response.status_code`, `http.route`) were chosen for the tracer hook to keep the spans interoperable with any OTel collector / backend. The Spring-Boot-style `http.server.requests` metric shape lives in the sibling `juneau-rest-server-metrics-micrometer` module (see above).
+
+#### Wiring
+
+```java
+@Configuration
+public class ObservabilityConfig {
+
+ // Option A: rely on the JVM-wide GlobalOpenTelemetry (recommended).
+ @Bean TracerHook tracer() { return new OtelTracerHook(); }
+
+ // Option B: pass a specific OpenTelemetry instance (e.g. for tests).
+ @Bean TracerHook tracer(OpenTelemetry otel) { return new OtelTracerHook(otel); }
+}
+```
+
+#### W3C trace-context propagation
+
+Incoming `traceparent` / `tracestate` request headers are extracted via the configured `TextMapPropagator` (default: `W3CTraceContextPropagator`) so the server span continues a caller-supplied distributed trace. Downstream HTTP calls made from inside the handler pick up the active span's context automatically when the outbound HTTP client honors the OTel `Context.current()` (the standard OTel client instrumentations do).
+
+On the **outgoing** side, `OtelTracerHook.startSpan(...)` now also renders the W3C `traceparent` / `tracestate` values from the server-started span context at span-start time and stashes them as request attributes (`juneau.traceparent` / `juneau.tracestate`). The new `TraceContextResponseProcessor` in `juneau-rest-server` writes those onto the HTTP response so callers can read the resulting trace id back — see the [juneau-rest-server](#juneau-rest-server) entry above.
+
+#### Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-tracing-otel</artifactId>
+ <version>10.0.0</version>
+</dependency>
+<dependency>
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-api</artifactId>
+ <version>1.62.0</version> <!-- consumer-supplied; provided scope on juneau-rest-server-tracing-otel -->
+</dependency>
+```
+
+See [REST Server — Observability (Micrometer + OpenTelemetry)](/docs/topics/RestServerObservability) for the full topic.
+
+#### Dependency upgrade
+
+- Bumped `io.opentelemetry:opentelemetry-api` from `1.43.0` to `1.62.0`. Closes Dependabot alerts #28 and #29 (CVE-2026-45292 / GHSA-rcgg-9c38-7xpx — "OpenTelemetry Java SDK has Unbounded Memory Allocation in W3C Baggage Propagation"; affected range `<= 1.61.0`). The bridge's API surface (`Tracer` / `Span` / `Context` / `Scope` / `TextMapGetter`) is stable across the 1.x line; no consumer-visible behavior change.
+
+## juneau-rest-server-view-jsp (new module)
+
+A new opt-in REST module, `juneau-rest-server-view-jsp`, adds JSP view-rendering to `juneau-rest-server` without bleeding the JSP-engine dependency (Apache Jasper) into the core. The new `View` interface (see [juneau-rest-server](#juneau-rest-server)) lives in core; this module ships the JSP-specific implementation. Engine-agnostic POM stance: the bridge module declares the JSP API + JSTL impl in `provided` scope only — consumers add the engine matching their container (Jetty 12 EE11's ` [...]
+
+### New Classes
+
+- **`org.apache.juneau.rest.server.view.jsp.JspMixin`** — REST mixin attachable via `@Rest(mixins=JspMixin.class)`. Adds a default `/jsp/*` mount that forwards raw `.jsp` requests to the container's JSP engine, and contributes `JspViewRenderer` to the mixin's response-processor chain. Builder API: `JspMixin.create().basePath("/WEB-INF/views/").build()`.
+- **`org.apache.juneau.rest.server.view.jsp.JspView`** — `View` implementation. Immutable value class; fluent builder: `JspView.of("hello.jsp").attr("name", name).header("Cache-Control", "no-store")`. `attr(...)` rejects `null` values because Servlet-spec `setAttribute(name, null)` deletes the binding rather than setting it.
+- **`org.apache.juneau.rest.server.view.jsp.JspViewRenderer`** — `ResponseProcessor` that detects `JspView`-typed return values and dispatches via `ServletContext.getRequestDispatcher(...).forward(...)`. When no JSP engine is on the classpath, surfaces `NO_ENGINE_DIAGNOSTIC` naming the missing dependency.
+
+#### Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-view-jsp</artifactId>
+ <version>10.0.0</version>
+</dependency>
+<!-- pick ONE of these (engine-agnostic stance): -->
+<dependency>
+ <groupId>org.eclipse.jetty.ee11</groupId>
+ <artifactId>jetty-ee11-apache-jsp</artifactId>
+ <version>12.1.8</version> <!-- Jetty 12 EE11 -->
+</dependency>
+<!-- OR -->
+<dependency>
+ <groupId>org.apache.tomcat.embed</groupId>
+ <artifactId>tomcat-embed-jasper</artifactId>
+ <version>10.1.34</version> <!-- embedded Tomcat / Spring Boot default -->
+</dependency>
+```
+
+#### Composition example
+
+```java
+@Rest(path="/app", mixins=JspMixin.class)
+public class AppResource extends RestServlet {
+
+ @Bean
+ JspMixin jsp() {
+ return JspMixin.create()
+ .basePath("/WEB-INF/views/")
+ .build();
+ }
+
+ @RestGet("/hello/{name}")
+ public View hello(@Path String name) {
+ return JspView.of("hello.jsp").attr("name", name);
+ }
+}
+```
+
+#### Typed-handler View-return integration (`JspView_TypedHandler_Test`)
+
+`@RestGet` methods returning `JspView` now reach `JspViewRenderer` rather than being
+bean-serialized, thanks to the `ResponseProcessorList` partition pass. To use `JspView`
+from a host class without the mixin, add `JspViewRenderer.class` to the host's
+`responseProcessors`:
+
+```java
+@Rest(responseProcessors={JspViewRenderer.class})
+public class AppResource extends BasicRestServlet {
+ @RestGet("/hello/{name}")
+ public View hello(@Path String name) {
+ return JspView.of("hello.jsp").attr("name", name);
+ }
+}
+```
+
+Full JSP rendering requires a real servlet container; the MockRest test verifies that the
+renderer is reached and the response body is not a bean-JSON dump of `JspView`.
+
+See [JSP View Support](/docs/topics/JspViewSupport) for the full topic — engine-selection matrix, Spring Boot fat-jar caveats, and known limitations.
+
+## juneau-rest-server-view-thymeleaf (new module)
+
+A new opt-in REST module, `juneau-rest-server-view-thymeleaf`, adds [Thymeleaf](https://www.thymeleaf.org/) view-rendering to `juneau-rest-server` — sibling to `juneau-rest-server-view-jsp`, but for the engine that Spring Boot autoconfigures by default. The same `View` interface (see [juneau-rest-server](#juneau-rest-server)) shipped with 10.0.0 backs both bridges. Engine-agnostic POM stance: the bridge module declares `org.thymeleaf:thymeleaf` in `provided` scope only — consumers add th [...]
+
+Unlike JSP, Thymeleaf's core engine has zero servlet-container dependencies — it renders directly to a `java.io.Writer`. The raw-template mount under `/thymeleaf/*` works fully under MockRest, Jetty microservices, and Spring Boot uniformly. The bridge picks up a Spring-autoconfigured `SpringTemplateEngine` automatically via `BeanStore.getBean(TemplateEngine.class)`; when no engine bean is registered the bridge constructs a default `TemplateEngine` with a single `ClassLoaderTemplateResolv [...]
+
+### New Classes
+
+- **`org.apache.juneau.rest.server.view.thymeleaf.ThymeleafMixin`** — REST mixin attachable via `@Rest(mixins=ThymeleafMixin.class)`. Adds a default `/thymeleaf/*` mount that renders raw `.html` templates under the configured base path, and contributes `ThymeleafViewRenderer` to the mixin's response-processor chain. Builder API: `ThymeleafMixin.create().basePath("/templates/").cacheTemplates(false).templateMode(TemplateMode.HTML).build()`. Configurable mount path via SVL `${juneau.thymel [...]
+- **`org.apache.juneau.rest.server.view.thymeleaf.ThymeleafView`** — `View` implementation. Immutable value class; fluent builder: `ThymeleafView.of("hello").attr("name", name).header("Cache-Control", "no-store")`. `attr(...)` rejects `null` values because a Thymeleaf `Context` silently drops `null`-valued bindings, which masks caller bugs.
+- **`org.apache.juneau.rest.server.view.thymeleaf.ThymeleafViewRenderer`** — `ResponseProcessor` that detects `ThymeleafView`-typed return values and asks the configured `org.thymeleaf.TemplateEngine` to render them directly onto the response writer. When no Thymeleaf engine is on the classpath, surfaces `NO_ENGINE_DIAGNOSTIC` naming the missing dependency.
+
+#### Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-view-thymeleaf</artifactId>
+ <version>10.0.0</version>
+</dependency>
+<!-- pick ONE of these (engine-agnostic stance): -->
+<dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-thymeleaf</artifactId>
+ <!-- Spring Boot autoconfigured SpringTemplateEngine -->
+</dependency>
+<!-- OR -->
+<dependency>
+ <groupId>org.thymeleaf</groupId>
+ <artifactId>thymeleaf</artifactId>
+ <version>3.1.5.RELEASE</version> <!-- Juneau microservice / Jetty -->
+</dependency>
+```
+
+#### Composition example
+
+```java
+@Rest(path="/app", mixins=ThymeleafMixin.class)
+public class AppResource extends RestServlet {
+
+ @Bean
+ ThymeleafMixin thymeleaf() {
+ return ThymeleafMixin.create()
+ .basePath("/templates/")
+ .build();
+ }
+
+ @RestGet("/hello/{name}")
+ public View hello(@Path String name) {
+ return ThymeleafView.of("hello").attr("name", name);
+ }
+}
+```
+
+#### Typed-handler View-return integration (`ThymeleafView_TypedHandler_Test`)
+
+`@RestGet` methods returning `ThymeleafView` now reach `ThymeleafViewRenderer` rather than
+being bean-serialized. Because Thymeleaf renders directly to a `Writer` with no
+servlet-container dependency, attribute interpolation (`th:text="${name}"` → `"Alice"`) is
+fully assertable under MockRest. Add `ThymeleafViewRenderer.class` to the host's
+`responseProcessors` to use `ThymeleafView` without the mixin.
+
+See [Thymeleaf View Support](/docs/topics/ThymeleafViewSupport) for the full topic — engine-selection matrix, Spring Boot integration notes, and known limitations.
+
+#### Dependency upgrade
+
+- Bumped `org.thymeleaf:thymeleaf` from `3.1.3.RELEASE` to `3.1.5.RELEASE`. Closes Dependabot alerts #24, #25, #26, and #27 — CVE-2026-40477 / GHSA-r4v4-5mwr-2fwr (improper restriction of accessible-object scope), CVE-2026-40478 / GHSA-xjw8-8c5c-9r79 (unauthorized expression syntax), and CVE-2026-41901 / GHSA-c9ph-gxww-7744 (sandboxed-expression syntax-recognition bypass). All four are sandbox-scope vulnerabilities; the Juneau bridge does not enable sandboxed expressions explicitly, but [...]
+
+## juneau-rest-server-view-mustache (new module)
+
+A new opt-in REST module, `juneau-rest-server-view-mustache`, adds [Mustache](https://mustache.github.io/) view-rendering to `juneau-rest-server` — sibling to `juneau-rest-server-view-jsp` and `juneau-rest-server-view-thymeleaf`, but for Mustache's intentionally logic-less template syntax that's portable across JavaScript, Go, Python, and Ruby front ends. The same `View` interface (see [juneau-rest-server](#juneau-rest-server)) shipped with 10.0.0 backs all three bridges. Engine-agnostic [...]
+
+Mustache's core engine has zero servlet-container dependencies — it compiles to bytecode and renders directly to a `java.io.Writer`. The raw-template mount under `/mustache/*` works fully under MockRest, Jetty microservices, and Spring Boot uniformly. The bridge picks up a user-supplied `MustacheFactory` automatically via `BeanStore.getBean(MustacheFactory.class)`; when no factory bean is registered the bridge constructs a default `DefaultMustacheFactory` anchored on the classpath direct [...]
+
+### New Classes
+
+- **`org.apache.juneau.rest.server.view.mustache.MustacheMixin`** — REST mixin attachable via `@Rest(mixins=MustacheMixin.class)`. Adds a default `/mustache/*` mount that renders raw `.mustache` templates under the configured base path, and contributes `MustacheViewRenderer` to the mixin's response-processor chain. Builder API: `MustacheMixin.create().basePath("/templates/").templateSuffix(".mustache").build()`. Configurable mount path via SVL `${juneau.mustache.path:mustache}` (system p [...]
+- **`org.apache.juneau.rest.server.view.mustache.MustacheView`** — `View` implementation. Immutable value class; fluent builder: `MustacheView.of("hello").attr("name", name).header("Cache-Control", "no-store")`. `attr(...)` rejects `null` values to surface caller bugs early.
+- **`org.apache.juneau.rest.server.view.mustache.MustacheViewRenderer`** — `ResponseProcessor` that detects `MustacheView`-typed return values and asks the configured `com.github.mustachejava.MustacheFactory` to compile and execute them directly onto the response writer. When no Mustache engine is on the classpath, surfaces `NO_ENGINE_DIAGNOSTIC` naming the missing dependency.
+
+#### Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-view-mustache</artifactId>
+ <version>10.0.0</version>
+</dependency>
+<!-- engine: -->
+<dependency>
+ <groupId>com.github.spullara.mustache.java</groupId>
+ <artifactId>compiler</artifactId>
+ <version>0.9.14</version>
+</dependency>
+```
+
+#### Composition example
+
+```java
+@Rest(path="/app", mixins=MustacheMixin.class)
+public class AppResource extends RestServlet {
+
+ @Bean
+ MustacheMixin mustache() {
+ return MustacheMixin.create()
+ .basePath("/templates/")
+ .build();
+ }
+
+ @RestGet("/hello/{name}")
+ public View hello(@Path String name) {
+ return MustacheView.of("hello").attr("name", name);
+ }
+}
+```
+
+#### Typed-handler View-return integration (`MustacheView_TypedHandler_Test`)
+
+`@RestGet` methods returning `MustacheView` now reach `MustacheViewRenderer` rather than
+being bean-serialized. mustache.java renders directly to a `Writer` with no
+servlet-container dependency, so `{{name}}` → `"Alice"` is fully assertable under MockRest.
+Add `MustacheViewRenderer.class` to the host's `responseProcessors` to use `MustacheView`
+without the mixin.
+
+See [Mustache View Support](/docs/topics/MustacheViewSupport) for the full topic — engine-selection matrix, Spring Boot integration notes (Spring Boot's official starter ships `jmustache`, not `mustache.java`), and known limitations.
+
+## juneau-rest-server-view-freemarker (new module)
+
+A new opt-in REST module, `juneau-rest-server-view-freemarker`, adds [Apache FreeMarker](https://freemarker.apache.org/) view-rendering to `juneau-rest-server` — sibling to `juneau-rest-server-view-jsp`, `juneau-rest-server-view-thymeleaf`, and `juneau-rest-server-view-mustache`. The same `View` interface (see [juneau-rest-server](#juneau-rest-server)) shipped with 10.0.0 backs all four bridges; with this module, the Phase B sibling lineup is complete. Engine-agnostic POM stance: the bri [...]
+
+FreeMarker is the engine behind countless admin consoles, code generators, email templates, and reporting tools — Apache-family alignment (ASF-maintained engine, same license as Juneau itself), richer expression syntax than logic-less Mustache, and native HTML auto-escaping via the `.ftlh` template flavor. The core engine has zero servlet-container dependencies; it asks `freemarker.template.Configuration` for a `Template` and `template.process(dataModel, writer)` streams the rendered out [...]
+
+### New Classes
+
+- **`org.apache.juneau.rest.server.view.freemarker.FreemarkerMixin`** — REST mixin attachable via `@Rest(mixins=FreemarkerMixin.class)`. Adds a default `/freemarker/*` mount that renders raw `.ftl` / `.ftlh` templates under the configured base path, and contributes `FreemarkerViewRenderer` to the mixin's response-processor chain. Builder API: `FreemarkerMixin.create().basePath("/templates/").templateSuffix(".ftlh").cacheTemplates(true).build()`. Configurable mount path via SVL `${juneau. [...]
+- **`org.apache.juneau.rest.server.view.freemarker.FreemarkerView`** — `View` implementation. Immutable value class; fluent builder: `FreemarkerView.of("hello.ftlh").attr("name", name).header("Cache-Control", "no-store")`. `attr(...)` rejects `null` values to surface caller bugs early.
+- **`org.apache.juneau.rest.server.view.freemarker.FreemarkerViewRenderer`** — `ResponseProcessor` that detects `FreemarkerView`-typed return values and asks the configured `freemarker.template.Configuration` to `getTemplate(name).process(dataModel, writer)` directly onto the response writer. When no FreeMarker engine is on the classpath, surfaces `NO_ENGINE_DIAGNOSTIC` naming the missing dependency (with a pointer to `spring-boot-starter-freemarker` for Spring Boot apps).
+
+#### Dependency
+
+```xml
+<dependency>
+ <groupId>org.apache.juneau</groupId>
+ <artifactId>juneau-rest-server-view-freemarker</artifactId>
+ <version>10.0.0</version>
... 45732 lines suppressed ...