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

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new 9e47f7cfe feat(java): implement Union type support for cross-language 
serialization (#3062)
9e47f7cfe is described below

commit 9e47f7cfe897f822983726b45c59e831e9aa9fbe
Author: zhan7236 <[email protected]>
AuthorDate: Mon Dec 22 21:29:30 2025 +0800

    feat(java): implement Union type support for cross-language serialization 
(#3062)
    
    ## Why?
    
    Java lacks native support for Union/Variant types that are common in
    other languages (C++ std::variant, Rust enum, Python typing.Union). This
    limits cross-language serialization interoperability when working with
    union types.
    
    ## What does this PR do?
    
    Implements Union type support in Java for cross-language serialization,
    addressing issue #3030.
    
    **Changes:**
    
    1. **Types.java**: Added `UNION` (38) and `NONE` (39) type constants
    following the xlang specification
    2. **Union.java**: A tagged union wrapper class that can hold one of
    several alternative types
    3. **UnionSerializer.java**: Handles serialization/deserialization with
    xlang protocol support
    4. **ClassResolver.java**: Registered UnionSerializer as default
    serializer
    5. **XtypeResolver.java**: Registered Union type with UNION type id for
    xlang serialization
    6. **xlang_type_mapping.md**: Updated type mapping documentation
    
    **Serialization format:**
    - Variant index (varuint32): identifies which alternative is active
    - Type info: type information for the active alternative
    - Value data: serialized value
    
    ## Related issues
    
    Closes #3030
    
    ## Does this PR introduce any user-facing change?
    
    Yes. Users can now use the `Union` class for cross-language union type
    serialization.
    
    - [x] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    Not applicable - this adds new functionality without modifying existing
    serialization paths.
---
 docs/specification/xlang_type_mapping.md           |   2 +-
 .../main/java/org/apache/fory/meta/ClassDef.java   |  36 ++
 .../org/apache/fory/resolver/XtypeResolver.java    |  26 +-
 .../apache/fory/serializer/UnionSerializer.java    | 116 ++++++
 .../src/main/java/org/apache/fory/type/Types.java  |   6 +
 .../java/org/apache/fory/type/union/Union.java     | 131 +++++++
 .../java/org/apache/fory/type/union/Union2.java    | 195 ++++++++++
 .../java/org/apache/fory/type/union/Union3.java    | 231 +++++++++++
 .../java/org/apache/fory/type/union/Union4.java    | 268 +++++++++++++
 .../java/org/apache/fory/type/union/Union5.java    | 310 +++++++++++++++
 .../java/org/apache/fory/type/union/Union6.java    | 356 +++++++++++++++++
 .../fory-core/native-image.properties              |   7 +
 .../fory/serializer/UnionSerializerTest.java       | 430 +++++++++++++++++++++
 13 files changed, 2112 insertions(+), 2 deletions(-)

diff --git a/docs/specification/xlang_type_mapping.md 
b/docs/specification/xlang_type_mapping.md
index 1661d0b77..58e45f166 100644
--- a/docs/specification/xlang_type_mapping.md
+++ b/docs/specification/xlang_type_mapping.md
@@ -66,7 +66,7 @@ Note:
 | float16_array           | 35           | float[]         | ndarray(float16)  
                | /               | `fory::float16_t[n]/vector<T>` | 
`[n]float16/[]T` | `Vec<fory::f16>`  |
 | float32_array           | 36           | float[]         | ndarray(float32)  
                | /               | `float[n]/vector<T>`           | 
`[n]float32/[]T` | `Vec<f32>`        |
 | float64_array           | 37           | double[]        | ndarray(float64)  
                | /               | `double[n]/vector<T>`          | 
`[n]float64/[]T` | `Vec<f64>`        |
-| union                   | 38           | /               | /                 
                | /               | `std::variant<Ts...>`          | /          
      | tagged union enum |
+| union                   | 38           | Union           | typing.Union      
                | /               | `std::variant<Ts...>`          | /          
      | tagged union enum |
 | none                    | 39           | null            | None              
                | null            | `std::monostate`               | nil        
      | `()`              |
 
 ## Type info(not implemented currently)
diff --git a/java/fory-core/src/main/java/org/apache/fory/meta/ClassDef.java 
b/java/fory-core/src/main/java/org/apache/fory/meta/ClassDef.java
index 4855a41a6..99c70efeb 100644
--- a/java/fory-core/src/main/java/org/apache/fory/meta/ClassDef.java
+++ b/java/fory-core/src/main/java/org/apache/fory/meta/ClassDef.java
@@ -754,6 +754,8 @@ public class ClassDef implements Serializable {
         case Types.ENUM:
         case Types.NAMED_ENUM:
           return new EnumFieldType(nullable, xtypeId);
+        case Types.UNION:
+          return new UnionFieldType(nullable, trackingRef);
         case Types.UNKNOWN:
           return new ObjectFieldType(xtypeId, false, nullable, trackingRef);
         default:
@@ -1159,6 +1161,40 @@ public class ClassDef implements Serializable {
     }
   }
 
+  /** Class for Union field type. Union types are always monomorphic and use 
declared type. */
+  public static class UnionFieldType extends FieldType {
+
+    public UnionFieldType(boolean nullable, boolean trackingRef) {
+      super(Types.UNION, true, nullable, trackingRef);
+    }
+
+    @Override
+    public TypeRef<?> toTypeToken(TypeResolver classResolver, TypeRef<?> 
declared) {
+      // Union types use the declared field type directly
+      if (declared != null) {
+        return declared;
+      }
+      // Fallback to base Union class if no declared type
+      return TypeRef.of(
+          org.apache.fory.type.union.Union.class, new TypeExtMeta(nullable, 
trackingRef));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return super.equals(o);
+    }
+
+    @Override
+    public int hashCode() {
+      return super.hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return "UnionFieldType{" + "nullable=" + nullable + ", trackingRef=" + 
trackingRef + '}';
+    }
+  }
+
   /** Build field type from generics, nested generics will be extracted too. */
   static FieldType buildFieldType(TypeResolver resolver, Field field) {
     Preconditions.checkNotNull(field);
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java 
b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
index fb1b797d2..94e1a2774 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
@@ -82,6 +82,7 @@ import org.apache.fory.serializer.SerializationUtils;
 import org.apache.fory.serializer.Serializer;
 import org.apache.fory.serializer.Serializers;
 import org.apache.fory.serializer.TimeSerializers;
+import org.apache.fory.serializer.UnionSerializer;
 import org.apache.fory.serializer.collection.CollectionLikeSerializer;
 import org.apache.fory.serializer.collection.CollectionSerializer;
 import 
org.apache.fory.serializer.collection.CollectionSerializers.ArrayListSerializer;
@@ -445,7 +446,8 @@ public class XtypeResolver extends TypeResolver {
       Serializer<?> s = classInfo.serializer;
       if (s instanceof TimeSerializers.TimeSerializer
           || s instanceof MapLikeSerializer
-          || s instanceof CollectionLikeSerializer) {
+          || s instanceof CollectionLikeSerializer
+          || s instanceof UnionSerializer) {
         return true;
       }
 
@@ -618,6 +620,7 @@ public class XtypeResolver extends TypeResolver {
     registerDefaultTypes(Types.SET, HashSet.class, LinkedHashSet.class, 
Set.class);
     registerDefaultTypes(Types.MAP, HashMap.class, LinkedHashMap.class, 
Map.class);
     registerDefaultTypes(Types.LOCAL_DATE, LocalDate.class);
+    registerUnionTypes();
   }
 
   private void registerDefaultTypes(int xtypeId, Class<?> defaultType, 
Class<?>... otherTypes) {
@@ -645,6 +648,27 @@ public class XtypeResolver extends TypeResolver {
     }
   }
 
+  private void registerUnionTypes() {
+    Class<?>[] unionClasses =
+        new Class<?>[] {
+          org.apache.fory.type.union.Union.class,
+          org.apache.fory.type.union.Union2.class,
+          org.apache.fory.type.union.Union3.class,
+          org.apache.fory.type.union.Union4.class,
+          org.apache.fory.type.union.Union5.class,
+          org.apache.fory.type.union.Union6.class
+        };
+    for (Class<?> cls : unionClasses) {
+      @SuppressWarnings("unchecked")
+      Class<? extends org.apache.fory.type.union.Union> unionCls =
+          (Class<? extends org.apache.fory.type.union.Union>) cls;
+      UnionSerializer serializer = new UnionSerializer(fory, unionCls);
+      ClassInfo classInfo = newClassInfo(cls, serializer, (short) Types.UNION);
+      classInfoMap.put(cls, classInfo);
+    }
+    xtypeIdToClassMap.put(Types.UNION, 
classInfoMap.get(org.apache.fory.type.union.Union.class));
+  }
+
   public ClassInfo writeClassInfo(MemoryBuffer buffer, Object obj) {
     ClassInfo classInfo = getClassInfo(obj.getClass(), classInfoCache);
     writeClassInfo(buffer, classInfo);
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/serializer/UnionSerializer.java 
b/java/fory-core/src/main/java/org/apache/fory/serializer/UnionSerializer.java
new file mode 100644
index 000000000..2d95fec7e
--- /dev/null
+++ 
b/java/fory-core/src/main/java/org/apache/fory/serializer/UnionSerializer.java
@@ -0,0 +1,116 @@
+/*
+ * 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.fory.serializer;
+
+import java.util.function.BiFunction;
+import org.apache.fory.Fory;
+import org.apache.fory.memory.MemoryBuffer;
+import org.apache.fory.type.union.Union;
+import org.apache.fory.type.union.Union2;
+import org.apache.fory.type.union.Union3;
+import org.apache.fory.type.union.Union4;
+import org.apache.fory.type.union.Union5;
+import org.apache.fory.type.union.Union6;
+
+/**
+ * Serializer for {@link Union} and its subclasses ({@link Union2}, {@link 
Union3}, {@link Union4},
+ * {@link Union5}, {@link Union6}).
+ *
+ * <p>The serialization format is:
+ *
+ * <ul>
+ *   <li>Variant index (varuint32): identifies which alternative type is active
+ *   <li>Value data: the serialized value of the active alternative
+ * </ul>
+ *
+ * <p>The Union type (Union, Union2, etc.) is determined by the declared field 
type during
+ * deserialization, not from the serialized data. This allows cross-language 
interoperability with
+ * union types in other languages like C++'s std::variant, Rust's enum, or 
Python's typing.Union.
+ */
+public class UnionSerializer extends Serializer<Union> {
+  /** Array of factories for creating Union instances by type tag. */
+  @SuppressWarnings("unchecked")
+  private static final BiFunction<Integer, Object, Union>[] FACTORIES =
+      new BiFunction[] {
+        (BiFunction<Integer, Object, Union>) Union::new,
+        (BiFunction<Integer, Object, Union>) (index, value) -> 
Union2.of(index, value),
+        (BiFunction<Integer, Object, Union>) (index, value) -> 
Union3.of(index, value),
+        (BiFunction<Integer, Object, Union>) (index, value) -> 
Union4.of(index, value),
+        (BiFunction<Integer, Object, Union>) (index, value) -> 
Union5.of(index, value),
+        (BiFunction<Integer, Object, Union>) (index, value) -> 
Union6.of(index, value)
+      };
+
+  private final BiFunction<Integer, Object, Union> factory;
+
+  @SuppressWarnings("unchecked")
+  public UnionSerializer(Fory fory, Class<? extends Union> cls) {
+    super(fory, (Class<Union>) cls);
+    this.factory = FACTORIES[getTypeIndex(cls)];
+  }
+
+  private static int getTypeIndex(Class<? extends Union> cls) {
+    if (cls == Union.class) {
+      return 0;
+    } else if (cls == Union2.class) {
+      return 1;
+    } else if (cls == Union3.class) {
+      return 2;
+    } else if (cls == Union4.class) {
+      return 3;
+    } else if (cls == Union5.class) {
+      return 4;
+    } else if (cls == Union6.class) {
+      return 5;
+    } else {
+      // Default to base Union for unknown subclasses
+      return 0;
+    }
+  }
+
+  @Override
+  public void xwrite(MemoryBuffer buffer, Union union) {
+    int index = union.getIndex();
+    buffer.writeVarUint32(index);
+
+    Object value = union.getValue();
+    if (value != null) {
+      fory.xwriteRef(buffer, value);
+    } else {
+      buffer.writeByte(Fory.NULL_FLAG);
+    }
+  }
+
+  @Override
+  public Union xread(MemoryBuffer buffer) {
+    int index = buffer.readVarUint32();
+    Object value = fory.xreadRef(buffer);
+    return factory.apply(index, value);
+  }
+
+  @Override
+  public Union copy(Union union) {
+    if (union == null) {
+      return null;
+    }
+    Object value = union.getValue();
+    Object copiedValue = value != null ? fory.copyObject(value) : null;
+    return factory.apply(union.getIndex(), copiedValue);
+  }
+}
diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Types.java 
b/java/fory-core/src/main/java/org/apache/fory/type/Types.java
index 051a2ea00..c93331946 100644
--- a/java/fory-core/src/main/java/org/apache/fory/type/Types.java
+++ b/java/fory-core/src/main/java/org/apache/fory/type/Types.java
@@ -159,6 +159,12 @@ public class Types {
   /** One dimensional float64 array. */
   public static final int FLOAT64_ARRAY = 37;
 
+  /** A tagged union type that can hold one of several alternative types. */
+  public static final int UNION = 38;
+
+  /** Represents an empty/unit value with no data (e.g., for empty union 
alternatives). */
+  public static final int NONE = 39;
+
   /** Bound value for range checks (types with id >= BOUND are not internal 
types). */
   public static final int BOUND = 64;
 
diff --git a/java/fory-core/src/main/java/org/apache/fory/type/union/Union.java 
b/java/fory-core/src/main/java/org/apache/fory/type/union/Union.java
new file mode 100644
index 000000000..e77214a2f
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/union/Union.java
@@ -0,0 +1,131 @@
+/*
+ * 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.fory.type.union;
+
+import java.util.Objects;
+
+/**
+ * A general union that holds a value and an index. This is the base class for 
all typed unions
+ * (Union2, Union3, etc.).
+ *
+ * <p>This class can be used directly when deserializing union data without 
knowing the specific
+ * union type, or when you need more than 6 alternative types.
+ *
+ * <p>For type-safe unions with up to 6 types, use {@link Union2}, {@link 
Union3}, {@link Union4},
+ * {@link Union5}, or {@link Union6} instead.
+ *
+ * <p>Usage example:
+ *
+ * <pre>{@code
+ * // When deserializing union data without type information
+ * Union union = new Union(0, "hello");
+ * Object value = union.getValue();
+ * int index = union.getIndex();
+ *
+ * // For unions with more than 6 types
+ * Union union = new Union(7, someValue);
+ * }</pre>
+ *
+ * @see Union2
+ * @see Union3
+ * @see Union4
+ * @see Union5
+ * @see Union6
+ */
+public class Union {
+  /** The index indicating which alternative is active. */
+  protected final int index;
+
+  /** The current value. */
+  protected final Object value;
+
+  /**
+   * Creates a new Union with the specified index and value.
+   *
+   * @param index the index of the active alternative
+   * @param value the value
+   */
+  public Union(int index, Object value) {
+    this.index = index;
+    this.value = value;
+  }
+
+  /**
+   * Gets the index of the active alternative.
+   *
+   * @return the index
+   */
+  public int getIndex() {
+    return index;
+  }
+
+  /**
+   * Gets the current value.
+   *
+   * @return the value
+   */
+  public Object getValue() {
+    return value;
+  }
+
+  /**
+   * Gets the current value cast to the specified type.
+   *
+   * @param <T> the expected type
+   * @param type the class of the expected type
+   * @return the current value cast to the specified type
+   * @throws ClassCastException if the value cannot be cast to the specified 
type
+   */
+  @SuppressWarnings("unchecked")
+  public <T> T getValue(Class<T> type) {
+    return (T) value;
+  }
+
+  /**
+   * Checks if this union currently holds a value.
+   *
+   * @return true if a value is set (not null), false otherwise
+   */
+  public boolean hasValue() {
+    return value != null;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof Union)) {
+      return false;
+    }
+    Union union = (Union) o;
+    return index == union.getIndex() && Objects.equals(value, 
union.getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(index, value);
+  }
+
+  @Override
+  public String toString() {
+    return "Union{index=" + index + ", value=" + value + "}";
+  }
+}
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/type/union/Union2.java 
b/java/fory-core/src/main/java/org/apache/fory/type/union/Union2.java
new file mode 100644
index 000000000..2d71bd71e
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/union/Union2.java
@@ -0,0 +1,195 @@
+/*
+ * 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.fory.type.union;
+
+/**
+ * A typed union that can hold one of two alternative types. The active 
alternative is identified by
+ * an index (0 or 1).
+ *
+ * <p>This class extends {@link Union} and provides a type-safe way to 
represent values that can be
+ * one of two types, similar to Rust's Result or Either types.
+ *
+ * <p>Usage example:
+ *
+ * <pre>{@code
+ * // Create a union holding a String (type T1, index 0)
+ * Union2<String, Long> union1 = Union2.ofT1("hello");
+ *
+ * // Create a union holding a Long (type T2, index 1)
+ * Union2<String, Long> union2 = Union2.ofT2(100L);
+ *
+ * // Get the value
+ * if (union1.isT1()) {
+ *     String value = union1.getT1();
+ * }
+ * }</pre>
+ *
+ * <p>For unions with more than 6 types, use the generic {@link Union} class 
instead.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @see Union
+ * @see Union3
+ * @see Union4
+ * @see Union5
+ * @see Union6
+ */
+public final class Union2<T1, T2> extends Union {
+
+  private Union2(int index, Object value) {
+    super(index, value);
+  }
+
+  /**
+   * Creates a Union2 holding a value of the first type T1.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param value the value of type T1
+   * @return a new Union2 instance
+   */
+  public static <T1, T2> Union2<T1, T2> ofT1(T1 value) {
+    Union2<T1, T2> union = new Union2<>(0, value);
+    assert union.getIndex() == 0 : "ofT1 should create union with index 0";
+    return union;
+  }
+
+  /**
+   * Creates a Union2 holding a value of the second type T2.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param value the value of type T2
+   * @return a new Union2 instance
+   */
+  public static <T1, T2> Union2<T1, T2> ofT2(T2 value) {
+    Union2<T1, T2> union = new Union2<>(1, value);
+    assert union.getIndex() == 1 : "ofT2 should create union with index 1";
+    return union;
+  }
+
+  /**
+   * Creates a Union2 from an index and value. Used primarily for 
deserialization.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param index the index (0 or 1)
+   * @param value the value
+   * @return a new Union2 instance
+   */
+  public static <T1, T2> Union2<T1, T2> of(int index, Object value) {
+    if (index < 0 || index > 1) {
+      throw new IllegalArgumentException("Index must be 0 or 1 for Union2, 
got: " + index);
+    }
+    return new Union2<>(index, value);
+  }
+
+  /**
+   * Gets the index of the active alternative.
+   *
+   * @return 0 if T1 is active, 1 if T2 is active
+   */
+  @Override
+  public int getIndex() {
+    return index;
+  }
+
+  /**
+   * Gets the value as the first type T1.
+   *
+   * @return the value cast to T1
+   * @throws ClassCastException if the active type is not T1
+   */
+  @SuppressWarnings("unchecked")
+  public T1 getT1() {
+    return (T1) value;
+  }
+
+  /**
+   * Gets the value as the second type T2.
+   *
+   * @return the value cast to T2
+   * @throws ClassCastException if the active type is not T2
+   */
+  @SuppressWarnings("unchecked")
+  public T2 getT2() {
+    return (T2) value;
+  }
+
+  /**
+   * Gets the raw value without type casting.
+   *
+   * @return the value
+   */
+  @Override
+  public Object getValue() {
+    return value;
+  }
+
+  /**
+   * Checks if the first type T1 is active.
+   *
+   * @return true if index is 0
+   */
+  public boolean isT1() {
+    return index == 0;
+  }
+
+  /**
+   * Checks if the second type T2 is active.
+   *
+   * @return true if index is 1
+   */
+  public boolean isT2() {
+    return index == 1;
+  }
+
+  /**
+   * Checks if this union currently holds a non-null value.
+   *
+   * @return true if a value is set (not null), false otherwise
+   */
+  @Override
+  public boolean hasValue() {
+    return value != null;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof Union)) {
+      return false;
+    }
+    Union union = (Union) o;
+    return index == union.getIndex() && java.util.Objects.equals(value, 
union.getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return java.util.Objects.hash(index, value);
+  }
+
+  @Override
+  public String toString() {
+    return "Union2{index=" + index + ", value=" + value + "}";
+  }
+}
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/type/union/Union3.java 
b/java/fory-core/src/main/java/org/apache/fory/type/union/Union3.java
new file mode 100644
index 000000000..ae83df509
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/union/Union3.java
@@ -0,0 +1,231 @@
+/*
+ * 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.fory.type.union;
+
+/**
+ * A typed union that can hold one of three alternative types. The active 
alternative is identified
+ * by an index (0, 1, or 2).
+ *
+ * <p>This class extends {@link Union} and provides a type-safe way to 
represent values that can be
+ * one of three types.
+ *
+ * <p>Usage example:
+ *
+ * <pre>{@code
+ * // Create a union holding an Integer (type T1, index 0)
+ * Union3<Integer, String, Double> union = Union3.ofT1(42);
+ *
+ * // Get the value
+ * if (union.isT1()) {
+ *     Integer value = union.getT1();
+ * }
+ * }</pre>
+ *
+ * <p>For unions with more than 6 types, use the generic {@link Union} class 
instead.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @see Union
+ * @see Union2
+ * @see Union4
+ * @see Union5
+ * @see Union6
+ */
+public final class Union3<T1, T2, T3> extends Union {
+
+  private Union3(int index, Object value) {
+    super(index, value);
+  }
+
+  /**
+   * Creates a Union3 holding a value of the first type T1.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param value the value of type T1
+   * @return a new Union3 instance
+   */
+  public static <T1, T2, T3> Union3<T1, T2, T3> ofT1(T1 value) {
+    Union3<T1, T2, T3> union = new Union3<>(0, value);
+    assert union.getIndex() == 0 : "ofT1 should create union with index 0";
+    return union;
+  }
+
+  /**
+   * Creates a Union3 holding a value of the second type T2.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param value the value of type T2
+   * @return a new Union3 instance
+   */
+  public static <T1, T2, T3> Union3<T1, T2, T3> ofT2(T2 value) {
+    Union3<T1, T2, T3> union = new Union3<>(1, value);
+    assert union.getIndex() == 1 : "ofT2 should create union with index 1";
+    return union;
+  }
+
+  /**
+   * Creates a Union3 holding a value of the third type T3.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param value the value of type T3
+   * @return a new Union3 instance
+   */
+  public static <T1, T2, T3> Union3<T1, T2, T3> ofT3(T3 value) {
+    Union3<T1, T2, T3> union = new Union3<>(2, value);
+    assert union.getIndex() == 2 : "ofT3 should create union with index 2";
+    return union;
+  }
+
+  /**
+   * Creates a Union3 from an index and value. Used primarily for 
deserialization.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param index the index (0, 1, or 2)
+   * @param value the value
+   * @return a new Union3 instance
+   */
+  public static <T1, T2, T3> Union3<T1, T2, T3> of(int index, Object value) {
+    if (index < 0 || index > 2) {
+      throw new IllegalArgumentException("Index must be 0, 1, or 2 for Union3, 
got: " + index);
+    }
+    return new Union3<>(index, value);
+  }
+
+  /**
+   * Gets the index of the active alternative.
+   *
+   * @return 0 if T1 is active, 1 if T2 is active, 2 if T3 is active
+   */
+  @Override
+  public int getIndex() {
+    return index;
+  }
+
+  /**
+   * Gets the value as the first type T1.
+   *
+   * @return the value cast to T1
+   * @throws ClassCastException if the active type is not T1
+   */
+  @SuppressWarnings("unchecked")
+  public T1 getT1() {
+    return (T1) value;
+  }
+
+  /**
+   * Gets the value as the second type T2.
+   *
+   * @return the value cast to T2
+   * @throws ClassCastException if the active type is not T2
+   */
+  @SuppressWarnings("unchecked")
+  public T2 getT2() {
+    return (T2) value;
+  }
+
+  /**
+   * Gets the value as the third type T3.
+   *
+   * @return the value cast to T3
+   * @throws ClassCastException if the active type is not T3
+   */
+  @SuppressWarnings("unchecked")
+  public T3 getT3() {
+    return (T3) value;
+  }
+
+  /**
+   * Gets the raw value without type casting.
+   *
+   * @return the value
+   */
+  @Override
+  public Object getValue() {
+    return value;
+  }
+
+  /**
+   * Checks if the first type T1 is active.
+   *
+   * @return true if index is 0
+   */
+  public boolean isT1() {
+    return index == 0;
+  }
+
+  /**
+   * Checks if the second type T2 is active.
+   *
+   * @return true if index is 1
+   */
+  public boolean isT2() {
+    return index == 1;
+  }
+
+  /**
+   * Checks if the third type T3 is active.
+   *
+   * @return true if index is 2
+   */
+  public boolean isT3() {
+    return index == 2;
+  }
+
+  /**
+   * Checks if this union currently holds a non-null value.
+   *
+   * @return true if a value is set (not null), false otherwise
+   */
+  @Override
+  public boolean hasValue() {
+    return value != null;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof Union)) {
+      return false;
+    }
+    Union union = (Union) o;
+    return index == union.getIndex() && java.util.Objects.equals(value, 
union.getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return java.util.Objects.hash(index, value);
+  }
+
+  @Override
+  public String toString() {
+    return "Union3{index=" + index + ", value=" + value + "}";
+  }
+}
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/type/union/Union4.java 
b/java/fory-core/src/main/java/org/apache/fory/type/union/Union4.java
new file mode 100644
index 000000000..0d96b8365
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/union/Union4.java
@@ -0,0 +1,268 @@
+/*
+ * 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.fory.type.union;
+
+/**
+ * A typed union that can hold one of four alternative types. The active 
alternative is identified
+ * by an index (0, 1, 2, or 3).
+ *
+ * <p>This class extends {@link Union} and provides a type-safe way to 
represent values that can be
+ * one of four types.
+ *
+ * <p>Usage example:
+ *
+ * <pre>{@code
+ * // Create a union holding a String (type T2, index 1)
+ * Union4<Integer, String, Double, Boolean> union = Union4.ofT2("hello");
+ *
+ * // Get the value
+ * if (union.isT2()) {
+ *     String value = union.getT2();
+ * }
+ * }</pre>
+ *
+ * <p>For unions with more than 6 types, use the generic {@link Union} class 
instead.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @see Union
+ * @see Union2
+ * @see Union3
+ * @see Union5
+ * @see Union6
+ */
+public final class Union4<T1, T2, T3, T4> extends Union {
+
+  private Union4(int index, Object value) {
+    super(index, value);
+  }
+
+  /**
+   * Creates a Union4 holding a value of the first type T1.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param value the value of type T1
+   * @return a new Union4 instance
+   */
+  public static <T1, T2, T3, T4> Union4<T1, T2, T3, T4> ofT1(T1 value) {
+    Union4<T1, T2, T3, T4> union = new Union4<>(0, value);
+    assert union.getIndex() == 0 : "ofT1 should create union with index 0";
+    return union;
+  }
+
+  /**
+   * Creates a Union4 holding a value of the second type T2.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param value the value of type T2
+   * @return a new Union4 instance
+   */
+  public static <T1, T2, T3, T4> Union4<T1, T2, T3, T4> ofT2(T2 value) {
+    Union4<T1, T2, T3, T4> union = new Union4<>(1, value);
+    assert union.getIndex() == 1 : "ofT2 should create union with index 1";
+    return union;
+  }
+
+  /**
+   * Creates a Union4 holding a value of the third type T3.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param value the value of type T3
+   * @return a new Union4 instance
+   */
+  public static <T1, T2, T3, T4> Union4<T1, T2, T3, T4> ofT3(T3 value) {
+    Union4<T1, T2, T3, T4> union = new Union4<>(2, value);
+    assert union.getIndex() == 2 : "ofT3 should create union with index 2";
+    return union;
+  }
+
+  /**
+   * Creates a Union4 holding a value of the fourth type T4.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param value the value of type T4
+   * @return a new Union4 instance
+   */
+  public static <T1, T2, T3, T4> Union4<T1, T2, T3, T4> ofT4(T4 value) {
+    Union4<T1, T2, T3, T4> union = new Union4<>(3, value);
+    assert union.getIndex() == 3 : "ofT4 should create union with index 3";
+    return union;
+  }
+
+  /**
+   * Creates a Union4 from an index and value. Used primarily for 
deserialization.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param index the index (0, 1, 2, or 3)
+   * @param value the value
+   * @return a new Union4 instance
+   */
+  public static <T1, T2, T3, T4> Union4<T1, T2, T3, T4> of(int index, Object 
value) {
+    if (index < 0 || index > 3) {
+      throw new IllegalArgumentException("Index must be 0-3 for Union4, got: " 
+ index);
+    }
+    return new Union4<>(index, value);
+  }
+
+  /**
+   * Gets the index of the active alternative.
+   *
+   * @return 0-3 indicating which type is active
+   */
+  @Override
+  public int getIndex() {
+    return index;
+  }
+
+  /**
+   * Gets the value as the first type T1.
+   *
+   * @return the value cast to T1
+   */
+  @SuppressWarnings("unchecked")
+  public T1 getT1() {
+    return (T1) value;
+  }
+
+  /**
+   * Gets the value as the second type T2.
+   *
+   * @return the value cast to T2
+   */
+  @SuppressWarnings("unchecked")
+  public T2 getT2() {
+    return (T2) value;
+  }
+
+  /**
+   * Gets the value as the third type T3.
+   *
+   * @return the value cast to T3
+   */
+  @SuppressWarnings("unchecked")
+  public T3 getT3() {
+    return (T3) value;
+  }
+
+  /**
+   * Gets the value as the fourth type T4.
+   *
+   * @return the value cast to T4
+   */
+  @SuppressWarnings("unchecked")
+  public T4 getT4() {
+    return (T4) value;
+  }
+
+  /**
+   * Gets the raw value without type casting.
+   *
+   * @return the value
+   */
+  @Override
+  public Object getValue() {
+    return value;
+  }
+
+  /**
+   * Checks if the first type T1 is active.
+   *
+   * @return true if index is 0
+   */
+  public boolean isT1() {
+    return index == 0;
+  }
+
+  /**
+   * Checks if the second type T2 is active.
+   *
+   * @return true if index is 1
+   */
+  public boolean isT2() {
+    return index == 1;
+  }
+
+  /**
+   * Checks if the third type T3 is active.
+   *
+   * @return true if index is 2
+   */
+  public boolean isT3() {
+    return index == 2;
+  }
+
+  /**
+   * Checks if the fourth type T4 is active.
+   *
+   * @return true if index is 3
+   */
+  public boolean isT4() {
+    return index == 3;
+  }
+
+  /**
+   * Checks if this union currently holds a non-null value.
+   *
+   * @return true if a value is set (not null), false otherwise
+   */
+  @Override
+  public boolean hasValue() {
+    return value != null;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof Union)) {
+      return false;
+    }
+    Union union = (Union) o;
+    return index == union.getIndex() && java.util.Objects.equals(value, 
union.getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return java.util.Objects.hash(index, value);
+  }
+
+  @Override
+  public String toString() {
+    return "Union4{index=" + index + ", value=" + value + "}";
+  }
+}
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/type/union/Union5.java 
b/java/fory-core/src/main/java/org/apache/fory/type/union/Union5.java
new file mode 100644
index 000000000..9770668c0
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/union/Union5.java
@@ -0,0 +1,310 @@
+/*
+ * 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.fory.type.union;
+
+/**
+ * A typed union that can hold one of five alternative types. The active 
alternative is identified
+ * by an index (0-4).
+ *
+ * <p>This class extends {@link Union} and provides a type-safe way to 
represent values that can be
+ * one of five types.
+ *
+ * <p>Usage example:
+ *
+ * <pre>{@code
+ * // Create a union holding a String (type T2, index 1)
+ * Union5<Integer, String, Double, Boolean, Long> union = Union5.ofT2("hello");
+ *
+ * // Get the value
+ * if (union.isT2()) {
+ *     String value = union.getT2();
+ * }
+ * }</pre>
+ *
+ * <p>For unions with more than 6 types, use the generic {@link Union} class 
instead.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @see Union
+ * @see Union2
+ * @see Union3
+ * @see Union4
+ * @see Union6
+ */
+public final class Union5<T1, T2, T3, T4, T5> extends Union {
+
+  private Union5(int index, Object value) {
+    super(index, value);
+  }
+
+  /**
+   * Creates a Union5 holding a value of the first type T1.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param value the value of type T1
+   * @return a new Union5 instance
+   */
+  public static <T1, T2, T3, T4, T5> Union5<T1, T2, T3, T4, T5> ofT1(T1 value) 
{
+    Union5<T1, T2, T3, T4, T5> union = new Union5<>(0, value);
+    assert union.getIndex() == 0 : "ofT1 should create union with index 0";
+    return union;
+  }
+
+  /**
+   * Creates a Union5 holding a value of the second type T2.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param value the value of type T2
+   * @return a new Union5 instance
+   */
+  public static <T1, T2, T3, T4, T5> Union5<T1, T2, T3, T4, T5> ofT2(T2 value) 
{
+    Union5<T1, T2, T3, T4, T5> union = new Union5<>(1, value);
+    assert union.getIndex() == 1 : "ofT2 should create union with index 1";
+    return union;
+  }
+
+  /**
+   * Creates a Union5 holding a value of the third type T3.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param value the value of type T3
+   * @return a new Union5 instance
+   */
+  public static <T1, T2, T3, T4, T5> Union5<T1, T2, T3, T4, T5> ofT3(T3 value) 
{
+    Union5<T1, T2, T3, T4, T5> union = new Union5<>(2, value);
+    assert union.getIndex() == 2 : "ofT3 should create union with index 2";
+    return union;
+  }
+
+  /**
+   * Creates a Union5 holding a value of the fourth type T4.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param value the value of type T4
+   * @return a new Union5 instance
+   */
+  public static <T1, T2, T3, T4, T5> Union5<T1, T2, T3, T4, T5> ofT4(T4 value) 
{
+    Union5<T1, T2, T3, T4, T5> union = new Union5<>(3, value);
+    assert union.getIndex() == 3 : "ofT4 should create union with index 3";
+    return union;
+  }
+
+  /**
+   * Creates a Union5 holding a value of the fifth type T5.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param value the value of type T5
+   * @return a new Union5 instance
+   */
+  public static <T1, T2, T3, T4, T5> Union5<T1, T2, T3, T4, T5> ofT5(T5 value) 
{
+    Union5<T1, T2, T3, T4, T5> union = new Union5<>(4, value);
+    assert union.getIndex() == 4 : "ofT5 should create union with index 4";
+    return union;
+  }
+
+  /**
+   * Creates a Union5 from an index and value. Used primarily for 
deserialization.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param index the index (0-4)
+   * @param value the value
+   * @return a new Union5 instance
+   */
+  public static <T1, T2, T3, T4, T5> Union5<T1, T2, T3, T4, T5> of(int index, 
Object value) {
+    if (index < 0 || index > 4) {
+      throw new IllegalArgumentException("Index must be 0-4 for Union5, got: " 
+ index);
+    }
+    return new Union5<>(index, value);
+  }
+
+  /**
+   * Gets the index of the active alternative.
+   *
+   * @return 0-4 indicating which type is active
+   */
+  @Override
+  public int getIndex() {
+    return index;
+  }
+
+  /**
+   * Gets the value as the first type T1.
+   *
+   * @return the value cast to T1
+   */
+  @SuppressWarnings("unchecked")
+  public T1 getT1() {
+    return (T1) value;
+  }
+
+  /**
+   * Gets the value as the second type T2.
+   *
+   * @return the value cast to T2
+   */
+  @SuppressWarnings("unchecked")
+  public T2 getT2() {
+    return (T2) value;
+  }
+
+  /**
+   * Gets the value as the third type T3.
+   *
+   * @return the value cast to T3
+   */
+  @SuppressWarnings("unchecked")
+  public T3 getT3() {
+    return (T3) value;
+  }
+
+  /**
+   * Gets the value as the fourth type T4.
+   *
+   * @return the value cast to T4
+   */
+  @SuppressWarnings("unchecked")
+  public T4 getT4() {
+    return (T4) value;
+  }
+
+  /**
+   * Gets the value as the fifth type T5.
+   *
+   * @return the value cast to T5
+   */
+  @SuppressWarnings("unchecked")
+  public T5 getT5() {
+    return (T5) value;
+  }
+
+  /**
+   * Gets the raw value without type casting.
+   *
+   * @return the value
+   */
+  @Override
+  public Object getValue() {
+    return value;
+  }
+
+  /**
+   * Checks if the first type T1 is active.
+   *
+   * @return true if index is 0
+   */
+  public boolean isT1() {
+    return index == 0;
+  }
+
+  /**
+   * Checks if the second type T2 is active.
+   *
+   * @return true if index is 1
+   */
+  public boolean isT2() {
+    return index == 1;
+  }
+
+  /**
+   * Checks if the third type T3 is active.
+   *
+   * @return true if index is 2
+   */
+  public boolean isT3() {
+    return index == 2;
+  }
+
+  /**
+   * Checks if the fourth type T4 is active.
+   *
+   * @return true if index is 3
+   */
+  public boolean isT4() {
+    return index == 3;
+  }
+
+  /**
+   * Checks if the fifth type T5 is active.
+   *
+   * @return true if index is 4
+   */
+  public boolean isT5() {
+    return index == 4;
+  }
+
+  /**
+   * Checks if this union currently holds a non-null value.
+   *
+   * @return true if a value is set (not null), false otherwise
+   */
+  @Override
+  public boolean hasValue() {
+    return value != null;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof Union)) {
+      return false;
+    }
+    Union union = (Union) o;
+    return index == union.getIndex() && java.util.Objects.equals(value, 
union.getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return java.util.Objects.hash(index, value);
+  }
+
+  @Override
+  public String toString() {
+    return "Union5{index=" + index + ", value=" + value + "}";
+  }
+}
diff --git 
a/java/fory-core/src/main/java/org/apache/fory/type/union/Union6.java 
b/java/fory-core/src/main/java/org/apache/fory/type/union/Union6.java
new file mode 100644
index 000000000..2a9a5d783
--- /dev/null
+++ b/java/fory-core/src/main/java/org/apache/fory/type/union/Union6.java
@@ -0,0 +1,356 @@
+/*
+ * 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.fory.type.union;
+
+/**
+ * A typed union that can hold one of six alternative types. The active 
alternative is identified by
+ * an index (0-5).
+ *
+ * <p>This class extends {@link Union} and provides a type-safe way to 
represent values that can be
+ * one of six types. Six types should cover most use cases. For unions with 
more than 6 types, use
+ * the generic {@link Union} class instead.
+ *
+ * <p>Usage example:
+ *
+ * <pre>{@code
+ * // Create a union holding a String (type T2, index 1)
+ * Union6<Integer, String, Double, Boolean, Long, Float> union = 
Union6.ofT2("hello");
+ *
+ * // Get the value
+ * if (union.isT2()) {
+ *     String value = union.getT2();
+ * }
+ * }</pre>
+ *
+ * <p>For unions with more than 6 types, use the generic {@link Union} class 
instead.
+ *
+ * @param <T1> the first alternative type
+ * @param <T2> the second alternative type
+ * @param <T3> the third alternative type
+ * @param <T4> the fourth alternative type
+ * @param <T5> the fifth alternative type
+ * @param <T6> the sixth alternative type
+ * @see Union
+ * @see Union2
+ * @see Union3
+ * @see Union4
+ * @see Union5
+ */
+public final class Union6<T1, T2, T3, T4, T5, T6> extends Union {
+
+  private Union6(int index, Object value) {
+    super(index, value);
+  }
+
+  /**
+   * Creates a Union6 holding a value of the first type T1.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param <T6> the sixth alternative type
+   * @param value the value of type T1
+   * @return a new Union6 instance
+   */
+  public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6> 
ofT1(T1 value) {
+    Union6<T1, T2, T3, T4, T5, T6> union = new Union6<>(0, value);
+    assert union.getIndex() == 0 : "ofT1 should create union with index 0";
+    return union;
+  }
+
+  /**
+   * Creates a Union6 holding a value of the second type T2.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param <T6> the sixth alternative type
+   * @param value the value of type T2
+   * @return a new Union6 instance
+   */
+  public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6> 
ofT2(T2 value) {
+    Union6<T1, T2, T3, T4, T5, T6> union = new Union6<>(1, value);
+    assert union.getIndex() == 1 : "ofT2 should create union with index 1";
+    return union;
+  }
+
+  /**
+   * Creates a Union6 holding a value of the third type T3.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param <T6> the sixth alternative type
+   * @param value the value of type T3
+   * @return a new Union6 instance
+   */
+  public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6> 
ofT3(T3 value) {
+    Union6<T1, T2, T3, T4, T5, T6> union = new Union6<>(2, value);
+    assert union.getIndex() == 2 : "ofT3 should create union with index 2";
+    return union;
+  }
+
+  /**
+   * Creates a Union6 holding a value of the fourth type T4.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param <T6> the sixth alternative type
+   * @param value the value of type T4
+   * @return a new Union6 instance
+   */
+  public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6> 
ofT4(T4 value) {
+    Union6<T1, T2, T3, T4, T5, T6> union = new Union6<>(3, value);
+    assert union.getIndex() == 3 : "ofT4 should create union with index 3";
+    return union;
+  }
+
+  /**
+   * Creates a Union6 holding a value of the fifth type T5.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param <T6> the sixth alternative type
+   * @param value the value of type T5
+   * @return a new Union6 instance
+   */
+  public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6> 
ofT5(T5 value) {
+    Union6<T1, T2, T3, T4, T5, T6> union = new Union6<>(4, value);
+    assert union.getIndex() == 4 : "ofT5 should create union with index 4";
+    return union;
+  }
+
+  /**
+   * Creates a Union6 holding a value of the sixth type T6.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param <T6> the sixth alternative type
+   * @param value the value of type T6
+   * @return a new Union6 instance
+   */
+  public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6> 
ofT6(T6 value) {
+    Union6<T1, T2, T3, T4, T5, T6> union = new Union6<>(5, value);
+    assert union.getIndex() == 5 : "ofT6 should create union with index 5";
+    return union;
+  }
+
+  /**
+   * Creates a Union6 from an index and value. Used primarily for 
deserialization.
+   *
+   * @param <T1> the first alternative type
+   * @param <T2> the second alternative type
+   * @param <T3> the third alternative type
+   * @param <T4> the fourth alternative type
+   * @param <T5> the fifth alternative type
+   * @param <T6> the sixth alternative type
+   * @param index the index (0-5)
+   * @param value the value
+   * @return a new Union6 instance
+   */
+  public static <T1, T2, T3, T4, T5, T6> Union6<T1, T2, T3, T4, T5, T6> of(
+      int index, Object value) {
+    if (index < 0 || index > 5) {
+      throw new IllegalArgumentException("Index must be 0-5 for Union6, got: " 
+ index);
+    }
+    return new Union6<>(index, value);
+  }
+
+  /**
+   * Gets the index of the active alternative.
+   *
+   * @return 0-5 indicating which type is active
+   */
+  @Override
+  public int getIndex() {
+    return index;
+  }
+
+  /**
+   * Gets the value as the first type T1.
+   *
+   * @return the value cast to T1
+   */
+  @SuppressWarnings("unchecked")
+  public T1 getT1() {
+    return (T1) value;
+  }
+
+  /**
+   * Gets the value as the second type T2.
+   *
+   * @return the value cast to T2
+   */
+  @SuppressWarnings("unchecked")
+  public T2 getT2() {
+    return (T2) value;
+  }
+
+  /**
+   * Gets the value as the third type T3.
+   *
+   * @return the value cast to T3
+   */
+  @SuppressWarnings("unchecked")
+  public T3 getT3() {
+    return (T3) value;
+  }
+
+  /**
+   * Gets the value as the fourth type T4.
+   *
+   * @return the value cast to T4
+   */
+  @SuppressWarnings("unchecked")
+  public T4 getT4() {
+    return (T4) value;
+  }
+
+  /**
+   * Gets the value as the fifth type T5.
+   *
+   * @return the value cast to T5
+   */
+  @SuppressWarnings("unchecked")
+  public T5 getT5() {
+    return (T5) value;
+  }
+
+  /**
+   * Gets the value as the sixth type T6.
+   *
+   * @return the value cast to T6
+   */
+  @SuppressWarnings("unchecked")
+  public T6 getT6() {
+    return (T6) value;
+  }
+
+  /**
+   * Gets the raw value without type casting.
+   *
+   * @return the value
+   */
+  @Override
+  public Object getValue() {
+    return value;
+  }
+
+  /**
+   * Checks if the first type T1 is active.
+   *
+   * @return true if index is 0
+   */
+  public boolean isT1() {
+    return index == 0;
+  }
+
+  /**
+   * Checks if the second type T2 is active.
+   *
+   * @return true if index is 1
+   */
+  public boolean isT2() {
+    return index == 1;
+  }
+
+  /**
+   * Checks if the third type T3 is active.
+   *
+   * @return true if index is 2
+   */
+  public boolean isT3() {
+    return index == 2;
+  }
+
+  /**
+   * Checks if the fourth type T4 is active.
+   *
+   * @return true if index is 3
+   */
+  public boolean isT4() {
+    return index == 3;
+  }
+
+  /**
+   * Checks if the fifth type T5 is active.
+   *
+   * @return true if index is 4
+   */
+  public boolean isT5() {
+    return index == 4;
+  }
+
+  /**
+   * Checks if the sixth type T6 is active.
+   *
+   * @return true if index is 5
+   */
+  public boolean isT6() {
+    return index == 5;
+  }
+
+  /**
+   * Checks if this union currently holds a non-null value.
+   *
+   * @return true if a value is set (not null), false otherwise
+   */
+  @Override
+  public boolean hasValue() {
+    return value != null;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof Union)) {
+      return false;
+    }
+    Union union = (Union) o;
+    return index == union.getIndex() && java.util.Objects.equals(value, 
union.getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return java.util.Objects.hash(index, value);
+  }
+
+  @Override
+  public String toString() {
+    return "Union6{index=" + index + ", value=" + value + "}";
+  }
+}
diff --git 
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
 
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
index 27de52e84..09a295654 100644
--- 
a/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
+++ 
b/java/fory-core/src/main/resources/META-INF/native-image/org.apache.fory/fory-core/native-image.properties
@@ -385,6 +385,7 @@ 
Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\
     org.apache.fory.serializer.TimeSerializers$YearSerializer,\
     org.apache.fory.serializer.TimeSerializers$ZoneOffsetSerializer,\
     org.apache.fory.serializer.TimeSerializers$ZonedDateTimeSerializer,\
+    org.apache.fory.serializer.UnionSerializer,\
     org.apache.fory.serializer.collection.CollectionSerializer,\
     
org.apache.fory.serializer.collection.CollectionSerializers$ArrayBlockingQueueSerializer,\
     
org.apache.fory.serializer.collection.CollectionSerializers$ArrayDequeSerializer,\
@@ -487,6 +488,12 @@ 
Args=--initialize-at-build-time=org.apache.fory.memory.MemoryBuffer,\
     org.apache.fory.type.Type,\
     org.apache.fory.type.Types,\
     org.apache.fory.type.TypeUtils,\
+    org.apache.fory.type.union.Union,\
+    org.apache.fory.type.union.Union2,\
+    org.apache.fory.type.union.Union3,\
+    org.apache.fory.type.union.Union4,\
+    org.apache.fory.type.union.Union5,\
+    org.apache.fory.type.union.Union6,\
     org.apache.fory.util.DefaultValueUtils,\
     org.apache.fory.util.DefaultValueUtils$DefaultValueField,\
     org.apache.fory.util.DefaultValueUtils$ScalaDefaultValueSupport,\
diff --git 
a/java/fory-core/src/test/java/org/apache/fory/serializer/UnionSerializerTest.java
 
b/java/fory-core/src/test/java/org/apache/fory/serializer/UnionSerializerTest.java
new file mode 100644
index 000000000..ae5655c08
--- /dev/null
+++ 
b/java/fory-core/src/test/java/org/apache/fory/serializer/UnionSerializerTest.java
@@ -0,0 +1,430 @@
+/*
+ * 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.fory.serializer;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.fory.Fory;
+import org.apache.fory.ForyTestBase;
+import org.apache.fory.config.CompatibleMode;
+import org.apache.fory.config.Language;
+import org.apache.fory.type.union.Union;
+import org.apache.fory.type.union.Union2;
+import org.apache.fory.type.union.Union3;
+import org.apache.fory.type.union.Union4;
+import org.apache.fory.type.union.Union5;
+import org.apache.fory.type.union.Union6;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for {@link UnionSerializer}. Union serialization is only used for 
xlang mode. Union types
+ * are serialized as part of struct fields, where the declared field type 
determines which Union
+ * serializer is used.
+ */
+public class UnionSerializerTest extends ForyTestBase {
+
+  @DataProvider(name = "compatibleMode")
+  public static Object[][] compatibleModeProvider() {
+    return new Object[][] {{CompatibleMode.COMPATIBLE}, 
{CompatibleMode.SCHEMA_CONSISTENT}};
+  }
+
+  private Fory createXlangFory(CompatibleMode mode) {
+    Fory fory =
+        Fory.builder()
+            .withLanguage(Language.XLANG)
+            .withCompatibleMode(mode)
+            .requireClassRegistration(true)
+            .build();
+    fory.register(StructWithUnion.class);
+    fory.register(StructWithUnion2.class);
+    fory.register(StructWithUnion3.class);
+    fory.register(StructWithUnion4.class);
+    fory.register(StructWithUnion5.class);
+    fory.register(StructWithUnion6.class);
+    return fory;
+  }
+
+  // Test struct classes with Union fields
+  public static class StructWithUnion {
+    public Union union;
+
+    public StructWithUnion() {}
+
+    public StructWithUnion(Union union) {
+      this.union = union;
+    }
+  }
+
+  public static class StructWithUnion2 {
+    public Union2<String, Long> union;
+
+    public StructWithUnion2() {}
+
+    public StructWithUnion2(Union2<String, Long> union) {
+      this.union = union;
+    }
+  }
+
+  public static class StructWithUnion3 {
+    public Union3<Integer, String, Double> union;
+
+    public StructWithUnion3() {}
+
+    public StructWithUnion3(Union3<Integer, String, Double> union) {
+      this.union = union;
+    }
+  }
+
+  public static class StructWithUnion4 {
+    public Union4<Integer, String, Double, Boolean> union;
+
+    public StructWithUnion4() {}
+
+    public StructWithUnion4(Union4<Integer, String, Double, Boolean> union) {
+      this.union = union;
+    }
+  }
+
+  public static class StructWithUnion5 {
+    public Union5<Integer, String, Double, Boolean, Long> union;
+
+    public StructWithUnion5() {}
+
+    public StructWithUnion5(Union5<Integer, String, Double, Boolean, Long> 
union) {
+      this.union = union;
+    }
+  }
+
+  public static class StructWithUnion6 {
+    public Union6<Integer, String, Double, Boolean, Long, Float> union;
+
+    public StructWithUnion6() {}
+
+    public StructWithUnion6(Union6<Integer, String, Double, Boolean, Long, 
Float> union) {
+      this.union = union;
+    }
+  }
+
+  @Test(dataProvider = "compatibleMode")
+  public void testUnionBasicTypes(CompatibleMode mode) {
+    Fory fory = createXlangFory(mode);
+
+    // Test with Integer value via struct
+    StructWithUnion struct = new StructWithUnion(new Union(0, 42));
+    byte[] bytes = fory.serialize(struct);
+    StructWithUnion deserialized = (StructWithUnion) fory.deserialize(bytes);
+    assertEquals(deserialized.union.getValue(), 42);
+    assertEquals(deserialized.union.getIndex(), 0);
+
+    // Test with String value
+    struct = new StructWithUnion(new Union(1, "hello"));
+    bytes = fory.serialize(struct);
+    deserialized = (StructWithUnion) fory.deserialize(bytes);
+    assertEquals(deserialized.union.getValue(), "hello");
+    assertEquals(deserialized.union.getIndex(), 1);
+  }
+
+  @Test(dataProvider = "compatibleMode")
+  public void testUnion2Serialization(CompatibleMode mode) {
+    Fory fory = createXlangFory(mode);
+
+    // Test with T1 (String) via struct field
+    StructWithUnion2 struct = new StructWithUnion2(Union2.ofT1("hello"));
+    byte[] bytes = fory.serialize(struct);
+    StructWithUnion2 deserialized = (StructWithUnion2) fory.deserialize(bytes);
+    assertEquals(deserialized.union.getValue(), "hello");
+    assertEquals(deserialized.union.getIndex(), 0);
+    assertTrue(deserialized.union.isT1());
+    assertTrue(deserialized.union instanceof Union2);
+
+    // Test with T2 (Long)
+    struct = new StructWithUnion2(Union2.ofT2(100L));
+    bytes = fory.serialize(struct);
+    deserialized = (StructWithUnion2) fory.deserialize(bytes);
+    assertEquals(deserialized.union.getValue(), 100L);
+    assertEquals(deserialized.union.getIndex(), 1);
+    assertTrue(deserialized.union.isT2());
+  }
+
+  @Test(dataProvider = "compatibleMode")
+  public void testUnion3Serialization(CompatibleMode mode) {
+    Fory fory = createXlangFory(mode);
+
+    // Test with T1
+    StructWithUnion3 struct = new StructWithUnion3(Union3.ofT1(42));
+    byte[] bytes = fory.serialize(struct);
+    StructWithUnion3 deserialized = (StructWithUnion3) fory.deserialize(bytes);
+    assertEquals(deserialized.union.getValue(), 42);
+    assertEquals(deserialized.union.getIndex(), 0);
+    assertTrue(deserialized.union.isT1());
+    assertTrue(deserialized.union instanceof Union3);
+
+    // Test with T2
+    struct = new StructWithUnion3(Union3.ofT2("test"));
+    bytes = fory.serialize(struct);
+    deserialized = (StructWithUnion3) fory.deserialize(bytes);
+    assertEquals(deserialized.union.getValue(), "test");
+    assertEquals(deserialized.union.getIndex(), 1);
+    assertTrue(deserialized.union.isT2());
+
+    // Test with T3
+    struct = new StructWithUnion3(Union3.ofT3(3.14));
+    bytes = fory.serialize(struct);
+    deserialized = (StructWithUnion3) fory.deserialize(bytes);
+    assertEquals((Double) deserialized.union.getValue(), 3.14, 0.0001);
+    assertEquals(deserialized.union.getIndex(), 2);
+    assertTrue(deserialized.union.isT3());
+  }
+
+  @Test(dataProvider = "compatibleMode")
+  public void testUnion4Serialization(CompatibleMode mode) {
+    Fory fory = createXlangFory(mode);
+
+    // Test with T1
+    StructWithUnion4 struct = new StructWithUnion4(Union4.ofT1(42));
+    byte[] bytes = fory.serialize(struct);
+    StructWithUnion4 deserialized = (StructWithUnion4) fory.deserialize(bytes);
+    assertEquals(deserialized.union.getValue(), 42);
+    assertEquals(deserialized.union.getIndex(), 0);
+    assertTrue(deserialized.union.isT1());
+    assertTrue(deserialized.union instanceof Union4);
+
+    // Test with T4
+    struct = new StructWithUnion4(Union4.ofT4(true));
+    bytes = fory.serialize(struct);
+    deserialized = (StructWithUnion4) fory.deserialize(bytes);
+    assertEquals(deserialized.union.getValue(), true);
+    assertEquals(deserialized.union.getIndex(), 3);
+    assertTrue(deserialized.union.isT4());
+  }
+
+  @Test(dataProvider = "compatibleMode")
+  public void testUnion5Serialization(CompatibleMode mode) {
+    Fory fory = createXlangFory(mode);
+
+    // Test with T1
+    StructWithUnion5 struct = new StructWithUnion5(Union5.ofT1(42));
+    byte[] bytes = fory.serialize(struct);
+    StructWithUnion5 deserialized = (StructWithUnion5) fory.deserialize(bytes);
+    assertEquals(deserialized.union.getValue(), 42);
+    assertEquals(deserialized.union.getIndex(), 0);
+    assertTrue(deserialized.union.isT1());
+    assertTrue(deserialized.union instanceof Union5);
+
+    // Test with T5
+    struct = new StructWithUnion5(Union5.ofT5(999L));
+    bytes = fory.serialize(struct);
+    deserialized = (StructWithUnion5) fory.deserialize(bytes);
+    assertEquals(deserialized.union.getValue(), 999L);
+    assertEquals(deserialized.union.getIndex(), 4);
+    assertTrue(deserialized.union.isT5());
+  }
+
+  @Test(dataProvider = "compatibleMode")
+  public void testUnion6Serialization(CompatibleMode mode) {
+    Fory fory = createXlangFory(mode);
+
+    // Test with T1
+    StructWithUnion6 struct = new StructWithUnion6(Union6.ofT1(42));
+    byte[] bytes = fory.serialize(struct);
+    StructWithUnion6 deserialized = (StructWithUnion6) fory.deserialize(bytes);
+    assertEquals(deserialized.union.getValue(), 42);
+    assertEquals(deserialized.union.getIndex(), 0);
+    assertTrue(deserialized.union.isT1());
+    assertTrue(deserialized.union instanceof Union6);
+
+    // Test with T6
+    struct = new StructWithUnion6(Union6.ofT6(1.5f));
+    bytes = fory.serialize(struct);
+    deserialized = (StructWithUnion6) fory.deserialize(bytes);
+    assertEquals(deserialized.union.getValue(), 1.5f);
+    assertEquals(deserialized.union.getIndex(), 5);
+    assertTrue(deserialized.union.isT6());
+  }
+
+  @Test(dataProvider = "compatibleMode")
+  public void testUnionWithCollections(CompatibleMode mode) {
+    Fory fory = createXlangFory(mode);
+
+    // Test with List
+    List<Integer> list = new ArrayList<>();
+    list.add(1);
+    list.add(2);
+    list.add(3);
+    StructWithUnion struct = new StructWithUnion(new Union(0, list));
+    byte[] bytes = fory.serialize(struct);
+    StructWithUnion deserialized = (StructWithUnion) fory.deserialize(bytes);
+    assertTrue(deserialized.union.getValue() instanceof List);
+    assertEquals(deserialized.union.getValue(), list);
+
+    // Test with Map
+    Map<String, Integer> map = new HashMap<>();
+    map.put("a", 1);
+    map.put("b", 2);
+    struct = new StructWithUnion(new Union(1, map));
+    bytes = fory.serialize(struct);
+    deserialized = (StructWithUnion) fory.deserialize(bytes);
+    assertTrue(deserialized.union.getValue() instanceof Map);
+    assertEquals(deserialized.union.getValue(), map);
+  }
+
+  @Test(dataProvider = "compatibleMode")
+  public void testUnionWithNull(CompatibleMode mode) {
+    Fory fory = createXlangFory(mode);
+
+    Union union = new Union(0, null);
+    assertFalse(union.hasValue());
+
+    StructWithUnion struct = new StructWithUnion(union);
+    byte[] bytes = fory.serialize(struct);
+    StructWithUnion deserialized = (StructWithUnion) fory.deserialize(bytes);
+    assertNotNull(deserialized.union);
+    assertNull(deserialized.union.getValue());
+    assertEquals(deserialized.union.getIndex(), 0);
+  }
+
+  @Test
+  public void testUnion2TypeSafety() {
+    Union2<String, Long> union = Union2.ofT1("hello");
+    assertTrue(union.isT1());
+    assertFalse(union.isT2());
+    assertEquals(union.getT1(), "hello");
+    assertEquals(union.getIndex(), 0);
+
+    Union2<String, Long> union2 = Union2.ofT2(100L);
+    assertFalse(union2.isT1());
+    assertTrue(union2.isT2());
+    assertEquals(union2.getT2(), Long.valueOf(100L));
+    assertEquals(union2.getIndex(), 1);
+  }
+
+  @Test
+  public void testUnion3TypeSafety() {
+    Union3<Integer, String, Double> union = Union3.ofT2("test");
+    assertFalse(union.isT1());
+    assertTrue(union.isT2());
+    assertFalse(union.isT3());
+    assertEquals(union.getT2(), "test");
+  }
+
+  @Test
+  public void testUnion4TypeSafety() {
+    Union4<Integer, String, Double, Boolean> union = Union4.ofT3(3.14);
+    assertFalse(union.isT1());
+    assertFalse(union.isT2());
+    assertTrue(union.isT3());
+    assertFalse(union.isT4());
+    assertEquals(union.getT3(), 3.14);
+  }
+
+  @Test
+  public void testUnion5TypeSafety() {
+    Union5<Integer, String, Double, Boolean, Long> union = Union5.ofT3(3.14);
+    assertFalse(union.isT1());
+    assertFalse(union.isT2());
+    assertTrue(union.isT3());
+    assertFalse(union.isT4());
+    assertFalse(union.isT5());
+    assertEquals(union.getT3(), 3.14);
+  }
+
+  @Test
+  public void testUnion6TypeSafety() {
+    Union6<Integer, String, Double, Boolean, Long, Float> union = 
Union6.ofT4(true);
+    assertFalse(union.isT1());
+    assertFalse(union.isT2());
+    assertFalse(union.isT3());
+    assertTrue(union.isT4());
+    assertFalse(union.isT5());
+    assertFalse(union.isT6());
+    assertEquals(union.getT4(), true);
+  }
+
+  @Test
+  public void testUnionEquality() {
+    Union union1 = new Union(0, 42);
+    Union union2 = new Union(0, 42);
+    assertEquals(union1, union2);
+    assertEquals(union1.hashCode(), union2.hashCode());
+
+    Union2<String, Long> u2a = Union2.ofT1("hello");
+    Union2<String, Long> u2b = Union2.ofT1("hello");
+    assertEquals(u2a, u2b);
+    assertEquals(u2a.hashCode(), u2b.hashCode());
+
+    // Union2-6 extends Union, so they should be equal to Union with same 
index/value
+    Union baseUnion = new Union(0, "hello");
+    assertEquals(u2a, baseUnion);
+    assertEquals(baseUnion, u2a);
+  }
+
+  @Test
+  public void testUnionToString() {
+    Union union = new Union(0, 42);
+    String str = union.toString();
+    assertTrue(str.contains("42"));
+    assertTrue(str.contains("0"));
+
+    Union2<String, Long> union2 = Union2.ofT1("hello");
+    String str2 = union2.toString();
+    assertTrue(str2.contains("hello"));
+  }
+
+  @Test
+  public void testUnionGetValueTyped() {
+    Union union = new Union(0, 42);
+    Integer value = union.getValue(Integer.class);
+    assertEquals(value, Integer.valueOf(42));
+  }
+
+  @Test(expectedExceptions = IllegalArgumentException.class)
+  public void testUnion2InvalidIndex() {
+    Union2.of(5, "test"); // Index out of bounds for Union2
+  }
+
+  @Test(expectedExceptions = IllegalArgumentException.class)
+  public void testUnion3InvalidIndex() {
+    Union3.of(5, "test"); // Index out of bounds for Union3
+  }
+
+  @Test(expectedExceptions = IllegalArgumentException.class)
+  public void testUnion4InvalidIndex() {
+    Union4.of(5, "test"); // Index out of bounds for Union4
+  }
+
+  @Test(expectedExceptions = IllegalArgumentException.class)
+  public void testUnion5InvalidIndex() {
+    Union5.of(6, "test"); // Index out of bounds for Union5
+  }
+
+  @Test(expectedExceptions = IllegalArgumentException.class)
+  public void testUnion6InvalidIndex() {
+    Union6.of(7, "test"); // Index out of bounds for Union6
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to