--- a/log4j-core/pom.xml	2026-04-14 14:24:27.283057809 +0200
+++ b/log4j-core/pom.xml	2026-04-14 14:28:48.101299959 +0200
@@ -156,6 +156,11 @@
       <scope>runtime</scope>
       <optional>true</optional>
     </dependency>
+    <dependency>
+      <groupId>org.codehaus.woodstox</groupId>
+      <artifactId>stax2-api</artifactId>
+      <optional>true</optional>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jXmlObjectMapper.java	2026-04-14 14:24:27.248175588 +0200
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jXmlObjectMapper.java	2026-04-14 14:31:00.994779825 +0200
@@ -17,8 +17,22 @@
 package org.apache.logging.log4j.core.jackson;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.ObjectCodec;
+import com.fasterxml.jackson.core.io.IOContext;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.xml.XmlFactory;
 import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import com.fasterxml.jackson.dataformat.xml.XmlNameProcessor;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.codehaus.stax2.XMLStreamWriter2;
+import org.codehaus.stax2.ri.Stax2WriterAdapter;
+import org.codehaus.stax2.util.StreamWriter2Delegate;
 
 /**
  * A Jackson XML {@link ObjectMapper} initialized for Log4j.
@@ -41,8 +55,167 @@
      * Create a new instance using the {@link Log4jXmlModule}.
      */
     public Log4jXmlObjectMapper(final boolean includeStacktrace, final boolean stacktraceAsString) {
-        super(new Log4jXmlModule(includeStacktrace, stacktraceAsString));
+        super(new SanitizingXmlFactory(), new Log4jXmlModule(includeStacktrace, stacktraceAsString));
         this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
     }
 
+
+    /**
+     * Writer that sanitizes text to be valid XML 1.0 by replacing disallowed code points with the replacement character (U+FFFD).
+     */
+    private static final class SanitizingWriter extends StreamWriter2Delegate {
+
+        private static final char REPLACEMENT_CHAR = '\uFFFD';
+
+        SanitizingWriter(final XMLStreamWriter2 delegate) {
+            super(delegate);
+            setParent(delegate);
+        }
+
+        @Override
+        public void writeAttribute(final String localName, final String value) throws XMLStreamException {
+            super.writeAttribute(localName, sanitizeXml10(value));
+        }
+
+        @Override
+        public void writeAttribute(final String namespaceURI, final String localName, final String value)
+                throws XMLStreamException {
+            super.writeAttribute(namespaceURI, localName, sanitizeXml10(value));
+        }
+
+        @Override
+        public void writeAttribute(
+                final String prefix, final String namespaceURI, final String localName, final String value)
+                throws XMLStreamException {
+            super.writeAttribute(prefix, namespaceURI, localName, sanitizeXml10(value));
+        }
+
+        @Override
+        public void writeCData(String text) throws XMLStreamException {
+            super.writeCData(sanitizeXml10(text));
+        }
+
+        @Override
+        public void writeCData(char[] text, int start, int len) throws XMLStreamException {
+            super.writeCData(sanitizeXml10(text, start, len));
+        }
+
+        @Override
+        public void writeCharacters(final String text) throws XMLStreamException {
+            super.writeCharacters(sanitizeXml10(text));
+        }
+
+        @Override
+        public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException {
+            super.writeCharacters(sanitizeXml10(text, start, len));
+        }
+
+        @Override
+        public void writeComment(String text) throws XMLStreamException {
+            super.writeComment(sanitizeXml10(text));
+        }
+
+        private static String sanitizeXml10(final String input) {
+            if (input == null) {
+                return null;
+            }
+            final int length = input.length();
+            // Only create a new string if we find an invalid code point.
+            // In the common case, this should avoid unnecessary allocations.
+            for (int i = 0; i < length; ) {
+                final int cp = input.codePointAt(i);
+                if (!isValidXml10(cp)) {
+                    final StringBuilder out = new StringBuilder(length);
+                    out.append(input, 0, i);
+                    appendSanitized(input, i, length, out);
+                    return out.toString();
+                }
+                i += Character.charCount(cp);
+            }
+            return input;
+        }
+
+        private static String sanitizeXml10(final char[] input, final int start, final int len) {
+            return sanitizeXml10(new String(input, start, len));
+        }
+
+        private static void appendSanitized(final String input, int i, final int length, final StringBuilder out) {
+            while (i < length) {
+                final int cp = input.codePointAt(i);
+                out.appendCodePoint(isValidXml10(cp) ? cp : REPLACEMENT_CHAR);
+                i += Character.charCount(cp);
+            }
+        }
+
+        /**
+         * Checks if a code point is valid
+         *
+         * @param codePoint a code point between {@code 0} and {@link Character#MAX_CODE_POINT}
+         * @return {@code true} if it is a valid XML 1.0 code point
+         */
+        private static boolean isValidXml10(final int codePoint) {
+            assert codePoint >= 0 && codePoint <= Character.MAX_CODE_POINT;
+            // XML 1.0 valid characters (Fifth Edition):
+            //   #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
+
+            // [#x20–#xD7FF] (placed early as a fast path for the most common case)
+            return (codePoint >= ' ' && codePoint < Character.MIN_SURROGATE)
+                    // #x9
+                    || codePoint == '\t'
+                    // #xA
+                    || codePoint == '\n'
+                    // #xD
+                    || codePoint == '\r'
+                    // [#xE000-#xFFFD]
+                    || (codePoint > Character.MAX_SURROGATE && codePoint <= 0xFFFD)
+                    // [#x10000-#x10FFFF]
+                    || codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT;
+        }
+    }
+
+    /**
+     * Factory that creates {@link SanitizingWriter} instances to ensure that all text written to the XML output is valid XML 1.0.
+     */
+    private static final class SanitizingXmlFactory extends XmlFactory {
+
+        private static final long serialVersionUID = 1L;
+
+        public SanitizingXmlFactory() {
+            super();
+        }
+
+        private SanitizingXmlFactory(
+                ObjectCodec oc,
+                int xpFeatures,
+                int xgFeatures,
+                XMLInputFactory xmlIn,
+                XMLOutputFactory xmlOut,
+                String nameForTextElem,
+                XmlNameProcessor nameProcessor) {
+            super(oc, xpFeatures, xgFeatures, xmlIn, xmlOut, nameForTextElem, nameProcessor);
+        }
+
+        @Override
+        protected XMLStreamWriter _createXmlWriter(final IOContext ctxt, final Writer w) throws IOException {
+            return new SanitizingWriter(Stax2WriterAdapter.wrapIfNecessary(super._createXmlWriter(ctxt, w)));
+        }
+
+        @Override
+        protected XMLStreamWriter _createXmlWriter(final IOContext ctxt, final OutputStream out) throws IOException {
+            return new SanitizingWriter(Stax2WriterAdapter.wrapIfNecessary(super._createXmlWriter(ctxt, out)));
+        }
+
+        @Override
+        public XmlFactory copy() {
+            _checkInvalidCopy(SanitizingXmlFactory.class);
+            return new SanitizingXmlFactory(
+                    _objectCodec,
+                    _xmlParserFeatures,
+                    _xmlGeneratorFeatures,
+                    _xmlInputFactory,
+                    _xmlOutputFactory,
+                    _cfgNameForTextElement,
+                    _nameProcessor);
+        }
+    }
 }
