LOG4J2-2993 Support stack trace truncation in JsonTemplateLayout. (#458)
diff --git a/log4j-layout-template-json/revapi.json b/log4j-layout-template-json/revapi.json
index 20bd8ed..77f8cde 100644
--- a/log4j-layout-template-json/revapi.json
+++ b/log4j-layout-template-json/revapi.json
@@ -32,6 +32,408 @@
         "code": "java.class.removed",
         "old": "class org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalFields",
         "justification": "LOG4J2-2961 Refactored for simplicity since it was already broken due to missing @PluginBuilderAttribute annotations"
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.String org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults::getMdcKeyPattern()",
+        "justification": "Removed unused property getter."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.String org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults::getNdcPattern()",
+        "justification": "Removed unused property getter."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::compareTo(E) @ org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.Class<E> java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::getDeclaringClass() @ org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.String java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::name() @ org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::ordinal() @ org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerImplementsInterface",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.Uris",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.Uris",
+        "interface": "java.lang.Comparable<org.apache.logging.log4j.layout.template.json.util.Uris>",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerImplementsInterface",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.Uris",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.Uris",
+        "interface": "java.io.Serializable",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method <T extends java.lang.Enum<T extends java.lang.Enum<T>>> T java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::valueOf(java.lang.Class<T>, java.lang.String) @ org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults::valueOf(java.lang.String)",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults[] org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults::values()",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerInheritsFromClass",
+        "old": "enum org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "new": "class org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.kindChanged",
+        "old": "enum org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "new": "class org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerImplementsInterface",
+        "old": "enum org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "new": "class org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "interface": "java.lang.Comparable<org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults>",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerImplementsInterface",
+        "old": "enum org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "new": "class org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults",
+        "interface": "java.io.Serializable",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::compareTo(E) @ org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.Class<E> java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::getDeclaringClass() @ org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.String java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::name() @ org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::ordinal() @ org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method <T extends java.lang.Enum<T extends java.lang.Enum<T>>> T java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::valueOf(java.lang.Class<T>, java.lang.String) @ org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers::valueOf(java.lang.String)",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers[] org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers::values()",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerInheritsFromClass",
+        "old": "enum org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "new": "class org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.kindChanged",
+        "old": "enum org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "new": "class org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerImplementsInterface",
+        "old": "enum org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "new": "class org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "interface": "java.lang.Comparable<org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers>",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerImplementsInterface",
+        "old": "enum org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "new": "class org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers",
+        "interface": "java.io.Serializable",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::compareTo(E) @ org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.Class<E> java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::getDeclaringClass() @ org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.String java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::name() @ org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::ordinal() @ org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method <T extends java.lang.Enum<T extends java.lang.Enum<T>>> T java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::valueOf(java.lang.Class<T>, java.lang.String) @ org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method org.apache.logging.log4j.layout.template.json.util.RecyclerFactories org.apache.logging.log4j.layout.template.json.util.RecyclerFactories::valueOf(java.lang.String)",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method org.apache.logging.log4j.layout.template.json.util.RecyclerFactories[] org.apache.logging.log4j.layout.template.json.util.RecyclerFactories::values()",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerInheritsFromClass",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.kindChanged",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerImplementsInterface",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "interface": "java.lang.Comparable<org.apache.logging.log4j.layout.template.json.util.RecyclerFactories>",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerImplementsInterface",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.RecyclerFactories",
+        "interface": "java.io.Serializable",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::compareTo(E) @ org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.Class<E> java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::getDeclaringClass() @ org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.String java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::name() @ org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::ordinal() @ org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method <T extends java.lang.Enum<T extends java.lang.Enum<T>>> T java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::valueOf(java.lang.Class<T>, java.lang.String) @ org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values::valueOf(java.lang.String)",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values[] org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values::values()",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerInheritsFromClass",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.kindChanged",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerImplementsInterface",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "interface": "java.lang.Comparable<org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values>",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerImplementsInterface",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.StringParameterParser.Values",
+        "interface": "java.io.Serializable",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::compareTo(E) @ org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.Class<E> java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::getDeclaringClass() @ org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.String java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::name() @ org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::ordinal() @ org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method <T extends java.lang.Enum<T extends java.lang.Enum<T>>> T java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::valueOf(java.lang.Class<T>, java.lang.String) @ org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method org.apache.logging.log4j.layout.template.json.util.StringParameterParser org.apache.logging.log4j.layout.template.json.util.StringParameterParser::valueOf(java.lang.String)",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method org.apache.logging.log4j.layout.template.json.util.StringParameterParser[] org.apache.logging.log4j.layout.template.json.util.StringParameterParser::values()",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerInheritsFromClass",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.kindChanged",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerImplementsInterface",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "interface": "java.lang.Comparable<org.apache.logging.log4j.layout.template.json.util.StringParameterParser>",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerImplementsInterface",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.StringParameterParser",
+        "interface": "java.io.Serializable",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::compareTo(E) @ org.apache.logging.log4j.layout.template.json.util.Uris",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.Class<E> java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::getDeclaringClass() @ org.apache.logging.log4j.layout.template.json.util.Uris",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method java.lang.String java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::name() @ org.apache.logging.log4j.layout.template.json.util.Uris",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::ordinal() @ org.apache.logging.log4j.layout.template.json.util.Uris",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method <T extends java.lang.Enum<T extends java.lang.Enum<T>>> T java.lang.Enum<E extends java.lang.Enum<E extends java.lang.Enum<E>>>::valueOf(java.lang.Class<T>, java.lang.String) @ org.apache.logging.log4j.layout.template.json.util.Uris",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method org.apache.logging.log4j.layout.template.json.util.Uris org.apache.logging.log4j.layout.template.json.util.Uris::valueOf(java.lang.String)",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method org.apache.logging.log4j.layout.template.json.util.Uris[] org.apache.logging.log4j.layout.template.json.util.Uris::values()",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.noLongerInheritsFromClass",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.Uris",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.Uris",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.class.kindChanged",
+        "old": "enum org.apache.logging.log4j.layout.template.json.util.Uris",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.Uris",
+        "justification": "Replaced 'enum' singletons with 'final class'es."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method char[] org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedPrintWriter::getBuffer()",
+        "justification": "LOG4J2-2993 Massaged (internal) API to make method names more Java-like and restrict access if possible."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedPrintWriter::getCapacity()",
+        "justification": "LOG4J2-2993 Massaged (internal) API to make method names more Java-like and restrict access if possible."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method int org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedPrintWriter::getPosition()",
+        "justification": "LOG4J2-2993 Massaged (internal) API to make method names more Java-like and restrict access if possible."
+      },
+      {
+        "code": "java.method.removed",
+        "old": "method boolean org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedPrintWriter::isTruncated()",
+        "justification": "LOG4J2-2993 Massaged (internal) API to make method names more Java-like and restrict access if possible."
+      },
+      {
+        "code": "java.class.visibilityReduced",
+        "old": "class org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedWriter",
+        "new": "class org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedWriter",
+        "justification": "LOG4J2-2993 Massaged (internal) API to make method names more Java-like and restrict access if possible."
       }
     ]
   }
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
index ffec0df..9b8830c 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
@@ -137,8 +137,8 @@
         final String eventTemplate = readEventTemplate(builder);
         final float maxByteCountPerChar = builder.charset.newEncoder().maxBytesPerChar();
         final int maxStringByteCount =
-                Math.toIntExact(Math.round(
-                        maxByteCountPerChar * builder.maxStringLength));
+                Math.toIntExact(Math.round(Math.ceil(
+                        maxByteCountPerChar * builder.maxStringLength)));
         final EventTemplateAdditionalField[] eventTemplateAdditionalFields =
                 builder.eventTemplateAdditionalFields != null
                         ? builder.eventTemplateAdditionalFields
@@ -151,6 +151,7 @@
                 .setJsonWriter(jsonWriter)
                 .setRecyclerFactory(builder.recyclerFactory)
                 .setMaxStringByteCount(maxStringByteCount)
+                .setTruncatedStringSuffix(builder.truncatedStringSuffix)
                 .setLocationInfoEnabled(builder.locationInfoEnabled)
                 .setStackTraceEnabled(builder.stackTraceEnabled)
                 .setStackTraceElementObjectResolver(stackTraceElementObjectResolver)
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java
index 8f7107b..e1d2cb6 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/EventResolverContext.java
@@ -41,6 +41,8 @@
 
     private final int maxStringByteCount;
 
+    private final String truncatedStringSuffix;
+
     private final boolean locationInfoEnabled;
 
     private final boolean stackTraceEnabled;
@@ -58,6 +60,7 @@
         this.jsonWriter = builder.jsonWriter;
         this.recyclerFactory = builder.recyclerFactory;
         this.maxStringByteCount = builder.maxStringByteCount;
+        this.truncatedStringSuffix = builder.truncatedStringSuffix;
         this.locationInfoEnabled = builder.locationInfoEnabled;
         this.stackTraceEnabled = builder.stackTraceEnabled;
         this.stackTraceObjectResolver = stackTraceEnabled
@@ -103,6 +106,10 @@
         return maxStringByteCount;
     }
 
+    String getTruncatedStringSuffix() {
+        return truncatedStringSuffix;
+    }
+
     boolean isLocationInfoEnabled() {
         return locationInfoEnabled;
     }
@@ -141,6 +148,8 @@
 
         private int maxStringByteCount;
 
+        private String truncatedStringSuffix;
+
         private boolean locationInfoEnabled;
 
         private boolean stackTraceEnabled;
@@ -185,6 +194,15 @@
             return this;
         }
 
+        public String getTruncatedStringSuffix() {
+            return truncatedStringSuffix;
+        }
+
+        public Builder setTruncatedStringSuffix(final String truncatedStringSuffix) {
+            this.truncatedStringSuffix = truncatedStringSuffix;
+            return this;
+        }
+
         public Builder setLocationInfoEnabled(final boolean locationInfoEnabled) {
             this.locationInfoEnabled = locationInfoEnabled;
             return this;
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionInternalResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionInternalResolverFactory.java
deleted file mode 100644
index 31b70cf..0000000
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionInternalResolverFactory.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.layout.template.json.resolver;
-
-/**
- * Exception resolver factory.
- *
- * <h3>Configuration</h3>
- *
- * <pre>
- * config      = field , [ stringified ]
- * field       = "field" -> ( "className" | "message" | "stackTrace" )
- * stringified = "stringified" -> boolean
- * </pre>
- */
-abstract class ExceptionInternalResolverFactory {
-
-    private static final EventResolver NULL_RESOLVER =
-            (ignored, jsonGenerator) -> jsonGenerator.writeNull();
-
-    EventResolver createInternalResolver(
-            final EventResolverContext context,
-            final TemplateResolverConfig config) {
-        final String fieldName = config.getString("field");
-        switch (fieldName) {
-            case "className": return createClassNameResolver();
-            case "message": return createMessageResolver(context);
-            case "stackTrace": return createStackTraceResolver(context, config);
-        }
-        throw new IllegalArgumentException("unknown field: " + config);
-
-    }
-
-    abstract EventResolver createClassNameResolver();
-
-    abstract EventResolver createMessageResolver(EventResolverContext context);
-
-    private EventResolver createStackTraceResolver(
-            final EventResolverContext context,
-            final TemplateResolverConfig config) {
-        if (!context.isStackTraceEnabled()) {
-            return NULL_RESOLVER;
-        }
-        final boolean stringified = config.getBoolean("stringified", false);
-        return stringified
-                ? createStackTraceStringResolver(context)
-                : createStackTraceObjectResolver(context);
-    }
-
-    abstract EventResolver createStackTraceStringResolver(EventResolverContext context);
-
-    abstract EventResolver createStackTraceObjectResolver(EventResolverContext context);
-
-}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionResolver.java
index 415104a..4cdcd28 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionResolver.java
@@ -16,76 +16,112 @@
  */
 package org.apache.logging.log4j.layout.template.json.resolver;
 
+import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
+import org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults;
 import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
 
 /**
  * Exception resolver.
- * <p>
- * Note that this resolver is toggled by {@link
- * JsonTemplateLayout.Builder#setStackTraceEnabled(boolean)}.
  *
- * @see ExceptionInternalResolverFactory
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config              = field , [ stringified ] , [ stackTrace ]
+ * field               = "field" -> ( "className" | "message" | "stackTrace" )
+ *
+ * stackTrace          = "stackTrace" -> stringified
+ * stringified         = "stringified" -> ( boolean | truncation )
+ * truncation          = "truncation" -> (
+ *                         [ suffix ]
+ *                       , [ pointMatcherStrings ]
+ *                       , [ pointMatcherRegexes ]
+ *                       )
+ * suffix              = "suffix" -> string
+ * pointMatcherStrings = "pointMatcherStrings" -> string[]
+ * pointMatcherRegexes = "pointMatcherRegexes" -> string[]
+ * </pre>
+ *
+ * <tt>stringified</tt> is set to <tt>false</tt> by default.
+ * <tt>stringified</tt> at the root level is <b>deprecated</b> in favor of
+ * <tt>stackTrace.stringified</tt>, which has precedence if both are provided.
+ * <p>
+ * <tt>pointMatcherStrings</tt> and <tt>pointMatcherRegexes</tt> enable the
+ * truncation of stringified stack traces after the given matching point. If
+ * both parameters are provided, <tt>pointMatcherStrings</tt> will be checked
+ * first.
+ * <p>
+ * If a stringified stack trace truncation takes place, it will be indicated
+ * with <tt>suffix</tt>, which by default is set to the configured
+ * <tt>truncatedStringSuffix</tt> in the layout, unless explicitly provided.
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve <tt>logEvent.getThrown().getClass().getCanonicalName()</tt>:
+ *
+ * <pre>
+ *  {
+ *   "$resolver": "exception",
+ *   "field": "className"
+ * }
+ * </pre>
+ *
+ * Resolve the stack trace into a list of <tt>StackTraceElement</tt> objects:
+ *
+ * <pre>
+ *  {
+ *   "$resolver": "exception",
+ *   "field": "stackTrace"
+ * }
+ * </pre>
+ *
+ * Resolve the stack trace into a string field:
+ *
+ * <pre>
+ *  {
+ *   "$resolver": "exception",
+ *   "field": "stackTrace",
+ *   "stackTrace": {
+ *     "stringified": true
+ *   }
+ * }
+ * </pre>
+ *
+ * Resolve the stack trace into a string field
+ * such that the content will be truncated by the given point matcher:
+ *
+ * <pre>
+ *  {
+ *   "$resolver": "exception",
+ *   "field": "stackTrace",
+ *   "stackTrace": {
+ *     "stringified": {
+ *       "truncation": {
+ *         "suffix": ">",
+ *         "pointMatcherStrings": ["at javax.servlet.http.HttpServlet.service"]
+ *       }
+ *     }
+ *   }
+ * }
+ * </pre>
+ *
+ * @see JsonTemplateLayout.Builder#getTruncatedStringSuffix()
+ * @see JsonTemplateLayoutDefaults#getTruncatedStringSuffix()
+ * @see ExceptionRootCauseResolver
  */
 class ExceptionResolver implements EventResolver {
 
-    private static final ExceptionInternalResolverFactory INTERNAL_RESOLVER_FACTORY =
-            new ExceptionInternalResolverFactory() {
+    private static final Logger LOGGER = StatusLogger.getLogger();
 
-                @Override
-                EventResolver createClassNameResolver() {
-                    return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
-                        final Throwable exception = logEvent.getThrown();
-                        if (exception == null) {
-                            jsonWriter.writeNull();
-                        } else {
-                            String exceptionClassName = exception.getClass().getCanonicalName();
-                            jsonWriter.writeString(exceptionClassName);
-                        }
-                    };
-                }
-
-                @Override
-                EventResolver createMessageResolver(final EventResolverContext context) {
-                    return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
-                        final Throwable exception = logEvent.getThrown();
-                        if (exception == null) {
-                            jsonWriter.writeNull();
-                        } else {
-                            String exceptionMessage = exception.getMessage();
-                            jsonWriter.writeString(exceptionMessage);
-                        }
-                    };
-                }
-
-                @Override
-                EventResolver createStackTraceStringResolver(final EventResolverContext context) {
-                    StackTraceStringResolver stackTraceStringResolver =
-                            new StackTraceStringResolver(context);
-                    return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
-                        final Throwable exception = logEvent.getThrown();
-                        if (exception == null) {
-                            jsonWriter.writeNull();
-                        } else {
-                            stackTraceStringResolver.resolve(exception, jsonWriter);
-                        }
-                    };
-                }
-
-                @Override
-                EventResolver createStackTraceObjectResolver(final EventResolverContext context) {
-                    return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
-                        final Throwable exception = logEvent.getThrown();
-                        if (exception == null) {
-                            jsonWriter.writeNull();
-                        } else {
-                            context.getStackTraceObjectResolver().resolve(exception, jsonWriter);
-                        }
-                    };
-                }
-
-            };
+    private static final EventResolver NULL_RESOLVER =
+            (ignored, jsonGenerator) -> jsonGenerator.writeNull();
 
     private final boolean stackTraceEnabled;
 
@@ -95,8 +131,176 @@
             final EventResolverContext context,
             final TemplateResolverConfig config) {
         this.stackTraceEnabled = context.isStackTraceEnabled();
-        this.internalResolver = INTERNAL_RESOLVER_FACTORY
-                .createInternalResolver(context, config);
+        this.internalResolver = createInternalResolver(context, config);
+    }
+
+    EventResolver createInternalResolver(
+            final EventResolverContext context,
+            final TemplateResolverConfig config) {
+        final String fieldName = config.getString("field");
+        switch (fieldName) {
+            case "className": return createClassNameResolver();
+            case "message": return createMessageResolver();
+            case "stackTrace": return createStackTraceResolver(context, config);
+        }
+        throw new IllegalArgumentException("unknown field: " + config);
+
+    }
+
+    private EventResolver createClassNameResolver() {
+        return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+            final Throwable exception = extractThrowable(logEvent);
+            if (exception == null) {
+                jsonWriter.writeNull();
+            } else {
+                String exceptionClassName = exception.getClass().getCanonicalName();
+                jsonWriter.writeString(exceptionClassName);
+            }
+        };
+    }
+
+    private EventResolver createMessageResolver() {
+        return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+            final Throwable exception = extractThrowable(logEvent);
+            if (exception == null) {
+                jsonWriter.writeNull();
+            } else {
+                String exceptionMessage = exception.getMessage();
+                jsonWriter.writeString(exceptionMessage);
+            }
+        };
+    }
+
+    private EventResolver createStackTraceResolver(
+            final EventResolverContext context,
+            final TemplateResolverConfig config) {
+        if (!context.isStackTraceEnabled()) {
+            return NULL_RESOLVER;
+        }
+        final boolean stringified = isStackTraceStringified(config);
+        return stringified
+                ? createStackTraceStringResolver(context, config)
+                : createStackTraceObjectResolver(context);
+    }
+
+    private static boolean isStackTraceStringified(
+            final TemplateResolverConfig config) {
+        final Boolean stringifiedOld = config.getBoolean("stringified");
+        if (stringifiedOld != null) {
+            LOGGER.warn(
+                    "\"stringified\" flag at the root level of an exception " +
+                            "[root cause] resolver is deprecated in favor of " +
+                            "\"stackTrace.stringified\"");
+        }
+        final Object stringifiedNew =
+                config.getObject(new String[]{"stackTrace", "stringified"});
+        if (stringifiedOld == null && stringifiedNew == null) {
+            return false;
+        } else if (stringifiedNew == null) {
+            return stringifiedOld;
+        } else {
+            return !(stringifiedNew instanceof Boolean) || (boolean) stringifiedNew;
+        }
+    }
+
+    private EventResolver createStackTraceStringResolver(
+            final EventResolverContext context,
+            final TemplateResolverConfig config) {
+
+        // Read the configuration.
+        final String truncationSuffix =
+                readTruncationSuffix(context, config);
+        final List<String> truncationPointMatcherStrings =
+                readTruncationPointMatcherStrings(config);
+        final List<String> truncationPointMatcherRegexes =
+                readTruncationPointMatcherRegexes(config);
+
+        // Create the resolver.
+        final StackTraceStringResolver resolver =
+                new StackTraceStringResolver(
+                        context,
+                        truncationSuffix,
+                        truncationPointMatcherStrings,
+                        truncationPointMatcherRegexes);
+
+        // Create the null-protected resolver.
+        return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+            final Throwable exception = extractThrowable(logEvent);
+            if (exception == null) {
+                jsonWriter.writeNull();
+            } else {
+                resolver.resolve(exception, jsonWriter);
+            }
+        };
+
+    }
+
+    private static String readTruncationSuffix(
+            final EventResolverContext context,
+            final TemplateResolverConfig config) {
+        final String suffix = config.getString(
+                new String[]{"stackTrace", "stringified", "truncation", "suffix"});
+        return suffix != null
+                ? suffix
+                : context.getTruncatedStringSuffix();
+    }
+
+    private static List<String> readTruncationPointMatcherStrings(
+            final TemplateResolverConfig config) {
+        List<String> strings = config.getList(
+                new String[]{"stackTrace", "stringified", "truncation", "pointMatcherStrings"},
+                String.class);
+        if (strings == null) {
+            strings = Collections.emptyList();
+        }
+        return strings;
+    }
+
+    private static List<String> readTruncationPointMatcherRegexes(
+            final TemplateResolverConfig config) {
+
+        // Extract the regexes.
+        List<String> regexes = config.getList(
+                new String[]{"stackTrace", "stringified", "truncation", "pointMatcherRegexes"},
+                String.class);
+        if (regexes == null) {
+            regexes = Collections.emptyList();
+        }
+
+        // Check the regex syntax.
+        for (int i = 0; i < regexes.size(); i++) {
+            final String regex = regexes.get(i);
+            try {
+                Pattern.compile(regex);
+            } catch (final PatternSyntaxException error) {
+                final String message = String.format(
+                        "invalid truncation point matcher regex at index %d: %s",
+                        i, regex);
+                throw new IllegalArgumentException(message, error);
+            }
+        }
+
+        // Return the extracted regexes.
+        return regexes;
+
+    }
+
+    private EventResolver createStackTraceObjectResolver(
+            final EventResolverContext context) {
+        return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+            final Throwable exception = extractThrowable(logEvent);
+            if (exception == null) {
+                jsonWriter.writeNull();
+            } else {
+                context
+                        .getStackTraceObjectResolver()
+                        .resolve(exception, jsonWriter);
+            }
+        };
+    }
+
+    Throwable extractThrowable(final LogEvent logEvent) {
+        return logEvent.getThrown();
     }
 
     static String getName() {
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolver.java
index 5218284..37119ca 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ExceptionRootCauseResolver.java
@@ -19,7 +19,6 @@
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.util.Throwables;
 import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
-import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
 
 /**
  * Exception root cause resolver.
@@ -27,81 +26,14 @@
  * Note that this resolver is toggled by {@link
  * JsonTemplateLayout.Builder#setStackTraceEnabled(boolean)}.
  *
- * @see ExceptionInternalResolverFactory
+ * @see ExceptionResolver
  */
-final class ExceptionRootCauseResolver implements EventResolver {
-
-    private static final ExceptionInternalResolverFactory INTERNAL_RESOLVER_FACTORY =
-            new ExceptionInternalResolverFactory() {
-
-                @Override
-                EventResolver createClassNameResolver() {
-                    return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
-                        final Throwable exception = logEvent.getThrown();
-                        if (exception == null) {
-                            jsonWriter.writeNull();
-                        } else {
-                            final Throwable rootCause = Throwables.getRootCause(exception);
-                            final String rootCauseClassName = rootCause.getClass().getCanonicalName();
-                            jsonWriter.writeString(rootCauseClassName);
-                        }
-                    };
-                }
-
-                @Override
-                EventResolver createMessageResolver(final EventResolverContext context) {
-                    return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
-                        final Throwable exception = logEvent.getThrown();
-                        if (exception == null) {
-                            jsonWriter.writeNull();
-                        } else {
-                            final Throwable rootCause = Throwables.getRootCause(exception);
-                            final String rootCauseMessage = rootCause.getMessage();
-                            jsonWriter.writeString(rootCauseMessage);
-                        }
-                    };
-                }
-
-                @Override
-                EventResolver createStackTraceStringResolver(final EventResolverContext context) {
-                    final StackTraceStringResolver stackTraceStringResolver =
-                            new StackTraceStringResolver(context);
-                    return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
-                        final Throwable exception = logEvent.getThrown();
-                        if (exception == null) {
-                            jsonWriter.writeNull();
-                        } else {
-                            final Throwable rootCause = Throwables.getRootCause(exception);
-                            stackTraceStringResolver.resolve(rootCause, jsonWriter);
-                        }
-                    };
-                }
-
-                @Override
-                EventResolver createStackTraceObjectResolver(EventResolverContext context) {
-                    return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
-                        final Throwable exception = logEvent.getThrown();
-                        if (exception == null) {
-                            jsonWriter.writeNull();
-                        } else {
-                            final Throwable rootCause = Throwables.getRootCause(exception);
-                            context.getStackTraceObjectResolver().resolve(rootCause, jsonWriter);
-                        }
-                    };
-                }
-
-            };
-
-    private final boolean stackTraceEnabled;
-
-    private final EventResolver internalResolver;
+final class ExceptionRootCauseResolver extends ExceptionResolver {
 
     ExceptionRootCauseResolver(
             final EventResolverContext context,
             final TemplateResolverConfig config) {
-        this.stackTraceEnabled = context.isStackTraceEnabled();
-        this.internalResolver = INTERNAL_RESOLVER_FACTORY
-                .createInternalResolver(context, config);
+        super(context, config);
     }
 
     static String getName() {
@@ -109,20 +41,9 @@
     }
 
     @Override
-    public boolean isResolvable() {
-        return stackTraceEnabled;
-    }
-
-    @Override
-    public boolean isResolvable(final LogEvent logEvent) {
-        return stackTraceEnabled && logEvent.getThrown() != null;
-    }
-
-    @Override
-    public void resolve(
-            final LogEvent logEvent,
-            final JsonWriter jsonWriter) {
-        internalResolver.resolve(logEvent, jsonWriter);
+    Throwable extractThrowable(final LogEvent logEvent) {
+        final Throwable thrown = logEvent.getThrown();
+        return thrown != null ? Throwables.getRootCause(thrown) : null;
     }
 
 }
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
index 725ac1e..61be39e 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
@@ -20,19 +20,52 @@
 import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
 import org.apache.logging.log4j.layout.template.json.util.Recycler;
 
+import java.util.List;
 import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 final class StackTraceStringResolver implements StackTraceResolver {
 
     private final Recycler<TruncatingBufferedPrintWriter> writerRecycler;
 
-    StackTraceStringResolver(final EventResolverContext context) {
+    private final boolean truncationEnabled;
+
+    private final String truncationSuffix;
+
+    private final List<String> truncationPointMatcherStrings;
+
+    private final List<Pattern> groupedTruncationPointMatcherRegexes;
+
+    StackTraceStringResolver(
+            final EventResolverContext context,
+            final String truncationSuffix,
+            final List<String> truncationPointMatcherStrings,
+            final List<String> truncationPointMatcherRegexes) {
         final Supplier<TruncatingBufferedPrintWriter> writerSupplier =
                 () -> TruncatingBufferedPrintWriter.ofCapacity(
                         context.getMaxStringByteCount());
         this.writerRecycler = context
                 .getRecyclerFactory()
                 .create(writerSupplier, TruncatingBufferedPrintWriter::close);
+        this.truncationEnabled =
+                !truncationPointMatcherStrings.isEmpty() ||
+                        !truncationPointMatcherRegexes.isEmpty();
+        this.truncationSuffix = truncationSuffix;
+        this.truncationPointMatcherStrings = truncationPointMatcherStrings;
+        this.groupedTruncationPointMatcherRegexes =
+                groupTruncationPointMatcherRegexes(truncationPointMatcherRegexes);
+    }
+
+    private static List<Pattern> groupTruncationPointMatcherRegexes(
+            final List<String> regexes) {
+        return regexes
+                .stream()
+                .map(regex -> Pattern.compile(
+                        "^.*(" + regex + ")(.*)$",
+                        Pattern.MULTILINE | Pattern.DOTALL))
+                .collect(Collectors.toList());
     }
 
     @Override
@@ -42,10 +75,53 @@
         final TruncatingBufferedPrintWriter writer = writerRecycler.acquire();
         try {
             throwable.printStackTrace(writer);
-            jsonWriter.writeString(writer.getBuffer(), 0, writer.getPosition());
+            truncate(writer);
+            jsonWriter.writeString(writer.buffer(), 0, writer.position());
         } finally {
             writerRecycler.release(writer);
         }
     }
 
+    private void truncate(final TruncatingBufferedPrintWriter writer) {
+
+        // Short-circuit if truncation is not enabled.
+        if (!truncationEnabled) {
+            return;
+        }
+
+        // Check for string matches.
+        // noinspection ForLoopReplaceableByForEach (avoid iterator allocation)
+        for (int i = 0; i < truncationPointMatcherStrings.size(); i++) {
+            final String matcher = truncationPointMatcherStrings.get(i);
+            final int matchIndex = writer.indexOf(matcher);
+            if (matchIndex > 0) {
+                final int truncationPointIndex = matchIndex + matcher.length();
+                truncate(writer, truncationPointIndex);
+                return;
+            }
+        }
+
+        // Check for regex matches.
+        // noinspection ForLoopReplaceableByForEach (avoid iterator allocation)
+        for (int i = 0; i < groupedTruncationPointMatcherRegexes.size(); i++) {
+            final Pattern pattern = groupedTruncationPointMatcherRegexes.get(i);
+            final Matcher matcher = pattern.matcher(writer);
+            final boolean matched = matcher.matches();
+            if (matched) {
+                final int lastGroup = matcher.groupCount();
+                final int truncationPointIndex = matcher.start(lastGroup);
+                truncate(writer, truncationPointIndex);
+                return;
+            }
+        }
+
+    }
+
+    private void truncate(
+            final TruncatingBufferedPrintWriter writer,
+            final int index) {
+        writer.position(index);
+        writer.print(truncationSuffix);
+    }
+
 }
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/MapAccessor.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/MapAccessor.java
index cc3ef76..2f97070 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/MapAccessor.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/MapAccessor.java
@@ -17,6 +17,7 @@
 package org.apache.logging.log4j.layout.template.json.util;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
@@ -71,10 +72,58 @@
     }
 
     public boolean exists(final String[] path) {
-        final Object value = getObject(path, Object.class);
+        final Object value = getObject(path);
         return value != null;
     }
 
+    public <E> List<E> getList(final String key, final Class<E> clazz) {
+        final String[] path = {key};
+        return getList(path, clazz);
+    }
+
+    public <E> List<E> getList(final String[] path, final Class<E> clazz) {
+
+        // Access the object.
+        final Object value = getObject(path);
+        if (value == null) {
+            return null;
+        }
+
+        // Check the type.
+        if (!(value instanceof List)) {
+            final String message = String.format(
+                    "was expecting a List<%s> at path %s: %s (of type %s)",
+                    clazz,
+                    Arrays.asList(path),
+                    value,
+                    value.getClass().getCanonicalName());
+            throw new IllegalArgumentException(message);
+        }
+
+        // Check the element types.
+        @SuppressWarnings("unchecked")
+        final List<Object> items = (List<Object>) value;
+        for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
+            final Object item = items.get(itemIndex);
+            if (!clazz.isInstance(item)) {
+                final String message = String.format(
+                        "was expecting a List<%s> item at path %s and index %d: %s (of type %s)",
+                        clazz,
+                        Arrays.asList(path),
+                        itemIndex,
+                        item,
+                        item != null ? item.getClass().getCanonicalName() : null);
+                throw new IllegalArgumentException(message);
+            }
+        }
+
+        // Return the typed list.
+        @SuppressWarnings("unchecked")
+        final List<E> typedItems = (List<E>) items;
+        return typedItems;
+
+    }
+
     public Object getObject(final String key) {
         final String[] path = {key};
         return getObject(path, Object.class);
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/TruncatingBufferedPrintWriter.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/TruncatingBufferedPrintWriter.java
index 8d7cb1e..7e9aa3c 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/TruncatingBufferedPrintWriter.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/TruncatingBufferedPrintWriter.java
@@ -17,8 +17,11 @@
 package org.apache.logging.log4j.layout.template.json.util;
 
 import java.io.PrintWriter;
+import java.util.Objects;
 
-public final class TruncatingBufferedPrintWriter extends PrintWriter {
+public final class TruncatingBufferedPrintWriter
+        extends PrintWriter
+        implements CharSequence {
 
     private final TruncatingBufferedWriter writer;
 
@@ -36,20 +39,44 @@
         return new TruncatingBufferedPrintWriter(writer);
     }
 
-    public char[] getBuffer() {
-        return writer.getBuffer();
+    public char[] buffer() {
+        return writer.buffer();
     }
 
-    public int getPosition() {
-        return writer.getPosition();
+    public int position() {
+        return writer.position();
     }
 
-    public int getCapacity() {
-        return writer.getCapacity();
+    public void position(final int index) {
+        writer.position(index);
     }
 
-    public boolean isTruncated() {
-        return writer.isTruncated();
+    public int capacity() {
+        return writer.capacity();
+    }
+
+    public boolean truncated() {
+        return writer.truncated();
+    }
+
+    public int indexOf(final CharSequence seq) {
+        Objects.requireNonNull(seq, "seq");
+        return writer.indexOf(seq);
+    }
+
+    @Override
+    public int length() {
+        return writer.length();
+    }
+
+    @Override
+    public char charAt(final int index) {
+        return writer.charAt(index);
+    }
+
+    @Override
+    public CharSequence subSequence(final int startIndex, final int endIndex) {
+        return writer.subSequence(startIndex, endIndex);
     }
 
     @Override
@@ -57,4 +84,9 @@
         writer.close();
     }
 
+    @Override
+    public String toString() {
+        return writer.toString();
+    }
+
 }
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/TruncatingBufferedWriter.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/TruncatingBufferedWriter.java
index ea50f77..1b88f12 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/TruncatingBufferedWriter.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/TruncatingBufferedWriter.java
@@ -19,7 +19,7 @@
 import java.io.Writer;
 import java.util.Objects;
 
-public final class TruncatingBufferedWriter extends Writer {
+final class TruncatingBufferedWriter extends Writer implements CharSequence {
 
     private final char[] buffer;
 
@@ -33,19 +33,26 @@
         this.truncated = false;
     }
 
-    char[] getBuffer() {
+    char[] buffer() {
         return buffer;
     }
 
-    int getPosition() {
+    int position() {
         return position;
     }
 
-    int getCapacity() {
+    void position(final int index) {
+        if (index < 0 || index >= buffer.length) {
+            throw new IllegalArgumentException("invalid index: " + index);
+        }
+        position = index;
+    }
+
+    int capacity() {
         return buffer.length;
     }
 
-    boolean isTruncated() {
+    boolean truncated() {
         return truncated;
     }
 
@@ -196,6 +203,53 @@
 
     }
 
+    int indexOf(final CharSequence seq) {
+
+        // Short-circuit if there is nothing to match.
+        final int seqLength = seq.length();
+        if (seqLength == 0) {
+            return 0;
+        }
+
+        // Short-circuit if the given input is longer than the buffer.
+        if (seqLength > position) {
+            return -1;
+        }
+
+        // Perform the search.
+        for (int bufferIndex = 0; bufferIndex < position; bufferIndex++) {
+            boolean found = true;
+            for (int seqIndex = 0; seqIndex < seqLength; seqIndex++) {
+                final char s = seq.charAt(seqIndex);
+                final char b = buffer[bufferIndex + seqIndex];
+                if (s != b) {
+                    found = false;
+                    break;
+                }
+            }
+            if (found) {
+                return bufferIndex;
+            }
+        }
+        return -1;
+
+    }
+
+    @Override
+    public int length() {
+        return position + 1;
+    }
+
+    @Override
+    public char charAt(final int index) {
+        return buffer[index];
+    }
+
+    @Override
+    public String subSequence(final int startIndex, final int endIndex) {
+        return new String(buffer, startIndex, endIndex - startIndex);
+    }
+
     @Override
     public void flush() {}
 
@@ -205,4 +259,9 @@
         truncated = false;
     }
 
+    @Override
+    public String toString() {
+        return new String(buffer, 0, position);
+    }
+
 }
diff --git a/log4j-layout-template-json/src/main/resources/EcsLayout.json b/log4j-layout-template-json/src/main/resources/EcsLayout.json
index dee7a84..708b27b 100644
--- a/log4j-layout-template-json/src/main/resources/EcsLayout.json
+++ b/log4j-layout-template-json/src/main/resources/EcsLayout.json
@@ -41,6 +41,8 @@
   "error.stack_trace": {
     "$resolver": "exception",
     "field": "stackTrace",
-    "stringified": true
+    "stackTrace": {
+      "stringified": true
+    }
   }
 }
diff --git a/log4j-layout-template-json/src/main/resources/GelfLayout.json b/log4j-layout-template-json/src/main/resources/GelfLayout.json
index dd43cc8..4281bba 100644
--- a/log4j-layout-template-json/src/main/resources/GelfLayout.json
+++ b/log4j-layout-template-json/src/main/resources/GelfLayout.json
@@ -8,7 +8,9 @@
   "full_message": {
     "$resolver": "exception",
     "field": "stackTrace",
-    "stringified": true
+    "stackTrace": {
+      "stringified": true
+    }
   },
   "timestamp": {
     "$resolver": "timestamp",
diff --git a/log4j-layout-template-json/src/main/resources/LogstashJsonEventLayoutV1.json b/log4j-layout-template-json/src/main/resources/LogstashJsonEventLayoutV1.json
index 3225930..809f705 100644
--- a/log4j-layout-template-json/src/main/resources/LogstashJsonEventLayoutV1.json
+++ b/log4j-layout-template-json/src/main/resources/LogstashJsonEventLayoutV1.json
@@ -15,7 +15,9 @@
     "stacktrace": {
       "$resolver": "exception",
       "field": "stackTrace",
-      "stringified": true
+      "stackTrace": {
+        "stringified": true
+      }
     }
   },
   "line_number": {
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java
index 057bff7..68152ea 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java
@@ -473,7 +473,8 @@
                 "ex_stacktrace", asMap(
                         "$resolver", "exception",
                         "field", "stackTrace",
-                        "stringified", true),
+                        "stackTrace", asMap(
+                                "stringified", true)),
                 "root_ex_class", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "className"),
@@ -483,7 +484,8 @@
                 "root_ex_stacktrace", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "stackTrace",
-                        "stringified", true)));
+                        "stackTrace", asMap(
+                                "stringified", true))));
 
         // Create the layout.
         final JsonTemplateLayout layout = JsonTemplateLayout
@@ -547,7 +549,8 @@
                 "root_ex_stacktrace", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "stackTrace",
-                        "stringified", true)));
+                        "stackTrace", asMap(
+                                "stringified", true))));
 
         // Create the layout.
         final JsonTemplateLayout layout = JsonTemplateLayout
@@ -1604,14 +1607,16 @@
                 "exStackTraceString", asMap(
                         "$resolver", "exception",
                         "field", "stackTrace",
-                        "stringified", true),
+                        "stackTrace", asMap(
+                                "stringified", true)),
                 "exRootCauseStackTrace", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "stackTrace"),
                 "exRootCauseStackTraceString", asMap(
                         "$resolver", "exceptionRootCause",
                         "field", "stackTrace",
-                        "stringified", true),
+                        "stackTrace", asMap(
+                                "stringified", true)),
                 "requiredFieldTriggeringError", true));
 
         // Create the layout.
@@ -1634,7 +1639,7 @@
     }
 
     @Test
-    void test_StackTraceTextResolver_with_maxStringLength() {
+    void test_stringified_exception_resolver_with_maxStringLength() {
 
         // Create the event template.
         final String eventTemplate = writeJson(asMap(
@@ -1672,6 +1677,123 @@
     }
 
     @Test
+    void test_stack_trace_truncation() {
+
+        // Create the exception to be logged.
+        final Exception childError =
+                new Exception("unique child exception message");
+        final Exception parentError =
+                new Exception("unique parent exception message", childError);
+
+        // Create the event template.
+        final String truncationSuffix = "~";
+        final String eventTemplate = writeJson(asMap(
+                // Raw exception.
+                "ex", asMap(
+                        "$resolver", "exception",
+                        "field", "stackTrace",
+                        "stackTrace", asMap(
+                                "stringified", true)),
+                // Exception matcher using strings.
+                "stringMatchedEx", asMap(
+                        "$resolver", "exception",
+                        "field", "stackTrace",
+                        "stackTrace", asMap(
+                                "stringified", asMap(
+                                        "truncation", asMap(
+                                                "suffix", truncationSuffix,
+                                                "pointMatcherStrings", Arrays.asList(
+                                                        "this string shouldn't match with anything",
+                                                        parentError.getMessage()))))),
+                // Exception matcher using regexes.
+                "regexMatchedEx", asMap(
+                        "$resolver", "exception",
+                        "field", "stackTrace",
+                        "stackTrace", asMap(
+                                "stringified", asMap(
+                                        "truncation", asMap(
+                                                "suffix", truncationSuffix,
+                                                "pointMatcherRegexes", Arrays.asList(
+                                                        "this string shouldn't match with anything",
+                                                        parentError
+                                                                .getMessage()
+                                                                .replace("unique", "[xu]n.que")))))),
+                // Raw exception root cause.
+                "rootEx", asMap(
+                        "$resolver", "exceptionRootCause",
+                        "field", "stackTrace",
+                        "stackTrace", asMap(
+                                "stringified", true)),
+                // Exception root cause matcher using strings.
+                "stringMatchedRootEx", asMap(
+                        "$resolver", "exceptionRootCause",
+                        "field", "stackTrace",
+                        "stackTrace", asMap(
+                                "stringified", asMap(
+                                        "truncation", asMap(
+                                                "suffix", truncationSuffix,
+                                                "pointMatcherStrings", Arrays.asList(
+                                                        "this string shouldn't match with anything",
+                                                        childError.getMessage()))))),
+                // Exception root cause matcher using regexes.
+                "regexMatchedRootEx", asMap(
+                        "$resolver", "exceptionRootCause",
+                        "field", "stackTrace",
+                        "stackTrace", asMap(
+                                "stringified", asMap(
+                                        "truncation", asMap(
+                                                "suffix", truncationSuffix,
+                                                "pointMatcherRegexes", Arrays.asList(
+                                                        "this string shouldn't match with anything",
+                                                        childError
+                                                                .getMessage()
+                                                                .replace("unique", "[xu]n.que"))))))));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setEventTemplate(eventTemplate)
+                .setStackTraceEnabled(true)
+                .build();
+
+        // Create the log event.
+        final LogEvent logEvent = Log4jLogEvent
+                .newBuilder()
+                .setLoggerName(LOGGER_NAME)
+                .setThrown(parentError)
+                .build();
+
+        // Check the serialized event.
+        final String expectedMatchedExEnd =
+                parentError.getMessage() + truncationSuffix;
+        final String expectedMatchedRootExEnd =
+                childError.getMessage() + truncationSuffix;
+        usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+
+            // Check the serialized exception.
+            assertThat(accessor.getString("ex"))
+                    .doesNotEndWith(expectedMatchedExEnd)
+                    .doesNotEndWith(expectedMatchedRootExEnd);
+            assertThat(accessor.getString("stringMatchedEx"))
+                    .endsWith(expectedMatchedExEnd);
+            assertThat(accessor.getString("regexMatchedEx"))
+                    .endsWith(expectedMatchedExEnd);
+
+            // Check the serialized exception root cause.
+            assertThat(accessor.getString("rootEx"))
+                    .doesNotEndWith(expectedMatchedExEnd)
+                    .doesNotEndWith(expectedMatchedRootExEnd);
+            assertThat(accessor.getString("stringMatchedRootEx"))
+                    .endsWith(expectedMatchedRootExEnd);
+            assertThat(accessor.getString("regexMatchedRootEx"))
+                    .endsWith(expectedMatchedRootExEnd);
+
+        });
+
+    }
+
+    @Test
     void test_null_eventDelimiter() {
 
         // Create the event template.
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/TruncatingBufferedWriterTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/TruncatingBufferedWriterTest.java
index a8b210c..b52d453 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/TruncatingBufferedWriterTest.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/TruncatingBufferedWriterTest.java
@@ -75,10 +75,10 @@
         expectedBuffer[expectedPosition++] = 'u';
         expectedBuffer[expectedPosition++] = 'l';
         expectedBuffer[expectedPosition++] = 'l';
-        Assertions.assertThat(writer.getBuffer()).isEqualTo(expectedBuffer);
-        Assertions.assertThat(writer.getPosition()).isEqualTo(expectedPosition);
-        Assertions.assertThat(writer.getCapacity()).isEqualTo(capacity);
-        Assertions.assertThat(writer.isTruncated()).isFalse();
+        Assertions.assertThat(writer.buffer()).isEqualTo(expectedBuffer);
+        Assertions.assertThat(writer.position()).isEqualTo(expectedPosition);
+        Assertions.assertThat(writer.capacity()).isEqualTo(capacity);
+        Assertions.assertThat(writer.truncated()).isFalse();
         verifyClose(writer);
 
     }
@@ -228,17 +228,17 @@
     private void verifyTruncation(
             final TruncatingBufferedWriter writer,
             final char c) {
-        Assertions.assertThat(writer.getBuffer()).isEqualTo(new char[]{c});
-        Assertions.assertThat(writer.getPosition()).isEqualTo(1);
-        Assertions.assertThat(writer.getCapacity()).isEqualTo(1);
-        Assertions.assertThat(writer.isTruncated()).isTrue();
+        Assertions.assertThat(writer.buffer()).isEqualTo(new char[]{c});
+        Assertions.assertThat(writer.position()).isEqualTo(1);
+        Assertions.assertThat(writer.capacity()).isEqualTo(1);
+        Assertions.assertThat(writer.truncated()).isTrue();
         verifyClose(writer);
     }
 
     private void verifyClose(final TruncatingBufferedWriter writer) {
         writer.close();
-        Assertions.assertThat(writer.getPosition()).isEqualTo(0);
-        Assertions.assertThat(writer.isTruncated()).isFalse();
+        Assertions.assertThat(writer.position()).isEqualTo(0);
+        Assertions.assertThat(writer.truncated()).isFalse();
     }
 
 }
diff --git a/log4j-layout-template-json/src/test/resources/testJsonTemplateLayout.json b/log4j-layout-template-json/src/test/resources/testJsonTemplateLayout.json
index daf455e..e8e1063 100644
--- a/log4j-layout-template-json/src/test/resources/testJsonTemplateLayout.json
+++ b/log4j-layout-template-json/src/test/resources/testJsonTemplateLayout.json
@@ -10,7 +10,9 @@
   "stacktrace": {
     "$resolver": "exception",
     "field": "stackTrace",
-    "stringified": true
+    "stackTrace": {
+      "stringified": true
+    }
   },
   "line_number": {
     "$resolver": "source",
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index b78a7c3..ad0b9d5 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -31,6 +31,9 @@
          - "remove" - Removed
     -->
     <release version="3.0.0" date="2021-MM-DD" description="GA Release 3.0.0">
+      <action issue="LOG4J2-2993" dev="vy" type="add">
+        Support stack trace truncation in JsonTemplateLayout.
+      </action>
       <action issue="LOG4J2-2998" dev="vy" type="fix">
         Fix truncation of excessive strings ending with a high surrogate in JsonWriter.
       </action>
diff --git a/src/site/asciidoc/manual/json-template-layout.adoc.vm b/src/site/asciidoc/manual/json-template-layout.adoc.vm
index 6003ffe..ff57244 100644
--- a/src/site/asciidoc/manual/json-template-layout.adoc.vm
+++ b/src/site/asciidoc/manual/json-template-layout.adoc.vm
@@ -59,13 +59,14 @@
     },
     "exception_message": {
       "$resolver": "exception",
-      "field": "message",
-      "stringified": true
+      "field": "message"
     },
     "stacktrace": {
       "$resolver": "exception",
       "field": "stackTrace",
-      "stringified": true
+      "stackTrace": {
+        "stringified": true
+      }
     }
   },
   "line_number": {
@@ -376,7 +377,9 @@
   "error.stack_trace": {
     "$resolver": "exception",
     "field": "stackTrace",
-    "stringified": true
+    "stackTrace": {
+      "stringified": true
+    }
   }
 }
 
@@ -434,20 +437,43 @@
 a|
 [source]
 ----
-config      = field , [ stringified ]
-field       = "field" -> (
-                "className"  \|
-                "message"    \|
-                "stackTrace" )
-stringified = "stringified" -> boolean
+config              = field , [ stringified ] , [ stackTrace ]
+field               = "field" -> ( "className" \| "message" \| "stackTrace" )
+
+stackTrace          = "stackTrace" -> stringified
+stringified         = "stringified" -> ( boolean \| truncation )
+truncation          = "truncation" -> (
+                        [ suffix ]
+                      , [ pointMatcherStrings ]
+                      , [ pointMatcherRegexes ]
+                      )
+suffix              = "suffix" -> string
+pointMatcherStrings = "pointMatcherStrings" -> string[]
+pointMatcherRegexes = "pointMatcherRegexes" -> string[]
 ----
 a|
 Resolves fields of the `Throwable` returned by `logEvent.getThrown()`.
 
+`stringified` is set to `false` by default. `stringified` at the root level is
+*deprecated* in favor of `stackTrace.stringified`, which has precedence if both
+are provided.
+
+`pointMatcherStrings` and `pointMatcherRegexes` enable the truncation of
+stringified stack traces after the given matching point. If both parameters are
+provided, `pointMatcherStrings` will be checked first.
+
+If a stringified stack trace truncation takes place, it will be indicated with
+`suffix`, which by default is set to the configured `truncatedStringSuffix` in
+the layout, unless explicitly provided.
+
 Note that this resolver is toggled by
 `log4j.layout.jsonTemplate.stackTraceEnabled` property.
-| Since `Throwable#getStackTrace()` clones the original `StackTraceElement[]`,
-  access to (and hence rendering of) stack traces are not garbage-free.
+a|
+Since `Throwable#getStackTrace()` clones the original `StackTraceElement[]`,
+access to (and hence rendering of) stack traces are not garbage-free.
+
+Each `pointMatcherRegexes` item triggers a `Pattern#matcher()` call, which is
+not garbage-free.
 a|
 Resolve `logEvent.getThrown().getClass().getCanonicalName()`:
 
@@ -476,20 +502,38 @@
 {
   "$resolver": "exception",
   "field": "stackTrace",
-  "stringified": true
+  "stackTrace": {
+    "stringified": true
+  }
+}
+----
+
+Resolve the stack trace into a string field such that the content will be
+truncated by the given point matcher:
+
+[source,json]
+----
+{
+  "$resolver": "exception",
+  "field": "stackTrace",
+  "stackTrace": {
+    "stringified": {
+      "truncation": {
+        "suffix": ">",
+        "pointMatcherStrings": ["at javax.servlet.http.HttpServlet.service"]
+      }
+    }
+  }
 }
 ----
 
 | exceptionRootCause
 | identical to `exception` resolver
-a|
-Resolves the fields of the innermost `Throwable` returned by
-`logEvent.getThrown()`.
-
-Note that this resolver is toggled by
-`log4j.layout.jsonTemplate.stackTraceEnabled` property.
+| identical to `exception` resolver with the exception that the innermost
+  `Throwable` in the causal-chain of `logEvent.getThrown()` is resolved
 | identical to `exception` resolver
-| identical to `exception` resolver
+| identical to `exception` resolver with the exception that `${dollar}resolver`
+  field needs to be set to `exceptionRootCause`
 
 | level
 a|
@@ -621,8 +665,6 @@
 [source]
 ----
 config      = [ stringified ] , [ fallbackKey ]
-pattern = "pattern" -> string
-includeStackTrace = "includeStacktrae" -> boolean
 stringified = "stringified" -> boolean
 fallbackKey = "fallbackKey" -> string
 ----
@@ -640,17 +682,6 @@
 }
 ----
 
-Resolve the message into a string using a pattern:
-
-[source,json]
-----
-{
-  "$resolver": "message",
-  "pattern": ""[%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress, corpAcctNumber} %C{1.}.%M:%L - %m"",
-  "stringified": true
-}
-----
-
 Resolve the message such that if it is an `ObjectMessage` or a
 `MultiformatMessage` with JSON support, its type (string, list, object, etc.)
 will be retained: