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

veithen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-axiom.git


The following commit(s) were added to refs/heads/master by this push:
     new e1a129801 Generalize @StringOrCharacterData into a parameterized 
@Union checker
e1a129801 is described below

commit e1a129801b56732bf26402a2e53c2d6fb1aa50be
Author: Andreas Veithen-Knowles <[email protected]>
AuthorDate: Sun Jun 14 22:01:31 2026 +0000

    Generalize @StringOrCharacterData into a parameterized @Union checker
    
    Replaces the hardcoded String|CharacterData union checker
    (components/checker-annotations, buildutils/stringorcharacterdata-checker)
    with a general-purpose @Union(types = {...}) type-use annotation and
    Checker Framework checker (buildutils/union-annotations,
    buildutils/union-checker). @StringOrCharacterData becomes
    @Union(types = {String.class, CharacterData.class}) throughout
    core-streams, core-mixins and om-mixins.
    
    Also adds a TreeAnnotator that improves the type computed for
    conditional expressions such as `data == null ? "" : data`, so that
    they type-check against a @Union(...) target without an explicit cast.
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---
 buildutils/pom.xml                                 |   3 +-
 .../StringOrCharacterDataAnnotatedTypeFactory.java | 161 ---------------------
 .../union-annotations}/pom.xml                     |   4 +-
 .../java/org/apache/axiom/checker/union/Union.java |  26 ++--
 .../apache/axiom/checker/union/UnionBottom.java    |  20 +--
 .../apache/axiom/checker/union/UnknownUnion.java   |  10 +-
 .../pom.xml                                        |   4 +-
 .../checker/union/UnionAnnotatedTypeFactory.java   | 136 +++++++++++++++++
 .../apache/axiom/checker/union/UnionChecker.java}  |  10 +-
 .../checker/union/UnionQualifierHierarchy.java     | 155 ++++++++++++++++++++
 .../apache/axiom/checker/union/UnionTransfer.java  |  70 +++++++++
 .../axiom/checker/union/UnionTreeAnnotator.java    |  82 +++++++++++
 .../axiom/checker/union/UnionTypeHierarchy.java    |  64 ++++++++
 components/core-streams/pom.xml                    |   4 +-
 .../axiom/core/stream/CharArrayCharacterData.java  |   7 +-
 .../apache/axiom/core/stream/CharacterData.java    |   7 +-
 .../DocumentElementExtractingFilterHandler.java    |   5 +-
 .../org/apache/axiom/core/stream/XmlHandler.java   |   5 +-
 .../axiom/core/stream/XmlHandlerWrapper.java       |   5 +-
 .../stax/push/input/InternalXMLStreamWriter.java   |   5 +-
 .../stax/push/input/XmlHandlerStreamWriter.java    |   6 +-
 .../xop/AbstractXOPDecodingFilterHandler.java      |   9 +-
 .../xop/AbstractXOPEncodingFilterHandler.java      |   9 +-
 components/pom.xml                                 |   1 -
 mixins/core-mixins/pom.xml                         |   2 +-
 .../apache/axiom/core/CoreCharacterDataNode.java   |   7 +-
 .../axiom/core/CoreCharacterDataSinkNode.java      |   6 +-
 .../axiom/core/CoreCharacterDataSourceNode.java    |   5 +-
 .../axiom/core/CoreMixedContentContainer.java      |   5 +-
 .../java/org/apache/axiom/core/CoreParentNode.java |   8 +-
 .../axiom/core/impl/AbstractNodeIterator.java      |   2 +-
 .../org/apache/axiom/core/impl/TreeWalkerImpl.java |  10 +-
 .../axiom/core/impl/builder/BuildableContext.java  |   7 +-
 .../axiom/core/impl/builder/BuilderHandler.java    |   6 +-
 .../apache/axiom/core/impl/builder/Context.java    |   6 +-
 .../axiom/core/impl/builder/UnwrappingContext.java |   6 +-
 ...CoreCharacterDataContainingParentNodeMixin.java |   9 +-
 .../impl/mixin/CoreCharacterDataNodeMixin.java     |  14 +-
 .../impl/mixin/CoreMixedContentContainerMixin.java |   7 +-
 .../axiom/core/impl/mixin/CoreParentNodeMixin.java |  29 ++--
 mixins/dom-mixins/pom.xml                          |   2 +-
 mixins/om-mixins/pom.xml                           |   2 +-
 .../om/impl/common/factory/OMFactoryImpl.java      |  11 +-
 .../org/apache/axiom/om/impl/intf/TextContent.java |   8 +-
 .../impl/stream/ds/PushOMDataSourceXOPHandler.java |   6 +-
 .../impl/stream/xop/XOPDecodingFilterHandler.java  |   5 +-
 mixins/pom.xml                                     |   2 +-
 47 files changed, 677 insertions(+), 296 deletions(-)

diff --git a/buildutils/pom.xml b/buildutils/pom.xml
index 7bf8a2081..7884a4fe1 100644
--- a/buildutils/pom.xml
+++ b/buildutils/pom.xml
@@ -36,7 +36,8 @@
         <module>axiom-weaver-maven-plugin</module>
         <module>buildutils-maven-plugin</module>
         <module>shade-axiom-xml</module>
-        <module>stringorcharacterdata-checker</module>
+        <module>union-annotations</module>
+        <module>union-checker</module>
     </modules>
 
     <properties>
diff --git 
a/buildutils/stringorcharacterdata-checker/src/main/java/org/apache/axiom/checker/StringOrCharacterDataAnnotatedTypeFactory.java
 
b/buildutils/stringorcharacterdata-checker/src/main/java/org/apache/axiom/checker/StringOrCharacterDataAnnotatedTypeFactory.java
deleted file mode 100644
index dc04f06fe..000000000
--- 
a/buildutils/stringorcharacterdata-checker/src/main/java/org/apache/axiom/checker/StringOrCharacterDataAnnotatedTypeFactory.java
+++ /dev/null
@@ -1,161 +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.axiom.checker;
-
-import com.sun.source.tree.BinaryTree;
-import com.sun.source.tree.TypeCastTree;
-import java.lang.annotation.Annotation;
-import java.util.LinkedHashSet;
-import java.util.Set;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.TypeMirror;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
-import org.apache.axiom.core.stream.annotations.UnknownCharacterDataType;
-import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
-import org.checkerframework.common.basetype.BaseTypeChecker;
-import org.checkerframework.framework.type.AnnotatedTypeMirror;
-import org.checkerframework.framework.type.QualifierHierarchy;
-import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
-import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
-import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
-import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
-import org.checkerframework.javacutil.AnnotationBuilder;
-import org.checkerframework.javacutil.TypesUtils;
-
-/**
- * The annotated type factory for the {@link StringOrCharacterDataChecker}. 
This factory
- * automatically adds {@code @StringOrCharacterData} to {@link String} types 
and to types that
- * implement {@link org.apache.axiom.core.stream.CharacterData}.
- */
-public class StringOrCharacterDataAnnotatedTypeFactory extends 
BaseAnnotatedTypeFactory {
-
-    private static final String CHARACTER_DATA_NAME = 
"org.apache.axiom.core.stream.CharacterData";
-
-    private final AnnotationMirror STRING_OR_CHARACTER_DATA;
-
-    public StringOrCharacterDataAnnotatedTypeFactory(BaseTypeChecker checker) {
-        super(checker);
-        STRING_OR_CHARACTER_DATA = AnnotationBuilder.fromClass(elements, 
StringOrCharacterData.class);
-        postInit();
-    }
-
-    @Override
-    protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() 
{
-        LinkedHashSet<Class<? extends Annotation>> qualifiers = new 
LinkedHashSet<>();
-        qualifiers.add(StringOrCharacterData.class);
-        qualifiers.add(UnknownCharacterDataType.class);
-        return qualifiers;
-    }
-
-    @Override
-    protected TreeAnnotator createTreeAnnotator() {
-        return new ListTreeAnnotator(super.createTreeAnnotator(), new 
StringOrCharacterDataTreeAnnotator(this));
-    }
-
-    /**
-     * Runs after {@link 
org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator}
-     * and re-applies {@code @StringOrCharacterData} to:
-     *
-     * <ul>
-     *   <li>Binary expressions whose result type is {@link String}. This is 
necessary because
-     *       {@code PropagationTreeAnnotator} computes the LUB of the operand 
qualifiers for binary
-     *       {@code +}, so a string concatenation such as {@code "literal " + 
intValue} would
-     *       otherwise produce {@code @UnknownCharacterDataType String}.
-     *   <li>Type cast expressions whose target type is {@link String} or 
implements {@link
-     *       org.apache.axiom.core.stream.CharacterData}.
-     * </ul>
-     */
-    private class StringOrCharacterDataTreeAnnotator extends TreeAnnotator {
-
-        
StringOrCharacterDataTreeAnnotator(StringOrCharacterDataAnnotatedTypeFactory 
factory) {
-            super(factory);
-        }
-
-        @Override
-        public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
-            if (TypesUtils.isString(type.getUnderlyingType())) {
-                type.replaceAnnotation(STRING_OR_CHARACTER_DATA);
-            }
-            return null;
-        }
-
-        @Override
-        public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) 
{
-            TypeMirror underlyingType = type.getUnderlyingType();
-            if (TypesUtils.isString(underlyingType) || 
isCharacterData(underlyingType)) {
-                type.replaceAnnotation(STRING_OR_CHARACTER_DATA);
-            }
-            return null;
-        }
-    }
-
-    @Override
-    protected TypeAnnotator createTypeAnnotator() {
-        return new ListTypeAnnotator(new 
StringOrCharacterDataTypeAnnotator(this), super.createTypeAnnotator());
-    }
-
-    private class StringOrCharacterDataTypeAnnotator extends TypeAnnotator {
-
-        
StringOrCharacterDataTypeAnnotator(StringOrCharacterDataAnnotatedTypeFactory 
factory) {
-            super(factory);
-        }
-
-        @Override
-        public Void visitDeclared(AnnotatedTypeMirror.AnnotatedDeclaredType 
type, Void p) {
-            if (shouldAnnotate(type)) {
-                type.addAnnotation(STRING_OR_CHARACTER_DATA);
-            }
-            return super.visitDeclared(type, null);
-        }
-    }
-
-    private boolean shouldAnnotate(AnnotatedTypeMirror.AnnotatedDeclaredType 
type) {
-        QualifierHierarchy hierarchy = getQualifierHierarchy();
-        Set<AnnotationMirror> annotations = type.getAnnotations();
-        // Don't override existing annotations
-        for (AnnotationMirror anno : annotations) {
-            if (hierarchy.isSubtypeQualifiersOnly(anno, 
STRING_OR_CHARACTER_DATA)
-                    || 
hierarchy.isSubtypeQualifiersOnly(STRING_OR_CHARACTER_DATA, anno)) {
-                return false;
-            }
-        }
-        TypeMirror underlyingType = type.getUnderlyingType();
-        if (TypesUtils.isString(underlyingType)) {
-            return true;
-        }
-        return isCharacterData(underlyingType);
-    }
-
-    private boolean isCharacterData(TypeMirror type) {
-        if (type instanceof DeclaredType declaredType) {
-            String qualifiedName = 
TypesUtils.getQualifiedName(declaredType).toString();
-            if (qualifiedName.equals(CHARACTER_DATA_NAME)) {
-                return true;
-            }
-            // Check supertypes
-            for (TypeMirror superType : types.directSupertypes(type)) {
-                if (isCharacterData(superType)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-}
diff --git a/components/checker-annotations/pom.xml 
b/buildutils/union-annotations/pom.xml
similarity index 94%
rename from components/checker-annotations/pom.xml
rename to buildutils/union-annotations/pom.xml
index c4ac604b4..3fbad830f 100644
--- a/components/checker-annotations/pom.xml
+++ b/buildutils/union-annotations/pom.xml
@@ -22,11 +22,11 @@
 
     <parent>
         <groupId>org.apache.ws.commons.axiom</groupId>
-        <artifactId>components</artifactId>
+        <artifactId>buildutils</artifactId>
         <version>2.1.0-SNAPSHOT</version>
     </parent>
 
-    <artifactId>checker-annotations</artifactId>
+    <artifactId>union-annotations</artifactId>
 
     <url>http://ws.apache.org/axiom/</url>
 
diff --git 
a/components/checker-annotations/src/main/java/org/apache/axiom/core/stream/annotations/StringOrCharacterData.java
 
b/buildutils/union-annotations/src/main/java/org/apache/axiom/checker/union/Union.java
similarity index 54%
copy from 
components/checker-annotations/src/main/java/org/apache/axiom/core/stream/annotations/StringOrCharacterData.java
copy to 
buildutils/union-annotations/src/main/java/org/apache/axiom/checker/union/Union.java
index a2a34fd95..7a0e9e99f 100644
--- 
a/components/checker-annotations/src/main/java/org/apache/axiom/core/stream/annotations/StringOrCharacterData.java
+++ 
b/buildutils/union-annotations/src/main/java/org/apache/axiom/checker/union/Union.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.core.stream.annotations;
+package org.apache.axiom.checker.union;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -25,14 +25,22 @@ import java.lang.annotation.Target;
 import org.checkerframework.framework.qual.SubtypeOf;
 
 /**
- * Indicates that the annotated {@link Object} type use holds a value that is 
either a {@link
- * String} or a {@link org.apache.axiom.core.stream.CharacterData} instance.
+ * Indicates that the annotated type use holds a value whose runtime type is a 
subtype of one of
+ * the {@link #types()}.
  *
- * <p>This annotation is enforced by the {@code StringOrCharacterDataChecker} 
which ensures that
- * only {@link String}, {@link org.apache.axiom.core.stream.CharacterData}, or 
another {@code
- * @StringOrCharacterData Object} can be assigned to a position annotated with 
this qualifier.
+ * <p>This annotation is enforced by the {@code UnionChecker}, which ensures 
that only values whose
+ * (static) type is a subtype of one of {@link #types()}, or another {@code 
@Union} type that is a
+ * subtype of this one, can be assigned to a position annotated with this 
qualifier. {@code
+ * instanceof} checks against one of {@link #types()} (including combinations 
using {@code ||} and
+ * {@code else if}) narrow a value of unknown type to {@code @Union}.
  */
 @Retention(RetentionPolicy.CLASS)
-@Target({ElementType.TYPE_USE})
-@SubtypeOf(UnknownCharacterDataType.class)
-public @interface StringOrCharacterData {}
+@Target(ElementType.TYPE_USE)
+@SubtypeOf(UnknownUnion.class)
+public @interface Union {
+    /**
+     * The types that make up the union. The annotated value's runtime type 
must be a subtype of at
+     * least one of these types.
+     */
+    Class<?>[] types();
+}
diff --git 
a/components/checker-annotations/src/main/java/org/apache/axiom/core/stream/annotations/StringOrCharacterData.java
 
b/buildutils/union-annotations/src/main/java/org/apache/axiom/checker/union/UnionBottom.java
similarity index 62%
rename from 
components/checker-annotations/src/main/java/org/apache/axiom/core/stream/annotations/StringOrCharacterData.java
rename to 
buildutils/union-annotations/src/main/java/org/apache/axiom/checker/union/UnionBottom.java
index a2a34fd95..e68052eb7 100644
--- 
a/components/checker-annotations/src/main/java/org/apache/axiom/core/stream/annotations/StringOrCharacterData.java
+++ 
b/buildutils/union-annotations/src/main/java/org/apache/axiom/checker/union/UnionBottom.java
@@ -16,23 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.core.stream.annotations;
+package org.apache.axiom.checker.union;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import org.checkerframework.framework.qual.LiteralKind;
+import org.checkerframework.framework.qual.QualifierForLiterals;
 import org.checkerframework.framework.qual.SubtypeOf;
 
-/**
- * Indicates that the annotated {@link Object} type use holds a value that is 
either a {@link
- * String} or a {@link org.apache.axiom.core.stream.CharacterData} instance.
- *
- * <p>This annotation is enforced by the {@code StringOrCharacterDataChecker} 
which ensures that
- * only {@link String}, {@link org.apache.axiom.core.stream.CharacterData}, or 
another {@code
- * @StringOrCharacterData Object} can be assigned to a position annotated with 
this qualifier.
- */
+/** The bottom type in the {@link Union} type hierarchy. This is the qualifier 
for {@code null}. */
 @Retention(RetentionPolicy.CLASS)
-@Target({ElementType.TYPE_USE})
-@SubtypeOf(UnknownCharacterDataType.class)
-public @interface StringOrCharacterData {}
+@Target(ElementType.TYPE_USE)
+@SubtypeOf(Union.class)
+@QualifierForLiterals(LiteralKind.NULL)
+public @interface UnionBottom {}
diff --git 
a/components/checker-annotations/src/main/java/org/apache/axiom/core/stream/annotations/UnknownCharacterDataType.java
 
b/buildutils/union-annotations/src/main/java/org/apache/axiom/checker/union/UnknownUnion.java
similarity index 80%
rename from 
components/checker-annotations/src/main/java/org/apache/axiom/core/stream/annotations/UnknownCharacterDataType.java
rename to 
buildutils/union-annotations/src/main/java/org/apache/axiom/checker/union/UnknownUnion.java
index 33ba01ab0..de3ea7b3d 100644
--- 
a/components/checker-annotations/src/main/java/org/apache/axiom/core/stream/annotations/UnknownCharacterDataType.java
+++ 
b/buildutils/union-annotations/src/main/java/org/apache/axiom/checker/union/UnknownUnion.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.core.stream.annotations;
+package org.apache.axiom.checker.union;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -26,11 +26,11 @@ import 
org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
 import org.checkerframework.framework.qual.SubtypeOf;
 
 /**
- * The top type in the StringOrCharacterData type hierarchy. This is the 
default qualifier for
- * unannotated types and represents an unknown character data type.
+ * The top type in the {@link Union} type hierarchy. This is the default 
qualifier for unannotated
+ * types and represents a value of unknown (unconstrained) type.
  */
 @Retention(RetentionPolicy.CLASS)
-@Target({ElementType.TYPE_USE})
+@Target(ElementType.TYPE_USE)
 @SubtypeOf({})
 @DefaultQualifierInHierarchy
-public @interface UnknownCharacterDataType {}
+public @interface UnknownUnion {}
diff --git a/buildutils/stringorcharacterdata-checker/pom.xml 
b/buildutils/union-checker/pom.xml
similarity index 93%
rename from buildutils/stringorcharacterdata-checker/pom.xml
rename to buildutils/union-checker/pom.xml
index cac55858b..51ad2dd6e 100644
--- a/buildutils/stringorcharacterdata-checker/pom.xml
+++ b/buildutils/union-checker/pom.xml
@@ -26,7 +26,7 @@
         <version>2.1.0-SNAPSHOT</version>
     </parent>
 
-    <artifactId>stringorcharacterdata-checker</artifactId>
+    <artifactId>union-checker</artifactId>
 
     <url>http://ws.apache.org/axiom/</url>
 
@@ -37,7 +37,7 @@
         </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
-            <artifactId>checker-annotations</artifactId>
+            <artifactId>union-annotations</artifactId>
             <version>${project.version}</version>
         </dependency>
         <dependency>
diff --git 
a/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionAnnotatedTypeFactory.java
 
b/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionAnnotatedTypeFactory.java
new file mode 100644
index 000000000..1eefb54e6
--- /dev/null
+++ 
b/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionAnnotatedTypeFactory.java
@@ -0,0 +1,136 @@
+/*
+ * 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.axiom.checker.union;
+
+import java.lang.annotation.Annotation;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.framework.type.TypeHierarchy;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/** The annotated type factory for the {@link UnionChecker}. */
+public class UnionAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+    final AnnotationMirror UNKNOWN_UNION;
+
+    private final ExecutableElement typesElement;
+    private final Types types;
+
+    public UnionAnnotatedTypeFactory(BaseTypeChecker checker) {
+        super(checker);
+        UNKNOWN_UNION = AnnotationBuilder.fromClass(elements, 
UnknownUnion.class);
+        typesElement = TreeUtils.getMethod(Union.class, "types", 0, 
getProcessingEnv());
+        types = checker.getTypeUtils();
+        postInit();
+    }
+
+    @Override
+    protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() 
{
+        LinkedHashSet<Class<? extends Annotation>> qualifiers = new 
LinkedHashSet<>();
+        qualifiers.add(Union.class);
+        qualifiers.add(UnknownUnion.class);
+        qualifiers.add(UnionBottom.class);
+        return qualifiers;
+    }
+
+    @Override
+    protected QualifierHierarchy createQualifierHierarchy() {
+        return new UnionQualifierHierarchy(getSupportedTypeQualifiers(), 
elements, this);
+    }
+
+    @Override
+    protected TypeHierarchy createTypeHierarchy() {
+        return new UnionTypeHierarchy(
+                checker, getQualifierHierarchy(), ignoreRawTypeArguments, 
checker.hasOption("invariantArrays"), this);
+    }
+
+    @Override
+    public CFTransfer createFlowTransferFunction(CFAbstractAnalysis<CFValue, 
CFStore, CFTransfer> analysis) {
+        return new UnionTransfer(analysis);
+    }
+
+    @Override
+    protected TreeAnnotator createTreeAnnotator() {
+        return new ListTreeAnnotator(super.createTreeAnnotator(), new 
UnionTreeAnnotator(this));
+    }
+
+    /**
+     * Returns whether {@code underlyingType} (erased) is a subtype of one of 
the types listed in
+     * {@code unionAnno}'s {@link Union#types()}. This is the same escape rule 
that {@link
+     * UnionTypeHierarchy} applies to allow an {@code @UnknownUnion}-typed 
expression to be used
+     * where {@code @Union(types = {...})} is expected.
+     */
+    boolean isSubtypeOfUnionMember(TypeMirror underlyingType, AnnotationMirror 
unionAnno) {
+        TypeMirror erased = types.erasure(underlyingType);
+        for (TypeMirror memberType : getUnionTypes(unionAnno)) {
+            if (types.isSubtype(erased, memberType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the {@link Union#types()} element of the given {@code @Union} 
annotation.
+     *
+     * @param unionAnno a {@code @Union(types = {...})} annotation
+     * @return the types listed in {@link Union#types()}
+     */
+    List<TypeMirror> getUnionTypes(AnnotationMirror unionAnno) {
+        return AnnotationUtils.getElementValueArray(unionAnno, typesElement, 
TypeMirror.class);
+    }
+
+    /**
+     * Creates a {@code @Union(types = {...})} annotation for the given member 
types.
+     *
+     * @param memberTypes the types to use for {@link Union#types()}
+     * @return the resulting annotation
+     */
+    AnnotationMirror createUnion(List<TypeMirror> memberTypes) {
+        return new AnnotationBuilder(getProcessingEnv(), Union.class)
+                .setValue("types", memberTypes)
+                .build();
+    }
+
+    /**
+     * Creates a {@code @Union(types = {memberType})} annotation for a single 
member type.
+     *
+     * @param memberType the type to use for {@link Union#types()}
+     * @return the resulting annotation
+     */
+    AnnotationMirror createUnion(TypeMirror memberType) {
+        return createUnion(List.of(memberType));
+    }
+}
diff --git 
a/buildutils/stringorcharacterdata-checker/src/main/java/org/apache/axiom/checker/StringOrCharacterDataChecker.java
 
b/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionChecker.java
similarity index 74%
rename from 
buildutils/stringorcharacterdata-checker/src/main/java/org/apache/axiom/checker/StringOrCharacterDataChecker.java
rename to 
buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionChecker.java
index e51466c7c..0da20cc42 100644
--- 
a/buildutils/stringorcharacterdata-checker/src/main/java/org/apache/axiom/checker/StringOrCharacterDataChecker.java
+++ 
b/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionChecker.java
@@ -16,16 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.axiom.checker;
+package org.apache.axiom.checker.union;
 
 import com.google.auto.service.AutoService;
 import javax.annotation.processing.Processor;
 import org.checkerframework.common.basetype.BaseTypeChecker;
 
 /**
- * A Checker Framework checker that enforces that {@code 
@StringOrCharacterData Object} can only be
- * assigned from {@link String}, {@link 
org.apache.axiom.core.stream.CharacterData}, or another
- * {@code @StringOrCharacterData Object}.
+ * A Checker Framework checker that enforces that a {@code @Union(types = 
{A.class, B.class, ...})}
+ * type use can only hold values whose type is a subtype of one of {@code A}, 
{@code B}, etc., or
+ * another {@code @Union} type that is a subtype of it.
  */
 @AutoService(Processor.class)
-public class StringOrCharacterDataChecker extends BaseTypeChecker {}
+public class UnionChecker extends BaseTypeChecker {}
diff --git 
a/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionQualifierHierarchy.java
 
b/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionQualifierHierarchy.java
new file mode 100644
index 000000000..b6eb7b47b
--- /dev/null
+++ 
b/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionQualifierHierarchy.java
@@ -0,0 +1,155 @@
+/*
+ * 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.axiom.checker.union;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
+import org.checkerframework.framework.util.QualifierKind;
+
+/**
+ * The qualifier hierarchy for {@link Union}, {@link UnknownUnion} and {@link 
UnionBottom}.
+ *
+ * <p>Subtyping between two {@link Union} annotations is subset based: {@code 
@Union(S1)} is a
+ * subtype of {@code @Union(S2)} if every type in {@code S1} is a subtype of 
some type in {@code
+ * S2}. The least upper bound of two {@link Union} annotations is the 
(deduplicated) union of their
+ * {@link Union#types()}. The greatest lower bound is approximated as {@code 
@Union(types = {})}
+ * (a qualifier that is a subtype of every {@code @Union} qualifier) unless 
the two sets of types
+ * are equal.
+ */
+final class UnionQualifierHierarchy extends MostlyNoElementQualifierHierarchy {
+
+    private final UnionAnnotatedTypeFactory atypeFactory;
+    private final Types types;
+    private final QualifierKind unionKind;
+
+    UnionQualifierHierarchy(
+            Collection<Class<? extends Annotation>> qualifierClasses,
+            Elements elements,
+            UnionAnnotatedTypeFactory atypeFactory) {
+        super(qualifierClasses, elements, atypeFactory);
+        this.atypeFactory = atypeFactory;
+        this.types = atypeFactory.getChecker().getTypeUtils();
+        this.unionKind = getQualifierKind(Union.class.getCanonicalName());
+    }
+
+    @Override
+    protected boolean isSubtypeWithElements(
+            AnnotationMirror subAnno, QualifierKind subKind, AnnotationMirror 
superAnno, QualifierKind superKind) {
+        // Both subKind and superKind are the Union kind: @Union(S1) <: 
@Union(S2) iff every type
+        // in S1 is a subtype of some type in S2.
+        List<TypeMirror> superTypes = atypeFactory.getUnionTypes(superAnno);
+        for (TypeMirror subType : atypeFactory.getUnionTypes(subAnno)) {
+            if (!isSubtypeOfAny(subType, superTypes)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean isSubtypeOfAny(TypeMirror subType, List<TypeMirror> 
superTypes) {
+        for (TypeMirror superType : superTypes) {
+            if (types.isSubtype(subType, superType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    protected AnnotationMirror leastUpperBoundWithElements(
+            AnnotationMirror a1,
+            QualifierKind qualifierKind1,
+            AnnotationMirror a2,
+            QualifierKind qualifierKind2,
+            QualifierKind lubKind) {
+        // The lub kind has elements, i.e. is the Union kind. Since 
UnionBottom is the only other
+        // kind without elements that can occur here (the lub of @Union and 
@UnknownUnion is
+        // @UnknownUnion, which has no elements and is therefore handled 
before this method is
+        // called), the lub of @Union(S) and @UnionBottom is @Union(S).
+        if (qualifierKind1 != unionKind) {
+            return a2;
+        }
+        if (qualifierKind2 != unionKind) {
+            return a1;
+        }
+        List<TypeMirror> types1 = atypeFactory.getUnionTypes(a1);
+        List<TypeMirror> union = new ArrayList<>(types1);
+        boolean changed = false;
+        for (TypeMirror type2 : atypeFactory.getUnionTypes(a2)) {
+            if (!containsSameType(union, type2)) {
+                union.add(type2);
+                changed = true;
+            }
+        }
+        return changed ? atypeFactory.createUnion(union) : a1;
+    }
+
+    @Override
+    protected AnnotationMirror greatestLowerBoundWithElements(
+            AnnotationMirror a1,
+            QualifierKind qualifierKind1,
+            AnnotationMirror a2,
+            QualifierKind qualifierKind2,
+            QualifierKind glbKind) {
+        // The glb kind has elements, i.e. is the Union kind. @UnionBottom 
can't occur here because
+        // the glb of @Union(S) and @UnionBottom is @UnionBottom, which has no 
elements and is
+        // therefore handled before this method is called. The only other kind 
without elements is
+        // @UnknownUnion, and the glb of @Union(S) and @UnknownUnion is 
@Union(S).
+        if (qualifierKind1 != unionKind) {
+            return a2;
+        }
+        if (qualifierKind2 != unionKind) {
+            return a1;
+        }
+        List<TypeMirror> types1 = atypeFactory.getUnionTypes(a1);
+        List<TypeMirror> types2 = atypeFactory.getUnionTypes(a2);
+        if (sameTypes(types1, types2)) {
+            return a1;
+        }
+        return atypeFactory.createUnion(List.of());
+    }
+
+    private boolean containsSameType(List<TypeMirror> list, TypeMirror type) {
+        for (TypeMirror element : list) {
+            if (types.isSameType(element, type)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean sameTypes(List<TypeMirror> types1, List<TypeMirror> 
types2) {
+        if (types1.size() != types2.size()) {
+            return false;
+        }
+        for (TypeMirror type1 : types1) {
+            if (!containsSameType(types2, type1)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git 
a/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionTransfer.java
 
b/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionTransfer.java
new file mode 100644
index 000000000..398a4ed7e
--- /dev/null
+++ 
b/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionTransfer.java
@@ -0,0 +1,70 @@
+/*
+ * 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.axiom.checker.union;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.util.Types;
+import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.InstanceOfNode;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.framework.flow.CFAbstractAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+
+/**
+ * Refines the type of the tested expression to {@code @Union(types = {T})}, 
where {@code T} is the
+ * type tested against, in the "then" branch of an {@code instanceof} check. 
Combined with the
+ * least upper bound computed by {@link UnionQualifierHierarchy} at 
control-flow join points, this
+ * makes checks such as {@code x instanceof A || x instanceof B} and the 
equivalent {@code if}/{@code
+ * else if} chain refine {@code x} to {@code @Union(types = {A, B})}.
+ */
+final class UnionTransfer extends CFTransfer {
+
+    private final UnionAnnotatedTypeFactory atypeFactory;
+    private final Types types;
+
+    UnionTransfer(CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
+        super(analysis);
+        atypeFactory = (UnionAnnotatedTypeFactory) analysis.getTypeFactory();
+        types = atypeFactory.getChecker().getTypeUtils();
+    }
+
+    @Override
+    public TransferResult<CFValue, CFStore> visitInstanceOf(InstanceOfNode 
node, TransferInput<CFValue, CFStore> in) {
+        TransferResult<CFValue, CFStore> result = super.visitInstanceOf(node, 
in);
+
+        JavaExpression expr = JavaExpression.fromNode(node.getOperand());
+        AnnotationMirror refined = 
atypeFactory.createUnion(types.erasure(node.getRefType()));
+
+        CFStore thenStore;
+        CFStore elseStore;
+        if (result.containsTwoStores()) {
+            thenStore = result.getThenStore();
+            elseStore = result.getElseStore();
+        } else {
+            thenStore = result.getRegularStore();
+            elseStore = thenStore.copy();
+        }
+        thenStore.insertValue(expr, refined);
+        return new ConditionalTransferResult<>(result.getResultValue(), 
thenStore, elseStore);
+    }
+}
diff --git 
a/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionTreeAnnotator.java
 
b/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionTreeAnnotator.java
new file mode 100644
index 000000000..08bebcd88
--- /dev/null
+++ 
b/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionTreeAnnotator.java
@@ -0,0 +1,82 @@
+/*
+ * 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.axiom.checker.union;
+
+import com.sun.source.tree.ConditionalExpressionTree;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * Improves the type computed for a conditional expression ({@code cond ? a : 
b}) where one branch
+ * is {@code @Union(types = {...})} and the other is the default {@code 
@UnknownUnion}.
+ *
+ * <p>By default, the least upper bound of {@code @Union(S)} and {@code 
@UnknownUnion} is {@code
+ * @UnknownUnion} (the top of the hierarchy), since {@code @UnknownUnion} has 
no {@link
+ * Union#types()} to take the union with. This is overly conservative when the
+ * {@code @UnknownUnion}-typed branch's own (erased) type is itself a subtype 
of a member of {@code
+ * S}: in that case, {@link UnionTypeHierarchy}'s escape rule already 
establishes {@code
+ * @UnknownUnion T <: @Union(S)}, so {@code @Union(S)} is itself a valid (and 
more useful) upper
+ * bound for both branches.
+ *
+ * <p>This allows expressions such as {@code data == null ? "" : data}, where 
{@code data} is
+ * {@code @Union(types = {String.class, CharacterData.class}) Object}, to be 
used directly (e.g. in
+ * a {@code return} statement) without an explicit cast.
+ */
+final class UnionTreeAnnotator extends TreeAnnotator {
+
+    private final UnionAnnotatedTypeFactory atypeFactory;
+
+    UnionTreeAnnotator(UnionAnnotatedTypeFactory atypeFactory) {
+        super(atypeFactory);
+        this.atypeFactory = atypeFactory;
+    }
+
+    @Override
+    public Void visitConditionalExpression(ConditionalExpressionTree tree, 
AnnotatedTypeMirror type) {
+        AnnotatedTypeMirror thenType = 
atypeFactory.getAnnotatedType(tree.getTrueExpression());
+        AnnotatedTypeMirror elseType = 
atypeFactory.getAnnotatedType(tree.getFalseExpression());
+        AnnotationMirror promoted = promote(thenType, elseType);
+        if (promoted == null) {
+            promoted = promote(elseType, thenType);
+        }
+        if (promoted != null) {
+            type.replaceAnnotation(promoted);
+        }
+        return super.visitConditionalExpression(tree, type);
+    }
+
+    /**
+     * If {@code unionSide} is {@code @Union(S)} and {@code unknownSide} is 
{@code @UnknownUnion}
+     * with an underlying type that is itself a subtype of some member of 
{@code S}, returns {@code
+     * @Union(S)}. Otherwise returns {@code null}.
+     */
+    private AnnotationMirror promote(AnnotatedTypeMirror unionSide, 
AnnotatedTypeMirror unknownSide) {
+        AnnotationMirror unionAnno = 
unionSide.getPrimaryAnnotationInHierarchy(atypeFactory.UNKNOWN_UNION);
+        if (unionAnno == null || !AnnotationUtils.areSameByName(unionAnno, 
Union.class.getCanonicalName())) {
+            return null;
+        }
+        AnnotationMirror unknownAnno = 
unknownSide.getPrimaryAnnotationInHierarchy(atypeFactory.UNKNOWN_UNION);
+        if (unknownAnno == null || !AnnotationUtils.areSameByName(unknownAnno, 
UnknownUnion.class.getCanonicalName())) {
+            return null;
+        }
+        return 
atypeFactory.isSubtypeOfUnionMember(unknownSide.getUnderlyingType(), unionAnno) 
? unionAnno : null;
+    }
+}
diff --git 
a/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionTypeHierarchy.java
 
b/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionTypeHierarchy.java
new file mode 100644
index 000000000..95ebfc5eb
--- /dev/null
+++ 
b/buildutils/union-checker/src/main/java/org/apache/axiom/checker/union/UnionTypeHierarchy.java
@@ -0,0 +1,64 @@
+/*
+ * 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.axiom.checker.union;
+
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.DefaultTypeHierarchy;
+import org.checkerframework.framework.type.QualifierHierarchy;
+import org.checkerframework.javacutil.AnnotationUtils;
+
+/**
+ * Adds an escape rule to the default subtyping rules: a value of 
unconstrained type ({@code
+ * @UnknownUnion}) may be assigned to a {@code @Union(types = 
{...})}-annotated location if its
+ * underlying (erased) Java type is itself a subtype of one of {@link 
Union#types()}. This allows,
+ * for example, a {@link String}-typed expression to be returned as {@code 
@Union(types =
+ * {String.class, ...}) Object} without an explicit cast.
+ */
+final class UnionTypeHierarchy extends DefaultTypeHierarchy {
+
+    private final UnionAnnotatedTypeFactory atypeFactory;
+
+    UnionTypeHierarchy(
+            BaseTypeChecker checker,
+            QualifierHierarchy qualHierarchy,
+            boolean ignoreRawTypes,
+            boolean invariantArrayComponents,
+            UnionAnnotatedTypeFactory atypeFactory) {
+        super(checker, qualHierarchy, ignoreRawTypes, 
invariantArrayComponents);
+        this.atypeFactory = atypeFactory;
+    }
+
+    @Override
+    public boolean isSubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror 
supertype) {
+        if (super.isSubtype(subtype, supertype)) {
+            return true;
+        }
+        AnnotationMirror superAnno = 
supertype.getPrimaryAnnotationInHierarchy(atypeFactory.UNKNOWN_UNION);
+        if (superAnno == null || !AnnotationUtils.areSameByName(superAnno, 
Union.class.getCanonicalName())) {
+            return false;
+        }
+        AnnotationMirror subAnno = 
subtype.getPrimaryAnnotationInHierarchy(atypeFactory.UNKNOWN_UNION);
+        if (subAnno == null || !AnnotationUtils.areSameByName(subAnno, 
UnknownUnion.class.getCanonicalName())) {
+            return false;
+        }
+        return 
atypeFactory.isSubtypeOfUnionMember(subtype.getUnderlyingType(), superAnno);
+    }
+}
diff --git a/components/core-streams/pom.xml b/components/core-streams/pom.xml
index 71d46b441..67912a443 100644
--- a/components/core-streams/pom.xml
+++ b/components/core-streams/pom.xml
@@ -37,7 +37,7 @@
         </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
-            <artifactId>checker-annotations</artifactId>
+            <artifactId>union-annotations</artifactId>
             <version>${project.version}</version>
             <scope>provided</scope>
         </dependency>
@@ -122,7 +122,7 @@
                     <annotationProcessorPaths combine.children="append">
                         <annotationProcessorPath>
                             <groupId>${project.groupId}</groupId>
-                            
<artifactId>stringorcharacterdata-checker</artifactId>
+                            <artifactId>union-checker</artifactId>
                             <version>${project.version}</version>
                         </annotationProcessorPath>
                     </annotationProcessorPaths>
diff --git 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/CharArrayCharacterData.java
 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/CharArrayCharacterData.java
index 1752b338d..756554030 100644
--- 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/CharArrayCharacterData.java
+++ 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/CharArrayCharacterData.java
@@ -19,8 +19,7 @@
 package org.apache.axiom.core.stream;
 
 import java.io.IOException;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
-import org.apache.axiom.core.stream.annotations.UnknownCharacterDataType;
+import org.apache.axiom.checker.union.Union;
 
 /**
  * A reusable {@link CharacterData} implementation backed by a {@code char[]} 
slice. Instances are
@@ -53,7 +52,7 @@ public final class CharArrayCharacterData implements 
CharacterData {
     }
 
     @Override
-    public String toString(@UnknownCharacterDataType CharArrayCharacterData 
this) {
+    public String toString() {
         return new String(ch, start, length);
     }
 
@@ -68,7 +67,7 @@ public final class CharArrayCharacterData implements 
CharacterData {
     }
 
     @Override
-    public @StringOrCharacterData Object retain() {
+    public @Union(types = {String.class, CharacterData.class}) Object retain() 
{
         return new String(ch, start, length);
     }
 }
diff --git 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/CharacterData.java
 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/CharacterData.java
index 5b2cbed07..1ec64322c 100644
--- 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/CharacterData.java
+++ 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/CharacterData.java
@@ -19,8 +19,7 @@
 package org.apache.axiom.core.stream;
 
 import java.io.IOException;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
-import org.apache.axiom.core.stream.annotations.UnknownCharacterDataType;
+import org.apache.axiom.checker.union.Union;
 
 // TODO: clean up this Javadoc
 /**
@@ -30,7 +29,7 @@ import 
org.apache.axiom.core.stream.annotations.UnknownCharacterDataType;
  */
 public interface CharacterData {
     @Override
-    String toString(@UnknownCharacterDataType CharacterData this);
+    String toString();
 
     void writeTo(CharacterDataSink sink) throws IOException;
 
@@ -51,6 +50,6 @@ public interface CharacterData {
      * @return a {@link String} or {@link CharacterData} representation of 
this instance that
      *     remains valid beyond the current invocation
      */
-    @StringOrCharacterData
+    @Union(types = {String.class, CharacterData.class})
     Object retain();
 }
diff --git 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/DocumentElementExtractingFilterHandler.java
 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/DocumentElementExtractingFilterHandler.java
index 7206b41e2..3791754c5 100644
--- 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/DocumentElementExtractingFilterHandler.java
+++ 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/DocumentElementExtractingFilterHandler.java
@@ -18,7 +18,7 @@
  */
 package org.apache.axiom.core.stream;
 
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
+import org.apache.axiom.checker.union.Union;
 
 public final class DocumentElementExtractingFilterHandler extends 
XmlHandlerWrapper {
     private int depth;
@@ -54,7 +54,8 @@ public final class DocumentElementExtractingFilterHandler 
extends XmlHandlerWrap
             throws StreamException {}
 
     @Override
-    public void processCharacterData(@StringOrCharacterData Object data, 
boolean ignorable) throws StreamException {
+    public void processCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data, boolean ignorable)
+            throws StreamException {
         if (depth > 0) {
             super.processCharacterData(data, ignorable);
         }
diff --git 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/XmlHandler.java
 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/XmlHandler.java
index bf66d885c..7d1b228a8 100644
--- 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/XmlHandler.java
+++ 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/XmlHandler.java
@@ -18,7 +18,7 @@
  */
 package org.apache.axiom.core.stream;
 
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
+import org.apache.axiom.checker.union.Union;
 
 public interface XmlHandler {
     void startDocument(String inputEncoding, String xmlVersion, String 
xmlEncoding, Boolean standalone)
@@ -123,7 +123,8 @@ public interface XmlHandler {
      *     otherwise
      * @throws StreamException if an error occurs when processing the event
      */
-    void processCharacterData(@StringOrCharacterData Object data, boolean 
ignorable) throws StreamException;
+    void processCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data, boolean ignorable)
+            throws StreamException;
 
     /**
      * Notify the handler of the beginning of a processing instruction.
diff --git 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/XmlHandlerWrapper.java
 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/XmlHandlerWrapper.java
index 2635f6ead..45213b62f 100644
--- 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/XmlHandlerWrapper.java
+++ 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/XmlHandlerWrapper.java
@@ -18,7 +18,7 @@
  */
 package org.apache.axiom.core.stream;
 
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
+import org.apache.axiom.checker.union.Union;
 
 public class XmlHandlerWrapper implements XmlHandler {
     private final XmlHandler parent;
@@ -81,7 +81,8 @@ public class XmlHandlerWrapper implements XmlHandler {
     }
 
     @Override
-    public void processCharacterData(@StringOrCharacterData Object data, 
boolean ignorable) throws StreamException {
+    public void processCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data, boolean ignorable)
+            throws StreamException {
         parent.processCharacterData(data, ignorable);
     }
 
diff --git 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/stax/push/input/InternalXMLStreamWriter.java
 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/stax/push/input/InternalXMLStreamWriter.java
index a410184a7..640ce81ec 100644
--- 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/stax/push/input/InternalXMLStreamWriter.java
+++ 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/stax/push/input/InternalXMLStreamWriter.java
@@ -20,8 +20,9 @@ package org.apache.axiom.core.stream.stax.push.input;
 
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
+import org.apache.axiom.checker.union.Union;
+import org.apache.axiom.core.stream.CharacterData;
 
 public interface InternalXMLStreamWriter extends XMLStreamWriter {
-    void writeCharacterData(@StringOrCharacterData Object data) throws 
XMLStreamException;
+    void writeCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data) throws XMLStreamException;
 }
diff --git 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/stax/push/input/XmlHandlerStreamWriter.java
 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/stax/push/input/XmlHandlerStreamWriter.java
index 8b5f2ff05..dceedc544 100644
--- 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/stax/push/input/XmlHandlerStreamWriter.java
+++ 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/stax/push/input/XmlHandlerStreamWriter.java
@@ -26,9 +26,10 @@ import java.util.NoSuchElementException;
 import javax.xml.XMLConstants;
 import javax.xml.namespace.NamespaceContext;
 import javax.xml.stream.XMLStreamException;
+import org.apache.axiom.checker.union.Union;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.StreamException;
 import org.apache.axiom.core.stream.XmlHandler;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
 import org.apache.axiom.core.stream.serializer.Serializer;
 import 
org.apache.axiom.core.stream.serializer.writer.UnmappableCharacterHandler;
 import org.apache.commons.logging.Log;
@@ -362,7 +363,8 @@ public final class XmlHandlerStreamWriter implements 
InternalXMLStreamWriter, Na
     }
 
     @Override
-    public void writeCharacterData(@StringOrCharacterData Object data) throws 
XMLStreamException {
+    public void writeCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data)
+            throws XMLStreamException {
         finishStartElement();
         try {
             handler.processCharacterData(data, false);
diff --git 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/xop/AbstractXOPDecodingFilterHandler.java
 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/xop/AbstractXOPDecodingFilterHandler.java
index 65a61e6d6..d442e8e61 100644
--- 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/xop/AbstractXOPDecodingFilterHandler.java
+++ 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/xop/AbstractXOPDecodingFilterHandler.java
@@ -21,10 +21,11 @@ package org.apache.axiom.core.stream.xop;
 import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import javax.xml.namespace.QName;
+import org.apache.axiom.checker.union.Union;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.StreamException;
 import org.apache.axiom.core.stream.XmlHandler;
 import org.apache.axiom.core.stream.XmlHandlerWrapper;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
 
 public abstract class AbstractXOPDecodingFilterHandler extends 
XmlHandlerWrapper {
     private static final String SOLE_CHILD_MSG =
@@ -45,7 +46,8 @@ public abstract class AbstractXOPDecodingFilterHandler 
extends XmlHandlerWrapper
         super(parent);
     }
 
-    protected abstract @StringOrCharacterData Object buildCharacterData(String 
contentID) throws StreamException;
+    protected abstract @Union(types = {String.class, CharacterData.class}) 
Object buildCharacterData(String contentID)
+            throws StreamException;
 
     private void inContent() throws StreamException {
         switch (state) {
@@ -129,7 +131,8 @@ public abstract class AbstractXOPDecodingFilterHandler 
extends XmlHandlerWrapper
     }
 
     @Override
-    public void processCharacterData(@StringOrCharacterData Object data, 
boolean ignorable) throws StreamException {
+    public void processCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data, boolean ignorable)
+            throws StreamException {
         inContent();
         super.processCharacterData(data, ignorable);
     }
diff --git 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/xop/AbstractXOPEncodingFilterHandler.java
 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/xop/AbstractXOPEncodingFilterHandler.java
index 41e618243..2acc4b0fc 100644
--- 
a/components/core-streams/src/main/java/org/apache/axiom/core/stream/xop/AbstractXOPEncodingFilterHandler.java
+++ 
b/components/core-streams/src/main/java/org/apache/axiom/core/stream/xop/AbstractXOPEncodingFilterHandler.java
@@ -18,10 +18,11 @@
  */
 package org.apache.axiom.core.stream.xop;
 
+import org.apache.axiom.checker.union.Union;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.StreamException;
 import org.apache.axiom.core.stream.XmlHandler;
 import org.apache.axiom.core.stream.XmlHandlerWrapper;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
 
 public abstract class AbstractXOPEncodingFilterHandler extends 
XmlHandlerWrapper {
     private boolean inXOPInclude;
@@ -30,7 +31,8 @@ public abstract class AbstractXOPEncodingFilterHandler 
extends XmlHandlerWrapper
         super(parent);
     }
 
-    protected abstract String processCharacterData(@StringOrCharacterData 
Object data) throws StreamException;
+    protected abstract String processCharacterData(@Union(types = 
{String.class, CharacterData.class}) Object data)
+            throws StreamException;
 
     /**
      * Build a cid URL from the given content ID as described in RFC2392.
@@ -91,7 +93,8 @@ public abstract class AbstractXOPEncodingFilterHandler 
extends XmlHandlerWrapper
     }
 
     @Override
-    public void processCharacterData(@StringOrCharacterData Object data, 
boolean ignorable) throws StreamException {
+    public void processCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data, boolean ignorable)
+            throws StreamException {
         if (!ignorable) {
             String contentID = processCharacterData(data);
             if (contentID != null) {
diff --git a/components/pom.xml b/components/pom.xml
index 26bd1b9f5..6d945feb7 100644
--- a/components/pom.xml
+++ b/components/pom.xml
@@ -34,7 +34,6 @@
     <modules>
         <module>base64-utils</module>
         <module>blob</module>
-        <module>checker-annotations</module>
         <module>core-streams</module>
         <module>io</module>
         <module>namespace-utils</module>
diff --git a/mixins/core-mixins/pom.xml b/mixins/core-mixins/pom.xml
index 7ca286b79..cd06aa8d5 100644
--- a/mixins/core-mixins/pom.xml
+++ b/mixins/core-mixins/pom.xml
@@ -38,7 +38,7 @@
         </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
-            <artifactId>checker-annotations</artifactId>
+            <artifactId>union-annotations</artifactId>
             <version>${project.version}</version>
             <scope>provided</scope>
         </dependency>
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreCharacterDataNode.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreCharacterDataNode.java
index ce5d9c2c5..ee410b99f 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreCharacterDataNode.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreCharacterDataNode.java
@@ -18,14 +18,15 @@
  */
 package org.apache.axiom.core;
 
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
+import org.apache.axiom.checker.union.Union;
+import org.apache.axiom.core.stream.CharacterData;
 
 public interface CoreCharacterDataNode extends CoreLeafNode, 
CoreCharacterDataContainer {
     @Override
-    @StringOrCharacterData
+    @Union(types = {String.class, CharacterData.class})
     Object coreGetCharacterData();
 
-    void coreSetCharacterData(@StringOrCharacterData Object data);
+    void coreSetCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data);
 
     /**
      * Check whether this text node contains element content whitespace (also 
called "ignorable
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreCharacterDataSinkNode.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreCharacterDataSinkNode.java
index 26c4efbdc..032072417 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreCharacterDataSinkNode.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreCharacterDataSinkNode.java
@@ -18,8 +18,10 @@
  */
 package org.apache.axiom.core;
 
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
+import org.apache.axiom.checker.union.Union;
+import org.apache.axiom.core.stream.CharacterData;
 
 public interface CoreCharacterDataSinkNode {
-    void coreSetCharacterData(@StringOrCharacterData Object data, Semantics 
semantics) throws CoreModelException;
+    void coreSetCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data, Semantics semantics)
+            throws CoreModelException;
 }
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreCharacterDataSourceNode.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreCharacterDataSourceNode.java
index 9c147e10f..8f931f6a0 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreCharacterDataSourceNode.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreCharacterDataSourceNode.java
@@ -18,9 +18,10 @@
  */
 package org.apache.axiom.core;
 
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
+import org.apache.axiom.checker.union.Union;
+import org.apache.axiom.core.stream.CharacterData;
 
 public interface CoreCharacterDataSourceNode extends CoreNode {
-    @StringOrCharacterData
+    @Union(types = {String.class, CharacterData.class})
     Object coreGetCharacterData() throws CoreModelException;
 }
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreMixedContentContainer.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreMixedContentContainer.java
index a68497dda..02b858dd1 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreMixedContentContainer.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreMixedContentContainer.java
@@ -18,9 +18,10 @@
  */
 package org.apache.axiom.core;
 
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
+import org.apache.axiom.checker.union.Union;
+import org.apache.axiom.core.stream.CharacterData;
 
 public interface CoreMixedContentContainer extends CoreParentNode, 
CoreCharacterDataSinkNode {
-    @StringOrCharacterData
+    @Union(types = {String.class, CharacterData.class})
     Object coreGetCharacterData(ElementAction elementAction) throws 
CoreModelException;
 }
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreParentNode.java 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreParentNode.java
index 2792de09e..2d1f2338b 100644
--- a/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreParentNode.java
+++ b/mixins/core-mixins/src/main/java/org/apache/axiom/core/CoreParentNode.java
@@ -18,10 +18,11 @@
  */
 package org.apache.axiom.core;
 
+import org.apache.axiom.checker.union.Union;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.StreamException;
 import org.apache.axiom.core.stream.XmlHandler;
 import org.apache.axiom.core.stream.XmlReader;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
 
 /** Interface for parent nodes. */
 public interface CoreParentNode extends CoreNode {
@@ -102,7 +103,8 @@ public interface CoreParentNode extends CoreNode {
             Mapper<S, ? super T> mapper,
             Semantics semantics);
 
-    void coreSetCharacterData(@StringOrCharacterData Object data, Semantics 
semantics) throws CoreModelException;
+    void coreSetCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data, Semantics semantics)
+            throws CoreModelException;
 
     void coreRemoveChildren(Semantics semantics) throws CoreModelException;
 
@@ -118,6 +120,6 @@ public interface CoreParentNode extends CoreNode {
 
     Content internalGetContent(boolean create);
 
-    @StringOrCharacterData
+    @Union(types = {String.class, CharacterData.class})
     Object internalGetCharacterData(ElementAction elementAction) throws 
CoreModelException;
 }
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/AbstractNodeIterator.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/AbstractNodeIterator.java
index 943b20aa1..95eda5b2f 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/AbstractNodeIterator.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/AbstractNodeIterator.java
@@ -136,7 +136,7 @@ public abstract class AbstractNodeIterator<T extends 
CoreNode, S> implements Nod
     }
 
     @Override
-    @SuppressWarnings("stringorcharacterdata")
+    @SuppressWarnings("union")
     public final S next() {
         if (hasNext()) {
             currentNode = nextNode;
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/TreeWalkerImpl.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/TreeWalkerImpl.java
index cfebe91d4..848c72be9 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/TreeWalkerImpl.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/TreeWalkerImpl.java
@@ -15,6 +15,7 @@
  */
 package org.apache.axiom.core.impl;
 
+import org.apache.axiom.checker.union.Union;
 import org.apache.axiom.core.CoreAttribute;
 import org.apache.axiom.core.CoreChildNode;
 import org.apache.axiom.core.CoreDocument;
@@ -27,6 +28,7 @@ import org.apache.axiom.core.CoreNode;
 import org.apache.axiom.core.CoreParentNode;
 import org.apache.axiom.core.InputContext;
 import org.apache.axiom.core.NodeConsumedException;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.DocumentElementExtractingFilterHandler;
 import org.apache.axiom.core.stream.StreamException;
 import org.apache.axiom.core.stream.XmlHandler;
@@ -258,10 +260,10 @@ public final class TreeWalkerImpl implements XmlReader {
                     }
                 }
                 case STATE_CONTENT_VISITED -> {
-                    @SuppressWarnings("stringorcharacterdata")
-                    
@org.apache.axiom.core.stream.annotations.StringOrCharacterData
-                    Object data = ((CoreParentNode) 
nextNode).internalGetContent();
-                    handler.processCharacterData(data, false);
+                    handler.processCharacterData(
+                            (@Union(types = {String.class, 
CharacterData.class}) Object)
+                                    ((CoreParentNode) 
nextNode).internalGetContent(),
+                            false);
                 }
                 default -> throw new IllegalStateException();
             }
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/BuildableContext.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/BuildableContext.java
index d460fe3b0..e5c476846 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/BuildableContext.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/BuildableContext.java
@@ -18,6 +18,7 @@
  */
 package org.apache.axiom.core.impl.builder;
 
+import org.apache.axiom.checker.union.Union;
 import org.apache.axiom.core.Builder;
 import org.apache.axiom.core.CoreCDATASection;
 import org.apache.axiom.core.CoreCharacterDataNode;
@@ -40,14 +41,13 @@ import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.NullXmlHandler;
 import org.apache.axiom.core.stream.StreamException;
 import org.apache.axiom.core.stream.XmlHandler;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
 
 final class BuildableContext extends Context implements InputContext {
     private final Context parentContext;
 
     private CoreParentNode target;
 
-    private @StringOrCharacterData Object pendingCharacterData;
+    private @Union(types = {String.class, CharacterData.class}) Object 
pendingCharacterData;
 
     /**
      * The {@link XmlHandler} object to send events to if pass-through is 
enabled. See {@link
@@ -261,7 +261,8 @@ final class BuildableContext extends Context implements 
InputContext {
     }
 
     @Override
-    void processCharacterData(@StringOrCharacterData Object data, boolean 
ignorable) throws StreamException {
+    void processCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data, boolean ignorable)
+            throws StreamException {
         if (passThroughHandler != null) {
             passThroughHandler.processCharacterData(data, ignorable);
         } else if (!ignorable && pendingCharacterData == null && 
target.coreGetFirstChildIfAvailable() == null) {
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/BuilderHandler.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/BuilderHandler.java
index 0eacd7772..75d949cd8 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/BuilderHandler.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/BuilderHandler.java
@@ -21,15 +21,16 @@ package org.apache.axiom.core.impl.builder;
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.Queue;
+import org.apache.axiom.checker.union.Union;
 import org.apache.axiom.core.Builder;
 import org.apache.axiom.core.CoreDocument;
 import org.apache.axiom.core.CoreNSAwareElement;
 import org.apache.axiom.core.CoreNode;
 import org.apache.axiom.core.DeferredParsingException;
 import org.apache.axiom.core.NodeFactory;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.StreamException;
 import org.apache.axiom.core.stream.XmlHandler;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
 
 final class BuilderHandler implements XmlHandler {
     final NodeFactory nodeFactory;
@@ -177,7 +178,8 @@ final class BuilderHandler implements XmlHandler {
     }
 
     @Override
-    public void processCharacterData(@StringOrCharacterData Object data, 
boolean ignorable) throws StreamException {
+    public void processCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data, boolean ignorable)
+            throws StreamException {
         context.processCharacterData(data, ignorable);
     }
 
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/Context.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/Context.java
index 9efe7fe71..26e943676 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/Context.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/Context.java
@@ -18,9 +18,10 @@
  */
 package org.apache.axiom.core.impl.builder;
 
+import org.apache.axiom.checker.union.Union;
 import org.apache.axiom.core.CoreParentNode;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.StreamException;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
 
 abstract class Context {
     protected final BuilderHandler builderHandler;
@@ -63,7 +64,8 @@ abstract class Context {
 
     abstract void attributesCompleted() throws StreamException;
 
-    abstract void processCharacterData(@StringOrCharacterData Object data, 
boolean ignorable) throws StreamException;
+    abstract void processCharacterData(
+            @Union(types = {String.class, CharacterData.class}) Object data, 
boolean ignorable) throws StreamException;
 
     abstract Context startProcessingInstruction(String piTarget) throws 
StreamException;
 
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/UnwrappingContext.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/UnwrappingContext.java
index 7d163dd03..56fd708f7 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/UnwrappingContext.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/builder/UnwrappingContext.java
@@ -18,10 +18,11 @@
  */
 package org.apache.axiom.core.impl.builder;
 
+import org.apache.axiom.checker.union.Union;
 import org.apache.axiom.core.CoreNSAwareElement;
 import org.apache.axiom.core.CoreParentNode;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.StreamException;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
 
 final class UnwrappingContext extends Context {
     private final CoreNSAwareElement root;
@@ -88,7 +89,8 @@ final class UnwrappingContext extends Context {
     }
 
     @Override
-    void processCharacterData(@StringOrCharacterData Object data, boolean 
ignorable) throws StreamException {
+    void processCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data, boolean ignorable)
+            throws StreamException {
         // TODO Auto-generated method stub
 
     }
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreCharacterDataContainingParentNodeMixin.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreCharacterDataContainingParentNodeMixin.java
index a7796a8bf..78f0b8691 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreCharacterDataContainingParentNodeMixin.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreCharacterDataContainingParentNodeMixin.java
@@ -18,17 +18,20 @@
  */
 package org.apache.axiom.core.impl.mixin;
 
+import org.apache.axiom.checker.union.Union;
 import org.apache.axiom.core.CoreCharacterDataContainingParentNode;
 import org.apache.axiom.core.CoreModelException;
 import org.apache.axiom.core.ElementAction;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.weaver.annotation.Mixin;
 
 @Mixin
 public abstract class CoreCharacterDataContainingParentNodeMixin implements 
CoreCharacterDataContainingParentNode {
     @Override
-    public final @StringOrCharacterData Object coreGetCharacterData() throws 
CoreModelException {
-        @StringOrCharacterData Object characterData = 
internalGetCharacterData(ElementAction.RETURN_NULL);
+    public final @Union(types = {String.class, CharacterData.class}) Object 
coreGetCharacterData()
+            throws CoreModelException {
+        @Union(types = {String.class, CharacterData.class})
+        Object characterData = 
internalGetCharacterData(ElementAction.RETURN_NULL);
         if (characterData == null) {
             throw new IllegalStateException();
         }
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreCharacterDataNodeMixin.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreCharacterDataNodeMixin.java
index f0729e82c..43f64e5dc 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreCharacterDataNodeMixin.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreCharacterDataNodeMixin.java
@@ -18,6 +18,7 @@
  */
 package org.apache.axiom.core.impl.mixin;
 
+import org.apache.axiom.checker.union.Union;
 import org.apache.axiom.core.ClonePolicy;
 import org.apache.axiom.core.CloneableCharacterData;
 import org.apache.axiom.core.CoreCharacterDataNode;
@@ -28,13 +29,12 @@ import org.apache.axiom.core.impl.Flags;
 import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.StreamException;
 import org.apache.axiom.core.stream.XmlHandler;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
 import org.apache.axiom.weaver.annotation.Mixin;
 
 @Mixin
 public abstract class CoreCharacterDataNodeMixin implements 
CoreCharacterDataNode {
     /** Either a {@link String} or a {@link CharacterData} object. */
-    private @StringOrCharacterData Object data;
+    private @Union(types = {String.class, CharacterData.class}) Object data;
 
     @Override
     public final NodeType coreGetNodeType() {
@@ -52,24 +52,26 @@ public abstract class CoreCharacterDataNodeMixin implements 
CoreCharacterDataNod
     }
 
     @Override
-    public final @StringOrCharacterData Object coreGetCharacterData() {
+    public final @Union(types = {String.class, CharacterData.class}) Object 
coreGetCharacterData() {
         return data == null ? "" : data;
     }
 
     @Override
-    public final void coreSetCharacterData(@StringOrCharacterData Object data) 
{
+    public final void coreSetCharacterData(@Union(types = {String.class, 
CharacterData.class}) Object data) {
         this.data = data;
     }
 
     @Override
-    public final void coreSetCharacterData(@StringOrCharacterData Object data, 
Semantics semantics) {
+    public final void coreSetCharacterData(
+            @Union(types = {String.class, CharacterData.class}) Object data, 
Semantics semantics) {
         this.data = data;
     }
 
     @Override
     public final <T> void init(ClonePolicy<T> policy, T options, CoreNode 
other) {
         CoreCharacterDataNode o = (CoreCharacterDataNode) other;
-        @StringOrCharacterData Object otherData = o.coreGetCharacterData();
+        @Union(types = {String.class, CharacterData.class})
+        Object otherData = o.coreGetCharacterData();
         data = otherData instanceof CloneableCharacterData 
cloneableCharacterData
                 ? cloneableCharacterData.clone(policy, options)
                 : otherData;
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreMixedContentContainerMixin.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreMixedContentContainerMixin.java
index e3ebd9354..5d71a6be9 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreMixedContentContainerMixin.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreMixedContentContainerMixin.java
@@ -18,17 +18,18 @@
  */
 package org.apache.axiom.core.impl.mixin;
 
+import org.apache.axiom.checker.union.Union;
 import org.apache.axiom.core.CoreMixedContentContainer;
 import org.apache.axiom.core.CoreModelException;
 import org.apache.axiom.core.ElementAction;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.weaver.annotation.Mixin;
 
 @Mixin
 public abstract class CoreMixedContentContainerMixin implements 
CoreMixedContentContainer {
     @Override
-    public final @StringOrCharacterData Object 
coreGetCharacterData(ElementAction elementAction)
-            throws CoreModelException {
+    public final @Union(types = {String.class, CharacterData.class}) Object 
coreGetCharacterData(
+            ElementAction elementAction) throws CoreModelException {
         return internalGetCharacterData(elementAction);
     }
 }
diff --git 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreParentNodeMixin.java
 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreParentNodeMixin.java
index cff323f48..697bd526e 100644
--- 
a/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreParentNodeMixin.java
+++ 
b/mixins/core-mixins/src/main/java/org/apache/axiom/core/impl/mixin/CoreParentNodeMixin.java
@@ -18,6 +18,7 @@
  */
 package org.apache.axiom.core.impl.mixin;
 
+import org.apache.axiom.checker.union.Union;
 import org.apache.axiom.core.Axis;
 import org.apache.axiom.core.Builder;
 import org.apache.axiom.core.ChildNotAllowedException;
@@ -52,7 +53,6 @@ import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.StreamException;
 import org.apache.axiom.core.stream.XmlHandler;
 import org.apache.axiom.core.stream.XmlReader;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
 import org.apache.axiom.weaver.annotation.Mixin;
 
 @Mixin
@@ -124,10 +124,7 @@ public abstract class CoreParentNodeMixin implements 
CoreParentNode {
             Content content = new Content();
             CoreCharacterDataNode cdata = 
coreGetNodeFactory().createCharacterDataNode();
             cdata.internalSetParent(this);
-            @SuppressWarnings("stringorcharacterdata")
-            @StringOrCharacterData
-            Object characterData = this.content;
-            cdata.coreSetCharacterData(characterData);
+            cdata.coreSetCharacterData((@Union(types = {String.class, 
CharacterData.class}) Object) this.content);
             content.firstChild = cdata;
             content.lastChild = cdata;
             this.content = content;
@@ -346,15 +343,13 @@ public abstract class CoreParentNodeMixin implements 
CoreParentNode {
     }
 
     @Override
-    public final @StringOrCharacterData Object 
internalGetCharacterData(ElementAction elementAction)
-            throws CoreModelException {
+    public final @Union(types = {String.class, CharacterData.class}) Object 
internalGetCharacterData(
+            ElementAction elementAction) throws CoreModelException {
         if (getState() == COMPACT) {
-            @SuppressWarnings("stringorcharacterdata")
-            @StringOrCharacterData
-            Object result = content;
-            return result;
+            return (@Union(types = {String.class, CharacterData.class}) 
Object) content;
         } else {
-            @StringOrCharacterData Object textContent = null;
+            @Union(types = {String.class, CharacterData.class})
+            Object textContent = null;
             StringBuilder buffer = null;
             int depth = 0;
             CoreChildNode child = coreGetFirstChild();
@@ -379,7 +374,7 @@ public abstract class CoreParentNodeMixin implements 
CoreParentNode {
                     }
                 } else {
                     if (child instanceof CoreCharacterDataNode || child 
instanceof CoreCDATASection) {
-                        @StringOrCharacterData
+                        @Union(types = {String.class, CharacterData.class})
                         Object textValue = ((CoreCharacterDataContainer) 
child).coreGetCharacterData();
                         if (textValue instanceof CharacterData || ((String) 
textValue).length() != 0) {
                             if (textContent == null) {
@@ -420,7 +415,8 @@ public abstract class CoreParentNodeMixin implements 
CoreParentNode {
     }
 
     @Override
-    public final void coreSetCharacterData(@StringOrCharacterData Object data, 
Semantics semantics)
+    public final void coreSetCharacterData(
+            @Union(types = {String.class, CharacterData.class}) Object data, 
Semantics semantics)
             throws CoreModelException {
         coreRemoveChildren(semantics);
         if (data != null && (data instanceof CharacterData || ((String) 
data).length() > 0)) {
@@ -453,9 +449,8 @@ public abstract class CoreParentNodeMixin implements 
CoreParentNode {
         CoreParentNode targetParent = (CoreParentNode) clone;
         if (policy.cloneChildren(options, coreGetNodeType()) && 
targetParent.isExpanded()) {
             if (getState() == COMPACT) {
-                @SuppressWarnings("stringorcharacterdata")
-                @StringOrCharacterData
-                Object content = this.content;
+                @Union(types = {String.class, CharacterData.class})
+                Object content = (@Union(types = {String.class, 
CharacterData.class}) Object) this.content;
                 if (content instanceof CloneableCharacterData) {
                     content = ((CloneableCharacterData) content).clone(policy, 
options);
                 }
diff --git a/mixins/dom-mixins/pom.xml b/mixins/dom-mixins/pom.xml
index ae472efa6..7b76f524f 100644
--- a/mixins/dom-mixins/pom.xml
+++ b/mixins/dom-mixins/pom.xml
@@ -49,7 +49,7 @@
         </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
-            <artifactId>checker-annotations</artifactId>
+            <artifactId>union-annotations</artifactId>
             <version>${project.version}</version>
             <scope>provided</scope>
         </dependency>
diff --git a/mixins/om-mixins/pom.xml b/mixins/om-mixins/pom.xml
index d2cb26420..dee606859 100644
--- a/mixins/om-mixins/pom.xml
+++ b/mixins/om-mixins/pom.xml
@@ -50,7 +50,7 @@
         </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
-            <artifactId>checker-annotations</artifactId>
+            <artifactId>union-annotations</artifactId>
             <version>${project.version}</version>
             <scope>provided</scope>
         </dependency>
diff --git 
a/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/common/factory/OMFactoryImpl.java
 
b/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/common/factory/OMFactoryImpl.java
index 3a7f44eab..6936abcea 100644
--- 
a/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/common/factory/OMFactoryImpl.java
+++ 
b/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/common/factory/OMFactoryImpl.java
@@ -23,8 +23,9 @@ import static 
org.apache.axiom.util.xml.NSUtils.generatePrefix;
 import java.util.Iterator;
 import javax.xml.namespace.QName;
 import org.apache.axiom.blob.Blob;
+import org.apache.axiom.checker.union.Union;
 import org.apache.axiom.core.CoreModelException;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.ext.stax.BlobProvider;
 import org.apache.axiom.om.OMAttribute;
 import org.apache.axiom.om.OMComment;
@@ -100,7 +101,8 @@ public class OMFactoryImpl implements OMFactory {
         return node;
     }
 
-    private AxiomText createAxiomText(OMContainer parent, 
@StringOrCharacterData Object content, int type) {
+    private AxiomText createAxiomText(
+            OMContainer parent, @Union(types = {String.class, 
CharacterData.class}) Object content, int type) {
         AxiomText node;
         switch (type) {
             case OMNode.TEXT_NODE -> {
@@ -158,8 +160,8 @@ public class OMFactoryImpl implements OMFactory {
             throw new IllegalArgumentException("QName text arg cannot be 
null!");
         }
         OMNamespace ns = ((AxiomElement) 
parent).handleNamespace(text.getNamespaceURI(), text.getPrefix());
-        return createAxiomText(
-                parent, ns == null ? text.getLocalPart() : ns.getPrefix() + 
":" + text.getLocalPart(), type);
+        String content = ns == null ? text.getLocalPart() : ns.getPrefix() + 
":" + text.getLocalPart();
+        return createAxiomText(parent, content, type);
     }
 
     @Override
@@ -390,6 +392,7 @@ public class OMFactoryImpl implements OMFactory {
             }
             case OMNode.TEXT_NODE, OMNode.SPACE_NODE, 
OMNode.CDATA_SECTION_NODE -> {
                 OMText text = (OMText) child;
+                @Union(types = {String.class, CharacterData.class})
                 Object content;
                 if (text.isBinary()) {
                     content = new TextContent(text.getContentID(), 
text.getBlob(), text.isOptimized());
diff --git 
a/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/intf/TextContent.java 
b/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/intf/TextContent.java
index f16abb98b..b5910109c 100644
--- 
a/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/intf/TextContent.java
+++ 
b/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/intf/TextContent.java
@@ -21,12 +21,11 @@ package org.apache.axiom.om.impl.intf;
 import java.io.IOException;
 import org.apache.axiom.blob.Blob;
 import org.apache.axiom.blob.Blobs;
+import org.apache.axiom.checker.union.Union;
 import org.apache.axiom.core.ClonePolicy;
 import org.apache.axiom.core.CloneableCharacterData;
 import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.CharacterDataSink;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
-import org.apache.axiom.core.stream.annotations.UnknownCharacterDataType;
 import org.apache.axiom.ext.stax.BlobProvider;
 import org.apache.axiom.mime.PartBlob;
 import org.apache.axiom.om.OMCloneOptions;
@@ -137,8 +136,7 @@ public final class TextContent implements 
CloneableCharacterData {
     }
 
     @Override
-    @SuppressWarnings("method.invocation")
-    public String toString(@UnknownCharacterDataType TextContent this) {
+    public String toString() {
         if (blobObject != null) {
             try {
                 return Base64Utils.encode(getBlob());
@@ -204,7 +202,7 @@ public final class TextContent implements 
CloneableCharacterData {
     }
 
     @Override
-    public @StringOrCharacterData Object retain() {
+    public @Union(types = {String.class, CharacterData.class}) Object retain() 
{
         return this;
     }
 }
diff --git 
a/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/stream/ds/PushOMDataSourceXOPHandler.java
 
b/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/stream/ds/PushOMDataSourceXOPHandler.java
index 873ef836a..038c54407 100644
--- 
a/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/stream/ds/PushOMDataSourceXOPHandler.java
+++ 
b/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/stream/ds/PushOMDataSourceXOPHandler.java
@@ -21,9 +21,10 @@ package org.apache.axiom.om.impl.stream.ds;
 import java.util.HashMap;
 import java.util.Map;
 import org.apache.axiom.blob.Blob;
+import org.apache.axiom.checker.union.Union;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.StreamException;
 import org.apache.axiom.core.stream.XmlHandler;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
 import org.apache.axiom.core.stream.xop.AbstractXOPDecodingFilterHandler;
 import org.apache.axiom.om.impl.intf.TextContent;
 import org.apache.axiom.om.impl.stream.xop.XOPHandler;
@@ -44,7 +45,8 @@ final class PushOMDataSourceXOPHandler extends 
AbstractXOPDecodingFilterHandler
     }
 
     @Override
-    protected @StringOrCharacterData Object buildCharacterData(String 
contentID) throws StreamException {
+    protected @Union(types = {String.class, CharacterData.class}) Object 
buildCharacterData(String contentID)
+            throws StreamException {
         Blob blob = blobs.get(contentID);
         if (blob == null) {
             throw new StreamException("No Blob found for content ID " + 
contentID);
diff --git 
a/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/stream/xop/XOPDecodingFilterHandler.java
 
b/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/stream/xop/XOPDecodingFilterHandler.java
index 59f706aab..189d3a0d2 100644
--- 
a/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/stream/xop/XOPDecodingFilterHandler.java
+++ 
b/mixins/om-mixins/src/main/java/org/apache/axiom/om/impl/stream/xop/XOPDecodingFilterHandler.java
@@ -20,8 +20,9 @@ package org.apache.axiom.om.impl.stream.xop;
 
 import java.io.IOException;
 import org.apache.axiom.blob.Blob;
+import org.apache.axiom.checker.union.Union;
+import org.apache.axiom.core.stream.CharacterData;
 import org.apache.axiom.core.stream.XmlHandler;
-import org.apache.axiom.core.stream.annotations.StringOrCharacterData;
 import org.apache.axiom.core.stream.xop.AbstractXOPDecodingFilterHandler;
 import org.apache.axiom.ext.stax.BlobProvider;
 import org.apache.axiom.om.OMAttachmentAccessor;
@@ -57,7 +58,7 @@ final class XOPDecodingFilterHandler extends 
AbstractXOPDecodingFilterHandler {
     }
 
     @Override
-    protected @StringOrCharacterData Object buildCharacterData(String 
contentID) {
+    protected @Union(types = {String.class, CharacterData.class}) Object 
buildCharacterData(String contentID) {
         return new TextContent(contentID, new 
BlobProviderImpl(attachmentAccessor, contentID), true);
     }
 }
diff --git a/mixins/pom.xml b/mixins/pom.xml
index 10f48bb92..20869b515 100644
--- a/mixins/pom.xml
+++ b/mixins/pom.xml
@@ -46,7 +46,7 @@
                     <annotationProcessorPaths combine.children="append">
                         <annotationProcessorPath>
                             <groupId>${project.groupId}</groupId>
-                            
<artifactId>stringorcharacterdata-checker</artifactId>
+                            <artifactId>union-checker</artifactId>
                             <version>${project.version}</version>
                         </annotationProcessorPath>
                     </annotationProcessorPaths>


Reply via email to