You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fory.apache.org by ch...@apache.org on 2026/05/01 13:57:07 UTC

(fory) branch main updated: feat(dart): support nested container field codec for dart (#3641)

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 c0b083977 feat(dart): support nested container field codec for dart (#3641)
c0b083977 is described below

commit c0b083977116a91ad376891b5f7e3e8362c28180
Author: Shawn Yang <sh...@gmail.com>
AuthorDate: Fri May 1 21:57:03 2026 +0800

    feat(dart): support nested container field codec for dart (#3641)
    
    ## Why?
    
    Dart needs a canonical field type annotation surface that can describe
    nested container element/key/value semantics, including fixed-width and
    encoded numeric types, without relying on separate numeric wrapper
    annotations. The schema fingerprint calculation also needs to include
    nested container type IDs consistently across runtimes so
    compatible/schema-version checks agree for annotated nested containers.
    
    ## What does this PR do?
    
    - Adds Dart nested container field type support through `ForyField(type:
    ...)`, `MapField`, `ListType`, scalar type specs, and generated support
    for nested element/key/value metadata.
    - Updates the Dart compiler generator and generated test models to emit
    Dart `int` fields with explicit Fory type specs for fixed-width integer
    semantics, while keeping exact-width wrapper classes where the Dart
    runtime still requires them.
    - Removes obsolete Dart numeric wrapper exports/tests for small
    fixed-width integer wrappers and adds runtime validation utilities for
    integer bounds.
    - Extends Dart cross-language and unit coverage for nested annotated
    containers, integer round trips, type-spec annotations, signed/unsigned
    serializers, and xlang protocol behavior.
    - Aligns nested container schema fingerprint generation across Java,
    C++, C#, Go, JavaScript, Python, Rust, and Swift, and adds focused
    Swift/Java xlang coverage for Dart nested annotated containers.
    - Updates Dart guides, package README content, generated-code docs, and
    the xlang serialization spec to document the new annotation/type mapping
    model.
    
    ## Related issues
    
    
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence of the final clean AI review results
    from both fresh reviewers on the current PR diff or current HEAD after
    the latest code changes.
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [x] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    This changes the Dart annotation/type-spec surface for fixed-width
    numeric and nested container field declarations. It updates schema
    fingerprint calculation to include nested container metadata
    consistently, but does not intentionally change the xlang binary
    protocol format.
    
    ## Benchmark
---
 .github/workflows/ci.yml                           |   7 +-
 README.md                                          |   6 +-
 benchmarks/dart/lib/src/models.dart                |  69 +-
 compiler/fory_compiler/generators/dart.py          | 199 +++--
 .../fory_compiler/tests/test_dart_generator.py     |  38 +-
 cpp/fory/serialization/type_resolver.cc            |  71 +-
 csharp/src/Fory.Generator/ForyObjectGenerator.cs   | 101 ++-
 dart/README.md                                     |  62 +-
 .../fory-test/lib/entity/xlang_test_models.dart    | 250 +++---
 dart/packages/fory-test/lib/model/person.dart      |   7 +-
 .../test/compatible_struct_slots_test.dart         |  20 +-
 .../test/cross_lang_test/xlang_test_main.dart      |  36 +-
 .../fory-test/test/generated_round_trip_test.dart  |  18 +-
 dart/packages/fory/README.md                       | 124 +--
 dart/packages/fory/example/example.dart            |   5 +-
 dart/packages/fory/lib/fory.dart                   |   7 -
 .../fory/lib/src/annotation/fory_field.dart        |  77 +-
 .../fory/lib/src/annotation/numeric_types.dart     |  80 --
 .../fory/lib/src/annotation/type_spec.dart         | 212 +++--
 .../fory/lib/src/codegen/fory_generator.dart       | 923 +++++++++++++++------
 .../fory/lib/src/codegen/generated_support.dart    |  25 +
 .../fory/lib/src/context/write_context.dart        |  22 +
 dart/packages/fory/lib/src/fory.dart               |  46 +
 .../fory/lib/src/resolver/type_resolver.dart       |  59 +-
 .../lib/src/serializer/primitive_serializers.dart  |  59 +-
 .../lib/src/serializer/serializer_support.dart     |  34 -
 dart/packages/fory/lib/src/types/int16.dart        | 145 ----
 dart/packages/fory/lib/src/types/int32.dart        | 145 ----
 dart/packages/fory/lib/src/types/int8.dart         | 160 ----
 dart/packages/fory/lib/src/types/uint16.dart       | 147 ----
 dart/packages/fory/lib/src/types/uint32.dart       | 147 ----
 dart/packages/fory/lib/src/types/uint8.dart        | 147 ----
 dart/packages/fory/lib/src/util/hash_util.dart     |  72 +-
 .../packages/fory/lib/src/util/int_validation.dart |  66 ++
 dart/packages/fory/test/container_ref_test.dart    |  35 +-
 .../fory/test/manual_registration_test.dart        |   4 +-
 dart/packages/fory/test/nested_type_spec_test.dart | 274 ++++++
 dart/packages/fory/test/numeric_wrapper_test.dart  | 475 -----------
 .../fory/test/runtime_validation_test.dart         |   4 +-
 .../packages/fory/test/signed_serializer_test.dart |  67 +-
 .../fory/test/type_spec_annotation_test.dart       | 215 ++---
 .../fory/test/unsigned_serializer_test.dart        |  93 ++-
 dart/packages/fory/test/xlang_protocol_test.dart   |  21 +-
 dart/test/workspace_smoke_test.dart                |   6 +-
 docs/compiler/generated-code.md                    |  12 +-
 docs/compiler/index.md                             |   2 +-
 docs/compiler/schema-idl.md                        |  36 +-
 docs/guide/dart/basic-serialization.md             |  12 +-
 docs/guide/dart/code-generation.md                 |   4 +-
 docs/guide/dart/cross-language.md                  |  20 +-
 docs/guide/dart/field-configuration.md             |  37 +-
 docs/guide/dart/index.md                           |  11 +-
 docs/guide/dart/schema-evolution.md                |   2 +-
 docs/guide/dart/supported-types.md                 |  75 +-
 docs/guide/dart/troubleshooting.md                 |   8 +-
 docs/guide/dart/web-platform-support.md            |  17 +-
 docs/guide/xlang/field-reference-tracking.md       |   4 +-
 docs/specification/xlang_serialization_spec.md     |   7 +-
 go/fory/field_info.go                              |  90 +-
 go/fory/struct_init.go                             |  11 +-
 .../main/java/org/apache/fory/meta/FieldTypes.java |   6 +-
 .../apache/fory/serializer/struct/Fingerprint.java |  84 +-
 .../java/org/apache/fory/xlang/DartXlangTest.java  |  12 +
 javascript/packages/core/lib/meta/TypeMeta.ts      |  32 +-
 python/pyfory/struct.py                            | 159 +++-
 python/pyfory/tests/test_cross_language.py         |   1 +
 rust/fory-derive/src/object/util.rs                | 204 +++--
 swift/Sources/Fory/MacroDeclarations.swift         |   3 +
 swift/Sources/ForyMacro/ForyObjectMacro.swift      | 224 ++++-
 swift/Tests/ForyTests/CompatibilityTests.swift     |  31 +
 70 files changed, 3104 insertions(+), 2780 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3a5b8db11..5d71d2208 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1333,7 +1333,8 @@ jobs:
             Person();
 
             String name = '';
-            Int32 age = Int32(0);
+            @ForyField(type: Int32Type())
+            int age = 0;
           }
           EOF
           cat <<'EOF' | sed 's/^          //' > "$FLUTTER_SMOKE_DIR/lib/main.dart"
@@ -1363,13 +1364,13 @@ jobs:
 
               final person = Person()
                 ..name = 'Ada'
-                ..age = Int32(36);
+                ..age = 36;
 
               final bytes = fory.serialize(person);
               final roundTrip = fory.deserialize<Person>(bytes);
 
               expect(roundTrip.name, 'Ada');
-              expect(roundTrip.age, Int32(36));
+              expect(roundTrip.age, 36);
             });
           }
           EOF
diff --git a/README.md b/README.md
index f22583a90..4ce534801 100644
--- a/README.md
+++ b/README.md
@@ -441,7 +441,9 @@ class Person {
   Person();
 
   String name = '';
-  Int32 age = Int32(0);
+
+  @ForyField(type: Int32Type())
+  int age = 0;
 }
 
 void main() {
@@ -455,7 +457,7 @@ void main() {
 
   final person = Person()
     ..name = 'chaokunyang'
-    ..age = Int32(28);
+    ..age = 28;
 
   final bytes = fory.serialize(person);
   final result = fory.deserialize<Person>(bytes);
diff --git a/benchmarks/dart/lib/src/models.dart b/benchmarks/dart/lib/src/models.dart
index a4ea9ff26..77c077ecf 100644
--- a/benchmarks/dart/lib/src/models.dart
+++ b/benchmarks/dart/lib/src/models.dart
@@ -50,29 +50,21 @@ class NumericStruct {
     required this.f8,
   });
 
-  @ForyField(id: 1)
-  @Int32Type()
+  @ForyField(id: 1, type: Int32Type())
   final int f1;
-  @ForyField(id: 2)
-  @Int32Type()
+  @ForyField(id: 2, type: Int32Type())
   final int f2;
-  @ForyField(id: 3)
-  @Int32Type()
+  @ForyField(id: 3, type: Int32Type())
   final int f3;
-  @ForyField(id: 4)
-  @Int32Type()
+  @ForyField(id: 4, type: Int32Type())
   final int f4;
-  @ForyField(id: 5)
-  @Int32Type()
+  @ForyField(id: 5, type: Int32Type())
   final int f5;
-  @ForyField(id: 6)
-  @Int32Type()
+  @ForyField(id: 6, type: Int32Type())
   final int f6;
-  @ForyField(id: 7)
-  @Int32Type()
+  @ForyField(id: 7, type: Int32Type())
   final int f7;
-  @ForyField(id: 8)
-  @Int32Type()
+  @ForyField(id: 8, type: Int32Type())
   final int f8;
 }
 
@@ -103,39 +95,31 @@ class Sample {
     required this.string,
   });
 
-  @ForyField(id: 1)
-  @Int32Type()
+  @ForyField(id: 1, type: Int32Type())
   final int intValue;
-  @ForyField(id: 2)
-  @Int64Type()
+  @ForyField(id: 2, type: Int64Type())
   final int longValue;
   @ForyField(id: 3)
   final Float32 floatValue;
   @ForyField(id: 4)
   final double doubleValue;
-  @ForyField(id: 5)
-  @Int32Type()
+  @ForyField(id: 5, type: Int32Type())
   final int shortValue;
-  @ForyField(id: 6)
-  @Int32Type()
+  @ForyField(id: 6, type: Int32Type())
   final int charValue;
   @ForyField(id: 7)
   final bool booleanValue;
-  @ForyField(id: 8)
-  @Int32Type()
+  @ForyField(id: 8, type: Int32Type())
   final int intValueBoxed;
-  @ForyField(id: 9)
-  @Int64Type()
+  @ForyField(id: 9, type: Int64Type())
   final int longValueBoxed;
   @ForyField(id: 10)
   final Float32 floatValueBoxed;
   @ForyField(id: 11)
   final double doubleValueBoxed;
-  @ForyField(id: 12)
-  @Int32Type()
+  @ForyField(id: 12, type: Int32Type())
   final int shortValueBoxed;
-  @ForyField(id: 13)
-  @Int32Type()
+  @ForyField(id: 13, type: Int32Type())
   final int charValueBoxed;
   @ForyField(id: 14)
   final bool booleanValueBoxed;
@@ -178,22 +162,17 @@ class Media {
   final String uri;
   @ForyField(id: 2)
   final String title;
-  @ForyField(id: 3)
-  @Int32Type()
+  @ForyField(id: 3, type: Int32Type())
   final int width;
-  @ForyField(id: 4)
-  @Int32Type()
+  @ForyField(id: 4, type: Int32Type())
   final int height;
   @ForyField(id: 5)
   final String format;
-  @ForyField(id: 6)
-  @Int64Type()
+  @ForyField(id: 6, type: Int64Type())
   final int duration;
-  @ForyField(id: 7)
-  @Int64Type()
+  @ForyField(id: 7, type: Int64Type())
   final int size;
-  @ForyField(id: 8)
-  @Int32Type()
+  @ForyField(id: 8, type: Int32Type())
   final int bitrate;
   @ForyField(id: 9)
   final bool hasBitrate;
@@ -219,11 +198,9 @@ class Image {
   final String uri;
   @ForyField(id: 2)
   final String title;
-  @ForyField(id: 3)
-  @Int32Type()
+  @ForyField(id: 3, type: Int32Type())
   final int width;
-  @ForyField(id: 4)
-  @Int32Type()
+  @ForyField(id: 4, type: Int32Type())
   final int height;
   @ForyField(id: 5)
   final MediaSize size;
diff --git a/compiler/fory_compiler/generators/dart.py b/compiler/fory_compiler/generators/dart.py
index e13ea0d5a..bcad160f5 100644
--- a/compiler/fory_compiler/generators/dart.py
+++ b/compiler/fory_compiler/generators/dart.py
@@ -45,22 +45,22 @@ class DartGenerator(BaseGenerator):
 
     PRIMITIVE_MAP = {
         PrimitiveKind.BOOL: "bool",
-        PrimitiveKind.INT8: "Int8",
-        PrimitiveKind.INT16: "Int16",
-        PrimitiveKind.INT32: "Int32",
-        PrimitiveKind.VARINT32: "Int32",
-        PrimitiveKind.INT64: "int",
-        PrimitiveKind.VARINT64: "int",
-        PrimitiveKind.TAGGED_INT64: "int",
-        PrimitiveKind.UINT8: "UInt8",
-        PrimitiveKind.UINT16: "UInt16",
-        PrimitiveKind.UINT32: "UInt32",
-        PrimitiveKind.VAR_UINT32: "UInt32",
-        PrimitiveKind.UINT64: "int",
-        PrimitiveKind.VAR_UINT64: "int",
-        PrimitiveKind.TAGGED_UINT64: "int",
+        PrimitiveKind.INT8: "int",
+        PrimitiveKind.INT16: "int",
+        PrimitiveKind.INT32: "int",
+        PrimitiveKind.VARINT32: "int",
+        PrimitiveKind.INT64: "Int64",
+        PrimitiveKind.VARINT64: "Int64",
+        PrimitiveKind.TAGGED_INT64: "Int64",
+        PrimitiveKind.UINT8: "int",
+        PrimitiveKind.UINT16: "int",
+        PrimitiveKind.UINT32: "int",
+        PrimitiveKind.VAR_UINT32: "int",
+        PrimitiveKind.UINT64: "Uint64",
+        PrimitiveKind.VAR_UINT64: "Uint64",
+        PrimitiveKind.TAGGED_UINT64: "Uint64",
         PrimitiveKind.FLOAT16: "Float16",
-        PrimitiveKind.BFLOAT16: "Float16",
+        PrimitiveKind.BFLOAT16: "Bfloat16",
         PrimitiveKind.FLOAT32: "Float32",
         PrimitiveKind.FLOAT64: "double",
         PrimitiveKind.STRING: "String",
@@ -506,7 +506,7 @@ class DartGenerator(BaseGenerator):
             converted_value = self._conversion_expression(
                 field_type.value_type,
                 "mapValue",
-                False,
+                field_type.value_optional,
                 parent_stack,
             )
             return f"Map<{key_type}, {value_type}>.of((({value_expr} as Map)).map((key, mapValue) => MapEntry({converted_key}, {converted_value})))"
@@ -526,7 +526,7 @@ class DartGenerator(BaseGenerator):
         elif isinstance(field_type, MapType):
             base = (
                 f"Map<{self.dart_type(field_type.key_type, parent_stack=parent_stack)}, "
-                f"{self.dart_type(field_type.value_type, parent_stack=parent_stack)}>"
+                f"{self.dart_type(field_type.value_type, field_type.value_optional, parent_stack)}>"
             )
         elif isinstance(field_type, NamedType):
             resolved = self.resolve_type(field_type.name, parent_stack)
@@ -568,6 +568,8 @@ class DartGenerator(BaseGenerator):
                 PrimitiveKind.UINT64: "Uint64List",
                 PrimitiveKind.VAR_UINT64: "Uint64List",
                 PrimitiveKind.TAGGED_UINT64: "Uint64List",
+                PrimitiveKind.FLOAT16: "Float16List",
+                PrimitiveKind.BFLOAT16: "Bfloat16List",
                 PrimitiveKind.FLOAT32: "Float32List",
                 PrimitiveKind.FLOAT64: "Float64List",
             }
@@ -589,22 +591,22 @@ class DartGenerator(BaseGenerator):
         if isinstance(t, PrimitiveType):
             return {
                 PrimitiveKind.BOOL: "false",
-                PrimitiveKind.INT8: "Int8(0)",
-                PrimitiveKind.INT16: "Int16(0)",
-                PrimitiveKind.INT32: "Int32(0)",
-                PrimitiveKind.VARINT32: "Int32(0)",
-                PrimitiveKind.INT64: "0",
-                PrimitiveKind.VARINT64: "0",
-                PrimitiveKind.TAGGED_INT64: "0",
-                PrimitiveKind.UINT8: "UInt8(0)",
-                PrimitiveKind.UINT16: "UInt16(0)",
-                PrimitiveKind.UINT32: "UInt32(0)",
-                PrimitiveKind.VAR_UINT32: "UInt32(0)",
-                PrimitiveKind.UINT64: "0",
-                PrimitiveKind.VAR_UINT64: "0",
-                PrimitiveKind.TAGGED_UINT64: "0",
-                PrimitiveKind.FLOAT16: "Float16.zero",
-                PrimitiveKind.BFLOAT16: "Float16.zero",
+                PrimitiveKind.INT8: "0",
+                PrimitiveKind.INT16: "0",
+                PrimitiveKind.INT32: "0",
+                PrimitiveKind.VARINT32: "0",
+                PrimitiveKind.INT64: "Int64(0)",
+                PrimitiveKind.VARINT64: "Int64(0)",
+                PrimitiveKind.TAGGED_INT64: "Int64(0)",
+                PrimitiveKind.UINT8: "0",
+                PrimitiveKind.UINT16: "0",
+                PrimitiveKind.UINT32: "0",
+                PrimitiveKind.VAR_UINT32: "0",
+                PrimitiveKind.UINT64: "Uint64(0)",
+                PrimitiveKind.VAR_UINT64: "Uint64(0)",
+                PrimitiveKind.TAGGED_UINT64: "Uint64(0)",
+                PrimitiveKind.FLOAT16: "Float16(0)",
+                PrimitiveKind.BFLOAT16: "Bfloat16(0)",
                 PrimitiveKind.FLOAT32: "Float32(0)",
                 PrimitiveKind.FLOAT64: "0.0",
                 PrimitiveKind.STRING: "''",
@@ -623,7 +625,7 @@ class DartGenerator(BaseGenerator):
             )
         if isinstance(t, MapType):
             key_type = self.dart_type(t.key_type, parent_stack=parent_stack)
-            value_type = self.dart_type(t.value_type, parent_stack=parent_stack)
+            value_type = self.dart_type(t.value_type, t.value_optional, parent_stack)
             return f"<{key_type}, {value_type}>{{}}"
         if isinstance(t, NamedType):
             resolved = self.resolve_type(t.name, parent_stack)
@@ -664,38 +666,85 @@ class DartGenerator(BaseGenerator):
         field: Field,
         parent_stack: Optional[List[Message]] = None,
     ) -> List[str]:
-        annotations: List[str] = []
-        numeric_annotation = self.numeric_annotation(field.field_type)
-        if numeric_annotation is not None:
-            annotations.append(numeric_annotation)
         args: List[str] = []
+        type_spec = self.type_spec_expression(
+            field.field_type,
+            parent_stack,
+            element_ref_override=field.element_ref,
+        )
+        if type_spec is not None:
+            args.append(f"type: {type_spec}")
         if field.tag_id is not None:
             args.append(f"id: {field.tag_id}")
         if field.ref:
             args.append("ref: true")
         if args:
-            annotations.append(f"@ForyField({', '.join(args)})")
-        # Emit container TypeSpec annotations for nested ref tracking.
-        if isinstance(field.field_type, ListType) and (
-            field.element_ref or field.field_type.element_ref
-        ):
-            annotations.append("@ListType(element: ValueType.ref())")
-        if isinstance(field.field_type, MapType) and field.field_type.value_ref:
-            annotations.append("@MapType(value: ValueType.ref())")
-        return annotations
+            return [f"@ForyField({', '.join(args)})"]
+        return []
 
-    def numeric_annotation(self, field_type: FieldType) -> Optional[str]:
-        if not isinstance(field_type, PrimitiveType):
+    def type_spec_expression(
+        self,
+        field_type: FieldType,
+        parent_stack: Optional[List[Message]] = None,
+        *,
+        ref_override: bool = False,
+        element_ref_override: bool = False,
+    ) -> Optional[str]:
+        if isinstance(field_type, PrimitiveType):
+            primitive = {
+                PrimitiveKind.INT8: "Int8Type()",
+                PrimitiveKind.INT16: "Int16Type()",
+                PrimitiveKind.INT32: "Int32Type(encoding: Encoding.fixed)",
+                PrimitiveKind.VARINT32: "Int32Type(encoding: Encoding.varint)",
+                PrimitiveKind.INT64: "Int64Type(encoding: Encoding.fixed)",
+                PrimitiveKind.VARINT64: "Int64Type(encoding: Encoding.varint)",
+                PrimitiveKind.TAGGED_INT64: "Int64Type(encoding: Encoding.tagged)",
+                PrimitiveKind.UINT8: "Uint8Type()",
+                PrimitiveKind.UINT16: "Uint16Type()",
+                PrimitiveKind.UINT32: "Uint32Type(encoding: Encoding.fixed)",
+                PrimitiveKind.VAR_UINT32: "Uint32Type(encoding: Encoding.varint)",
+                PrimitiveKind.UINT64: "Uint64Type(encoding: Encoding.fixed)",
+                PrimitiveKind.VAR_UINT64: "Uint64Type(encoding: Encoding.varint)",
+                PrimitiveKind.TAGGED_UINT64: "Uint64Type(encoding: Encoding.tagged)",
+                PrimitiveKind.FLOAT16: "Float16Type()",
+                PrimitiveKind.BFLOAT16: "Bfloat16Type()",
+                PrimitiveKind.FLOAT32: "Float32Type()",
+            }.get(field_type.kind)
+            if primitive is not None:
+                return primitive
+            if ref_override:
+                return "DeclaredType(ref: true)"
+            return None
+        if isinstance(field_type, NamedType):
+            if ref_override:
+                return "DeclaredType(ref: true)"
             return None
-        return {
-            PrimitiveKind.INT32: "@Int32Type(compress: false)",
-            PrimitiveKind.INT64: "@Int64Type(encoding: LongEncoding.fixed)",
-            PrimitiveKind.TAGGED_INT64: "@Int64Type(encoding: LongEncoding.tagged)",
-            PrimitiveKind.VAR_UINT32: "@Uint32Type()",
-            PrimitiveKind.UINT64: "@Uint64Type(encoding: LongEncoding.fixed)",
-            PrimitiveKind.VAR_UINT64: "@Uint64Type()",
-            PrimitiveKind.TAGGED_UINT64: "@Uint64Type(encoding: LongEncoding.tagged)",
-        }.get(field_type.kind)
+        if isinstance(field_type, ListType):
+            element_ref = field_type.element_ref or element_ref_override
+            element_spec = self.type_spec_expression(
+                field_type.element_type,
+                parent_stack,
+                ref_override=element_ref,
+            )
+            if element_spec is None:
+                return None
+            return f"ListType(element: {element_spec})"
+        if isinstance(field_type, MapType):
+            key_spec = self.type_spec_expression(field_type.key_type, parent_stack)
+            value_spec = self.type_spec_expression(
+                field_type.value_type,
+                parent_stack,
+                ref_override=field_type.value_ref,
+            )
+            args: List[str] = []
+            if key_spec is not None:
+                args.append(f"key: {key_spec}")
+            if value_spec is not None:
+                args.append(f"value: {value_spec}")
+            if not args:
+                return None
+            return f"MapType({', '.join(args)})"
+        return None
 
     def enum_case_name(self, enum: Enum, value: EnumValue) -> str:
         name = value.name
@@ -985,7 +1034,7 @@ class DartGenerator(BaseGenerator):
             args.append(
                 self._field_type_literal_from_type(
                     field_type.value_type,
-                    False,
+                    field_type.value_optional,
                     field_type.value_ref,
                     indent + 2,
                     parent_stack,
@@ -1030,22 +1079,22 @@ class DartGenerator(BaseGenerator):
         if isinstance(field_type, PrimitiveType):
             return {
                 PrimitiveKind.BOOL: ("bool", "TypeIds.boolType"),
-                PrimitiveKind.INT8: ("Int8", "TypeIds.int8"),
-                PrimitiveKind.INT16: ("Int16", "TypeIds.int16"),
-                PrimitiveKind.INT32: ("Int32", "TypeIds.int32"),
-                PrimitiveKind.VARINT32: ("Int32", "TypeIds.varInt32"),
-                PrimitiveKind.INT64: ("int", "TypeIds.int64"),
-                PrimitiveKind.VARINT64: ("int", "TypeIds.varInt64"),
-                PrimitiveKind.TAGGED_INT64: ("int", "TypeIds.taggedInt64"),
-                PrimitiveKind.UINT8: ("UInt8", "TypeIds.uint8"),
-                PrimitiveKind.UINT16: ("UInt16", "TypeIds.uint16"),
-                PrimitiveKind.UINT32: ("UInt32", "TypeIds.uint32"),
-                PrimitiveKind.VAR_UINT32: ("UInt32", "TypeIds.varUint32"),
-                PrimitiveKind.UINT64: ("int", "TypeIds.uint64"),
-                PrimitiveKind.VAR_UINT64: ("int", "TypeIds.varUint64"),
-                PrimitiveKind.TAGGED_UINT64: ("int", "TypeIds.taggedUint64"),
+                PrimitiveKind.INT8: ("int", "TypeIds.int8"),
+                PrimitiveKind.INT16: ("int", "TypeIds.int16"),
+                PrimitiveKind.INT32: ("int", "TypeIds.int32"),
+                PrimitiveKind.VARINT32: ("int", "TypeIds.varInt32"),
+                PrimitiveKind.INT64: ("Int64", "TypeIds.int64"),
+                PrimitiveKind.VARINT64: ("Int64", "TypeIds.varInt64"),
+                PrimitiveKind.TAGGED_INT64: ("Int64", "TypeIds.taggedInt64"),
+                PrimitiveKind.UINT8: ("int", "TypeIds.uint8"),
+                PrimitiveKind.UINT16: ("int", "TypeIds.uint16"),
+                PrimitiveKind.UINT32: ("int", "TypeIds.uint32"),
+                PrimitiveKind.VAR_UINT32: ("int", "TypeIds.varUint32"),
+                PrimitiveKind.UINT64: ("Uint64", "TypeIds.uint64"),
+                PrimitiveKind.VAR_UINT64: ("Uint64", "TypeIds.varUint64"),
+                PrimitiveKind.TAGGED_UINT64: ("Uint64", "TypeIds.taggedUint64"),
                 PrimitiveKind.FLOAT16: ("Float16", "TypeIds.float16"),
-                PrimitiveKind.BFLOAT16: ("Float16", "TypeIds.float16"),
+                PrimitiveKind.BFLOAT16: ("Bfloat16", "TypeIds.bfloat16"),
                 PrimitiveKind.FLOAT32: ("Float32", "TypeIds.float32"),
                 PrimitiveKind.FLOAT64: ("double", "TypeIds.float64"),
                 PrimitiveKind.STRING: ("String", "TypeIds.string"),
@@ -1070,6 +1119,8 @@ class DartGenerator(BaseGenerator):
                     "Uint16List": "TypeIds.uint16Array",
                     "Uint32List": "TypeIds.uint32Array",
                     "Uint64List": "TypeIds.uint64Array",
+                    "Float16List": "TypeIds.float16Array",
+                    "Bfloat16List": "TypeIds.bfloat16Array",
                     "Float32List": "TypeIds.float32Array",
                     "Float64List": "TypeIds.float64Array",
                 }[arr]
diff --git a/compiler/fory_compiler/tests/test_dart_generator.py b/compiler/fory_compiler/tests/test_dart_generator.py
index 3311adb98..5f7e3e285 100644
--- a/compiler/fory_compiler/tests/test_dart_generator.py
+++ b/compiler/fory_compiler/tests/test_dart_generator.py
@@ -56,9 +56,17 @@ def test_dart_generator_emits_annotated_structs_and_generated_part_registration(
     assert "part 'demo.fory.dart';" in file.content
     assert "@ForyStruct()" in file.content
     assert "final class Scalar {" in file.content
-    assert "@Int32Type(compress: false)" in file.content
-    assert "@Uint64Type(encoding: LongEncoding.tagged)" in file.content
-    assert "@ForyField(id: 1)" in file.content
+    assert (
+        "@ForyField(type: Int32Type(encoding: Encoding.fixed), id: 1)" in file.content
+    )
+    assert (
+        "@ForyField(type: Int32Type(encoding: Encoding.varint), id: 2)" in file.content
+    )
+    assert (
+        "@ForyField(type: Uint64Type(encoding: Encoding.tagged), id: 3)" in file.content
+    )
+    assert "int fixedValue = 0;" in file.content
+    assert "Uint64 taggedValue = Uint64(0);" in file.content
     assert (
         "DemoFory.register(fory, Scalar, id: registrationMode.id, namespace: registrationMode.namespace, typeName: registrationMode.typeName);"
         in file.content
@@ -138,7 +146,11 @@ def test_dart_generator_uses_typed_lists_for_non_nullable_primitive_lists():
     )
 
     assert "Int32List ints = Int32List(0);" in file.content
-    assert "List<Int32?> nullableInts = <Int32?>[];" in file.content
+    assert (
+        "@ForyField(type: ListType(element: Int32Type(encoding: Encoding.varint)), id: 2)"
+        in file.content
+    )
+    assert "List<int?> nullableInts = <int?>[];" in file.content
     assert "Int32List? maybeInts = null;" in file.content
     assert "factory ValueUnion.values(Uint32List value)" in file.content
 
@@ -176,10 +188,14 @@ def test_dart_generator_emits_container_ref_annotations_for_builder_metadata():
         """
     )
 
-    assert "@ForyField(id: 1)" in file.content
-    assert "@ListType(element: ValueType.ref())" in file.content
-    assert "@ForyField(id: 2)" in file.content
-    assert "@MapType(value: ValueType.ref())" in file.content
+    assert (
+        "@ForyField(type: ListType(element: DeclaredType(ref: true)), id: 1)"
+        in file.content
+    )
+    assert (
+        "@ForyField(type: MapType(value: DeclaredType(ref: true)), id: 2)"
+        in file.content
+    )
     assert "@ForyField(id: 3, ref: true)" in file.content
 
 
@@ -194,8 +210,10 @@ def test_dart_generator_marks_map_value_ref_messages_as_ref_capable():
         """
     )
 
-    assert "@ForyField(id: 1)" in file.content
-    assert "@MapType(value: ValueType.ref())" in file.content
+    assert (
+        "@ForyField(type: MapType(value: DeclaredType(ref: true)), id: 1)"
+        in file.content
+    )
     assert "Map<String, Node> byName = <String, Node>{};" in file.content
 
 
diff --git a/cpp/fory/serialization/type_resolver.cc b/cpp/fory/serialization/type_resolver.cc
index 563afb6b6..fe1faca19 100644
--- a/cpp/fory/serialization/type_resolver.cc
+++ b/cpp/fory/serialization/type_resolver.cc
@@ -24,6 +24,7 @@
 #include "fory/type/type.h"
 #include <algorithm>
 #include <cstring>
+#include <functional>
 #include <map>
 #include <unordered_map>
 
@@ -1123,10 +1124,8 @@ std::string TypeMeta::compute_struct_fingerprint(
   // Field Components:
   //   - field_id_or_name: tag ID as string if configured, otherwise snake_case
   //   field name
-  //   - type_id: Fory TypeId as decimal string (e.g., "4" for INT32)
-  //   - ref: "1" if reference tracking enabled, "0" otherwise (always "0" in
-  //   C++)
-  //   - nullable: "1" if null flag is written, "0" otherwise
+  //   - field_type_fingerprint:
+  //     <type_id>,<ref>,<nullable>[<nested_type_fingerprint>]
   //
   // Example fingerprints:
   //   - With tag IDs: "0,4,0,0;1,12,0,1;"
@@ -1149,30 +1148,58 @@ std::string TypeMeta::compute_struct_fingerprint(
   // reserve a rough estimate to avoid reallocations
   fingerprint.reserve(sorted_fields.size() * 24);
 
+  auto fingerprint_type_id = [](uint32_t type_id) -> uint32_t {
+    if (type_id == static_cast<uint32_t>(TypeId::ENUM) ||
+        type_id == static_cast<uint32_t>(TypeId::NAMED_ENUM) ||
+        type_id == static_cast<uint32_t>(TypeId::STRUCT) ||
+        type_id == static_cast<uint32_t>(TypeId::COMPATIBLE_STRUCT) ||
+        type_id == static_cast<uint32_t>(TypeId::NAMED_STRUCT) ||
+        type_id == static_cast<uint32_t>(TypeId::NAMED_COMPATIBLE_STRUCT) ||
+        type_id == static_cast<uint32_t>(TypeId::EXT) ||
+        type_id == static_cast<uint32_t>(TypeId::NAMED_EXT) ||
+        type_id == static_cast<uint32_t>(TypeId::UNION) ||
+        type_id == static_cast<uint32_t>(TypeId::TYPED_UNION) ||
+        type_id == static_cast<uint32_t>(TypeId::NAMED_UNION)) {
+      return static_cast<uint32_t>(TypeId::UNKNOWN);
+    }
+    return type_id;
+  };
+  std::function<void(std::string &, const FieldType &, bool, bool)>
+      append_field_type = [&](std::string &out, const FieldType &field_type,
+                              bool include_ref, bool include_nullable) {
+        out.append(std::to_string(fingerprint_type_id(field_type.type_id)));
+        out.push_back(',');
+        out.push_back(include_ref && field_type.track_ref ? '1' : '0');
+        out.push_back(',');
+        out.push_back(include_nullable && field_type.nullable ? '1' : '0');
+        if (field_type.type_id == static_cast<uint32_t>(TypeId::LIST) ||
+            field_type.type_id == static_cast<uint32_t>(TypeId::SET)) {
+          out.push_back('[');
+          if (!field_type.generics.empty()) {
+            append_field_type(out, field_type.generics.front(), false, false);
+          }
+          out.push_back(']');
+        } else if (field_type.type_id == static_cast<uint32_t>(TypeId::MAP)) {
+          out.push_back('[');
+          if (!field_type.generics.empty()) {
+            append_field_type(out, field_type.generics.front(), false, false);
+          }
+          out.push_back('|');
+          if (field_type.generics.size() > 1) {
+            append_field_type(out, field_type.generics[1], false, false);
+          }
+          out.push_back(']');
+        }
+      };
+
   for (const auto &fi : sorted_fields) {
     std::string field_id_or_name = fi.field_id >= 0
                                        ? std::to_string(fi.field_id)
                                        : normalize_field_name(fi.field_name);
     fingerprint.append(field_id_or_name);
     fingerprint.push_back(',');
-
-    // Java's ObjectSerializer.get_type_id returns Types.UNKNOWN (0) for:
-    // - abstract classes, interfaces, and enum types
-    // - user-defined struct types (in xlang mode)
-    // This aligns the hash computation with Java's behavior.
-    uint32_t effective_type_id = fi.field_type.type_id;
-    if (effective_type_id == static_cast<uint32_t>(TypeId::ENUM) ||
-        effective_type_id == static_cast<uint32_t>(TypeId::NAMED_ENUM) ||
-        effective_type_id == static_cast<uint32_t>(TypeId::STRUCT) ||
-        effective_type_id == static_cast<uint32_t>(TypeId::NAMED_STRUCT) ||
-        effective_type_id == static_cast<uint32_t>(TypeId::UNION)) {
-      effective_type_id = static_cast<uint32_t>(TypeId::UNKNOWN);
-    }
-    fingerprint.append(std::to_string(effective_type_id));
-    fingerprint.push_back(',');
-    fingerprint.push_back(fi.field_type.track_ref ? '1' : '0');
-    fingerprint.push_back(',');
-    fingerprint.append(fi.field_type.nullable ? "1;" : "0;");
+    append_field_type(fingerprint, fi.field_type, true, true);
+    fingerprint.push_back(';');
   }
 
   return fingerprint;
diff --git a/csharp/src/Fory.Generator/ForyObjectGenerator.cs b/csharp/src/Fory.Generator/ForyObjectGenerator.cs
index c5dc09d5c..c38ac8c91 100644
--- a/csharp/src/Fory.Generator/ForyObjectGenerator.cs
+++ b/csharp/src/Fory.Generator/ForyObjectGenerator.cs
@@ -16,6 +16,7 @@
 // under the License.
 
 using System.Collections.Immutable;
+using System.Globalization;
 using System.Text;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -1660,18 +1661,8 @@ public sealed class ForyObjectGenerator : IIncrementalGenerator
         bool first = true;
         foreach (MemberModel member in ordered)
         {
-            uint fingerprintTypeId = (member.Classification.IsPrimitive || member.Classification.IsBuiltIn)
-                ? member.Classification.TypeId
-                : 0;
-            string trackRefExpr = member.DynamicAnyKind switch
-            {
-                DynamicAnyKind.AnyValue => "(trackRef ? 1 : 0)",
-                _ => member.Classification.IsBuiltIn || !member.IsRefType
-                    ? "0"
-                    : "(trackRef ? 1 : 0)",
-            };
-            string nullable = member.IsNullable ? "1" : "0";
-            string piece = $"\"{EscapeString(member.FieldIdentifier)},{fingerprintTypeId},\" + {trackRefExpr} + \",{nullable};\"";
+            string piece =
+                $"\"{EscapeString(member.FieldIdentifier)},\" + {BuildSchemaFieldTypeFingerprintExpression(member.TypeMeta, "trackRef", includeNullable: true)} + \";\"";
             if (!first)
             {
                 sb.Append(" + ");
@@ -1684,6 +1675,92 @@ public sealed class ForyObjectGenerator : IIncrementalGenerator
         return sb.ToString().Replace("b_float16", "bfloat16");
     }
 
+    private static string BuildSchemaFieldTypeFingerprintExpression(
+        TypeMetaFieldTypeModel model,
+        string trackRefExpr,
+        bool includeNullable)
+    {
+        string localTrackRefExpr = model.TrackRefByContext
+            ? $"({trackRefExpr} ? 1 : 0)"
+            : "0";
+        string prefix =
+            $"\"{NormalizeSchemaFingerprintTypeId(model.TypeIdExpr).ToString(CultureInfo.InvariantCulture)},\" + {localTrackRefExpr} + \","
+            + (includeNullable && model.Nullable ? "1" : "0")
+            + "\"";
+        if (model.Generics.Length == 0)
+        {
+            return prefix;
+        }
+
+        if (model.Generics.Length == 1)
+        {
+            string child = BuildSchemaFieldTypeFingerprintExpression(model.Generics[0], "false", includeNullable: false);
+            return $"{prefix} + \"[\" + {child} + \"]\"";
+        }
+
+        if (model.Generics.Length == 2)
+        {
+            string key = BuildSchemaFieldTypeFingerprintExpression(model.Generics[0], "false", includeNullable: false);
+            string value = BuildSchemaFieldTypeFingerprintExpression(model.Generics[1], "false", includeNullable: false);
+            return $"{prefix} + \"[\" + {key} + \"|\" + {value} + \"]\"";
+        }
+
+        throw new InvalidOperationException("schema fingerprint supports only list/set/map generic arity");
+    }
+
+    private static uint NormalizeSchemaFingerprintTypeId(string typeIdExpr)
+    {
+        if (!TryParseSchemaFingerprintTypeId(typeIdExpr, out uint typeId))
+        {
+            throw new InvalidOperationException($"unsupported schema fingerprint type id expression {typeIdExpr}");
+        }
+
+        return typeId switch
+        {
+            0 or 25 or 26 or 27 or 28 or 29 or 30 or 31 or 32 or 33 or 34 or 35 => 0,
+            _ => typeId,
+        };
+    }
+
+    private static bool TryParseSchemaFingerprintTypeId(string typeIdExpr, out uint typeId)
+    {
+        string normalized = typeIdExpr.Replace(" ", string.Empty);
+        if (normalized.StartsWith("(uint)", StringComparison.Ordinal))
+        {
+            normalized = normalized.Substring(6);
+        }
+
+        if (uint.TryParse(normalized, NumberStyles.None, CultureInfo.InvariantCulture, out typeId))
+        {
+            return true;
+        }
+
+        switch (normalized)
+        {
+            case "global::Apache.Fory.TypeId.Unknown":
+                typeId = 0;
+                return true;
+            case "global::Apache.Fory.TypeId.List":
+                typeId = 22;
+                return true;
+            case "global::Apache.Fory.TypeId.Set":
+                typeId = 23;
+                return true;
+            case "global::Apache.Fory.TypeId.Map":
+                typeId = 24;
+                return true;
+            case "global::Apache.Fory.TypeId.Enum":
+                typeId = 25;
+                return true;
+            case "global::Apache.Fory.TypeId.Union":
+                typeId = 33;
+                return true;
+            default:
+                typeId = 0;
+                return false;
+        }
+    }
+
     private static string BuildTypeMetaExpression(TypeMetaFieldTypeModel model, string trackRefExpr)
     {
         string localTrackRefExpr = model.TrackRefByContext ? trackRefExpr : "false";
diff --git a/dart/README.md b/dart/README.md
index e7496e463..dae19b840 100644
--- a/dart/README.md
+++ b/dart/README.md
@@ -21,32 +21,36 @@ including getting started, API reference, and code examples.
 
 ## Type Mapping
 
-| Fory xlang type | Dart type                |
-| --------------- | ------------------------ |
-| bool            | `bool`                   |
-| int8            | `fory.Int8` (wrapper)    |
-| int16           | `fory.Int16` (wrapper)   |
-| int32           | `fory.Int32` (wrapper)   |
-| int64           | `int`                    |
-| float16         | `fory.Float16` (wrapper) |
-| float32         | `fory.Float32` (wrapper) |
-| float64         | `double`                 |
-| string          | `String`                 |
-| binary          | `Uint8List`              |
-| local_date      | `LocalDate`              |
-| timestamp       | `Timestamp`              |
-| list            | `List`                   |
-| set             | `Set`                    |
-| map             | `Map`                    |
-| enum            | `enum`                   |
-| named_struct    | `class`                  |
-| bool_array      | `List<bool>`             |
-| int8_array      | `Int8List`               |
-| int16_array     | `Int16List`              |
-| int32_array     | `Int32List`              |
-| int64_array     | `Int64List`              |
-| float32_array   | `Float32List`            |
-| float64_array   | `Float64List`            |
+| Fory xlang type | Dart type                                |
+| --------------- | ---------------------------------------- |
+| bool            | `bool`                                   |
+| int8            | `int` + `@ForyField(type: Int8Type())`   |
+| int16           | `int` + `@ForyField(type: Int16Type())`  |
+| int32           | `int` + `@ForyField(type: Int32Type())`  |
+| int64           | `int` or `Int64`                         |
+| uint8           | `int` + `@ForyField(type: Uint8Type())`  |
+| uint16          | `int` + `@ForyField(type: Uint16Type())` |
+| uint32          | `int` + `@ForyField(type: Uint32Type())` |
+| uint64          | `int` or `Uint64`                        |
+| float16         | `fory.Float16` (wrapper)                 |
+| float32         | `fory.Float32` (wrapper)                 |
+| float64         | `double`                                 |
+| string          | `String`                                 |
+| binary          | `Uint8List`                              |
+| local_date      | `LocalDate`                              |
+| timestamp       | `Timestamp`                              |
+| list            | `List`                                   |
+| set             | `Set`                                    |
+| map             | `Map`                                    |
+| enum            | `enum`                                   |
+| named_struct    | `class`                                  |
+| bool_array      | `List<bool>`                             |
+| int8_array      | `Int8List`                               |
+| int16_array     | `Int16List`                              |
+| int32_array     | `Int32List`                              |
+| int64_array     | `Int64List`                              |
+| float32_array   | `Float32List`                            |
+| float64_array   | `Float64List`                            |
 
 ## Quick Start
 
@@ -62,7 +66,9 @@ class Person {
   Person();
 
   String name = '';
-  Int32 age = Int32(0);
+
+  @ForyField(type: Int32Type())
+  int age = 0;
 }
 ```
 
@@ -76,7 +82,7 @@ Serialize and deserialize:
 final fory = Fory();
 PersonFory.register(fory, Person, namespace: 'example', typeName: 'Person');
 
-final bytes = fory.serialize(Person()..name = 'Ada'..age = Int32(36));
+final bytes = fory.serialize(Person()..name = 'Ada'..age = 36);
 final roundTrip = fory.deserialize<Person>(bytes);
 ```
 
diff --git a/dart/packages/fory-test/lib/entity/xlang_test_models.dart b/dart/packages/fory-test/lib/entity/xlang_test_models.dart
index 8c6783376..63643d183 100644
--- a/dart/packages/fory-test/lib/entity/xlang_test_models.dart
+++ b/dart/packages/fory-test/lib/entity/xlang_test_models.dart
@@ -84,15 +84,24 @@ class Item {
 class SimpleStruct {
   SimpleStruct();
 
-  Map<Int32?, double?> f1 = <Int32?, double?>{};
-  Int32 f2 = Int32(0);
+  @MapField(key: Int32Type())
+  Map<int?, double?> f1 = <int?, double?>{};
+
+  @ForyField(type: Int32Type())
+  int f2 = 0;
   Item f3 = Item();
   String f4 = '';
   Color f5 = Color.green;
-  List<String> f6 = <String>[];
-  Int32 f7 = Int32(0);
-  Int32 f8 = Int32(0);
-  Int32 last = Int32(0);
+  List<String?> f6 = <String?>[];
+
+  @ForyField(type: Int32Type())
+  int f7 = 0;
+
+  @ForyField(type: Int32Type())
+  int f8 = 0;
+
+  @ForyField(type: Int32Type())
+  int last = 0;
 }
 
 @ForyStruct()
@@ -113,12 +122,23 @@ class FixedOverrideStruct {
 class Item1 {
   Item1();
 
-  Int32 f1 = Int32(0);
-  Int32 f2 = Int32(0);
-  Int32 f3 = Int32(0);
-  Int32 f4 = Int32(0);
-  Int32 f5 = Int32(0);
-  Int32 f6 = Int32(0);
+  @ForyField(type: Int32Type())
+  int f1 = 0;
+
+  @ForyField(type: Int32Type())
+  int f2 = 0;
+
+  @ForyField(type: Int32Type())
+  int f3 = 0;
+
+  @ForyField(type: Int32Type())
+  int f4 = 0;
+
+  @ForyField(type: Int32Type())
+  int f5 = 0;
+
+  @ForyField(type: Int32Type())
+  int f6 = 0;
 }
 
 final class Union2 {
@@ -167,11 +187,39 @@ class StructWithMap {
   Map<String?, String?> data = <String?, String?>{};
 }
 
+@ForyStruct()
+class NestedAnnotatedContainerSchemaConsistent {
+  NestedAnnotatedContainerSchemaConsistent();
+
+  @MapField(
+    key: Uint32Type(nullable: true, encoding: Encoding.fixed),
+    value: ListType(
+      nullable: true,
+      element: Uint64Type(nullable: true, encoding: Encoding.tagged),
+    ),
+  )
+  Map<int?, List<int?>?> values = <int?, List<int?>?>{};
+}
+
+@ForyStruct()
+class NestedAnnotatedContainerCompatible {
+  NestedAnnotatedContainerCompatible();
+
+  @MapField(
+    key: Uint32Type(nullable: true, encoding: Encoding.fixed),
+    value: ListType(
+      nullable: true,
+      element: Uint64Type(nullable: true, encoding: Encoding.tagged),
+    ),
+  )
+  Map<int?, List<int?>?> values = <int?, List<int?>?>{};
+}
+
 @ForyStruct()
 class MyStruct {
   MyStruct();
 
-  @Int32Type()
+  @ForyField(type: Int32Type())
   int id = 0;
 }
 
@@ -206,7 +254,7 @@ class EmptyWrapper {
 class VersionCheckStruct {
   VersionCheckStruct();
 
-  @Int32Type()
+  @ForyField(type: Int32Type())
   int f1 = 0;
 
   @ForyField(nullable: true)
@@ -224,7 +272,7 @@ class Dog implements Animal {
   Dog();
 
   @override
-  @Int32Type()
+  @ForyField(type: Int32Type())
   int age = 0;
 
   @ForyField(nullable: true)
@@ -235,11 +283,11 @@ class Dog implements Animal {
 class Cat implements Animal {
   Cat();
 
-  @Int32Type()
+  @ForyField(type: Int32Type())
   @override
   int age = 0;
 
-  @Int32Type()
+  @ForyField(type: Int32Type())
   int lives = 0;
 }
 
@@ -247,14 +295,14 @@ class Cat implements Animal {
 class AnimalListHolder {
   AnimalListHolder();
 
-  List<Animal> animals = <Animal>[];
+  List<Animal?> animals = <Animal?>[];
 }
 
 @ForyStruct()
 class AnimalMapHolder {
   AnimalMapHolder();
 
-  Map<String, Animal> animalMap = <String, Animal>{};
+  Map<String?, Animal?> animalMap = <String?, Animal?>{};
 }
 
 @ForyStruct()
@@ -307,20 +355,25 @@ class TwoEnumFieldStruct {
 class NullableComprehensiveSchemaConsistent {
   NullableComprehensiveSchemaConsistent();
 
-  Int8 byteField = Int8(0);
-  Int16 shortField = Int16(0);
-  Int32 intField = Int32(0);
+  @ForyField(type: Int8Type())
+  int byteField = 0;
+
+  @ForyField(type: Int16Type())
+  int shortField = 0;
+
+  @ForyField(type: Int32Type())
+  int intField = 0;
   int longField = 0;
   Float32 floatField = Float32(0);
   double doubleField = 0;
   bool boolField = false;
   String stringField = '';
-  List<String> listField = <String>[];
-  Set<String> setField = <String>{};
-  Map<String, String> mapField = <String, String>{};
+  List<String?> listField = <String?>[];
+  Set<String?> setField = <String?>{};
+  Map<String?, String?> mapField = <String?, String?>{};
 
-  @ForyField(nullable: true)
-  Int32? nullableInt;
+  @ForyField(nullable: true, type: Int32Type())
+  int? nullableInt;
 
   @ForyField(nullable: true)
   int? nullableLong;
@@ -338,54 +391,61 @@ class NullableComprehensiveSchemaConsistent {
   String? nullableString;
 
   @ForyField(nullable: true)
-  List<String>? nullableList;
+  List<String?>? nullableList;
 
   @ForyField(nullable: true)
-  Set<String>? nullableSet;
+  Set<String?>? nullableSet;
 
   @ForyField(nullable: true)
-  Map<String, String>? nullableMap;
+  Map<String?, String?>? nullableMap;
 }
 
 @ForyStruct()
 class NullableComprehensiveCompatible {
   NullableComprehensiveCompatible();
 
-  Int8 byteField = Int8(0);
-  Int16 shortField = Int16(0);
-  Int32 intField = Int32(0);
+  @ForyField(type: Int8Type())
+  int byteField = 0;
+
+  @ForyField(type: Int16Type())
+  int shortField = 0;
+
+  @ForyField(type: Int32Type())
+  int intField = 0;
   int longField = 0;
   Float32 floatField = Float32(0);
   double doubleField = 0;
   bool boolField = false;
 
-  Int32 boxedInt = Int32(0);
+  @ForyField(type: Int32Type())
+  int boxedInt = 0;
   int boxedLong = 0;
   Float32 boxedFloat = Float32(0);
   double boxedDouble = 0;
   bool boxedBool = false;
 
   String stringField = '';
-  List<String> listField = <String>[];
-  Set<String> setField = <String>{};
-  Map<String, String> mapField = <String, String>{};
+  List<String?> listField = <String?>[];
+  Set<String?> setField = <String?>{};
+  Map<String?, String?> mapField = <String?, String?>{};
 
-  Int32 nullableInt1 = Int32(0);
+  @ForyField(type: Int32Type())
+  int nullableInt1 = 0;
   int nullableLong1 = 0;
   Float32 nullableFloat1 = Float32(0);
   double nullableDouble1 = 0;
   bool nullableBool1 = false;
   String nullableString2 = '';
-  List<String> nullableList2 = <String>[];
-  Set<String> nullableSet2 = <String>{};
-  Map<String, String> nullableMap2 = <String, String>{};
+  List<String?> nullableList2 = <String?>[];
+  Set<String?> nullableSet2 = <String?>{};
+  Map<String?, String?> nullableMap2 = <String?, String?>{};
 }
 
 @ForyStruct()
 class RefInnerSchemaConsistent {
   RefInnerSchemaConsistent();
 
-  @Int32Type()
+  @ForyField(type: Int32Type())
   int id = 0;
   String name = '';
 }
@@ -405,7 +465,7 @@ class RefOuterSchemaConsistent {
 class RefInnerCompatible {
   RefInnerCompatible();
 
-  @Int32Type()
+  @ForyField(type: Int32Type())
   int id = 0;
   String name = '';
 }
@@ -425,7 +485,8 @@ class RefOuterCompatible {
 class RefOverrideElement {
   RefOverrideElement();
 
-  Int32 id = Int32(0);
+  @ForyField(type: Int32Type())
+  int id = 0;
   String name = '';
 }
 
@@ -451,48 +512,46 @@ class CircularRefStruct {
 class UnsignedSchemaConsistent {
   UnsignedSchemaConsistent();
 
-  Uint8 u8Field = Uint8(0);
-  Uint16 u16Field = Uint16(0);
+  @ForyField(type: Uint8Type())
+  int u8Field = 0;
 
-  @Uint32Type(compress: true)
-  Uint32 u32VarField = Uint32(0);
+  @ForyField(type: Uint16Type())
+  int u16Field = 0;
 
-  @Uint32Type(compress: false)
-  Uint32 u32FixedField = Uint32(0);
+  @ForyField(type: Uint32Type(encoding: Encoding.varint))
+  int u32VarField = 0;
 
-  @Uint64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Uint32Type(encoding: Encoding.fixed))
+  int u32FixedField = 0;
+
+  @ForyField(type: Uint64Type(encoding: Encoding.varint))
   int u64VarField = 0;
 
-  @Uint64Type(encoding: LongEncoding.fixed)
+  @ForyField(type: Uint64Type(encoding: Encoding.fixed))
   int u64FixedField = 0;
 
-  @Uint64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Uint64Type(encoding: Encoding.tagged))
   int u64TaggedField = 0;
 
-  @ForyField(nullable: true)
-  Uint8? u8NullableField;
+  @ForyField(nullable: true, type: Uint8Type())
+  int? u8NullableField;
 
-  @ForyField(nullable: true)
-  Uint16? u16NullableField;
+  @ForyField(nullable: true, type: Uint16Type())
+  int? u16NullableField;
 
-  @ForyField(nullable: true)
-  @Uint32Type(compress: true)
-  Uint32? u32VarNullableField;
+  @ForyField(nullable: true, type: Uint32Type(encoding: Encoding.varint))
+  int? u32VarNullableField;
 
-  @ForyField(nullable: true)
-  @Uint32Type(compress: false)
-  Uint32? u32FixedNullableField;
+  @ForyField(nullable: true, type: Uint32Type(encoding: Encoding.fixed))
+  int? u32FixedNullableField;
 
-  @ForyField(nullable: true)
-  @Uint64Type(encoding: LongEncoding.varint)
+  @ForyField(nullable: true, type: Uint64Type(encoding: Encoding.varint))
   int? u64VarNullableField;
 
-  @ForyField(nullable: true)
-  @Uint64Type(encoding: LongEncoding.fixed)
+  @ForyField(nullable: true, type: Uint64Type(encoding: Encoding.fixed))
   int? u64FixedNullableField;
 
-  @ForyField(nullable: true)
-  @Uint64Type(encoding: LongEncoding.tagged)
+  @ForyField(nullable: true, type: Uint64Type(encoding: Encoding.tagged))
   int? u64TaggedNullableField;
 }
 
@@ -500,11 +559,10 @@ class UnsignedSchemaConsistent {
 class UnsignedSchemaConsistentSimple {
   UnsignedSchemaConsistentSimple();
 
-  @Uint64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Uint64Type(encoding: Encoding.tagged))
   int u64Tagged = 0;
 
-  @ForyField(nullable: true)
-  @Uint64Type(encoding: LongEncoding.tagged)
+  @ForyField(nullable: true, type: Uint64Type(encoding: Encoding.tagged))
   int? u64TaggedNullable;
 }
 
@@ -512,47 +570,45 @@ class UnsignedSchemaConsistentSimple {
 class UnsignedSchemaCompatible {
   UnsignedSchemaCompatible();
 
-  @ForyField(nullable: true)
-  Uint8? u8Field1;
+  @ForyField(nullable: true, type: Uint8Type())
+  int? u8Field1;
 
-  @ForyField(nullable: true)
-  Uint16? u16Field1;
+  @ForyField(nullable: true, type: Uint16Type())
+  int? u16Field1;
 
-  @ForyField(nullable: true)
-  @Uint32Type(compress: true)
-  Uint32? u32VarField1;
+  @ForyField(nullable: true, type: Uint32Type(encoding: Encoding.varint))
+  int? u32VarField1;
 
-  @ForyField(nullable: true)
-  @Uint32Type(compress: false)
-  Uint32? u32FixedField1;
+  @ForyField(nullable: true, type: Uint32Type(encoding: Encoding.fixed))
+  int? u32FixedField1;
 
-  @ForyField(nullable: true)
-  @Uint64Type(encoding: LongEncoding.varint)
+  @ForyField(nullable: true, type: Uint64Type(encoding: Encoding.varint))
   int? u64VarField1;
 
-  @ForyField(nullable: true)
-  @Uint64Type(encoding: LongEncoding.fixed)
+  @ForyField(nullable: true, type: Uint64Type(encoding: Encoding.fixed))
   int? u64FixedField1;
 
-  @ForyField(nullable: true)
-  @Uint64Type(encoding: LongEncoding.tagged)
+  @ForyField(nullable: true, type: Uint64Type(encoding: Encoding.tagged))
   int? u64TaggedField1;
 
-  Uint8 u8Field2 = Uint8(0);
-  Uint16 u16Field2 = Uint16(0);
+  @ForyField(type: Uint8Type())
+  int u8Field2 = 0;
+
+  @ForyField(type: Uint16Type())
+  int u16Field2 = 0;
 
-  @Uint32Type(compress: true)
-  Uint32 u32VarField2 = Uint32(0);
+  @ForyField(type: Uint32Type(encoding: Encoding.varint))
+  int u32VarField2 = 0;
 
-  @Uint32Type(compress: false)
-  Uint32 u32FixedField2 = Uint32(0);
+  @ForyField(type: Uint32Type(encoding: Encoding.fixed))
+  int u32FixedField2 = 0;
 
-  @Uint64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Uint64Type(encoding: Encoding.varint))
   int u64VarField2 = 0;
 
-  @Uint64Type(encoding: LongEncoding.fixed)
+  @ForyField(type: Uint64Type(encoding: Encoding.fixed))
   int u64FixedField2 = 0;
 
-  @Uint64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Uint64Type(encoding: Encoding.tagged))
   int u64TaggedField2 = 0;
 }
diff --git a/dart/packages/fory-test/lib/model/person.dart b/dart/packages/fory-test/lib/model/person.dart
index a6946637e..7b9ccda23 100644
--- a/dart/packages/fory-test/lib/model/person.dart
+++ b/dart/packages/fory-test/lib/model/person.dart
@@ -33,10 +33,13 @@ class Person {
   Person();
 
   String name = '';
-  Int32 age = Int32(0);
+  @ForyField(type: Int32Type())
+  int age = 0;
   Color favoriteColor = Color.red;
   List<String?> tags = <String?>[];
-  Map<String, Int32> scores = <String, Int32>{};
+
+  @MapField(value: Int32Type())
+  Map<String, int> scores = <String, int>{};
 }
 
 @ForyStruct()
diff --git a/dart/packages/fory-test/test/compatible_struct_slots_test.dart b/dart/packages/fory-test/test/compatible_struct_slots_test.dart
index 7a5ede723..bf2f3a216 100644
--- a/dart/packages/fory-test/test/compatible_struct_slots_test.dart
+++ b/dart/packages/fory-test/test/compatible_struct_slots_test.dart
@@ -44,13 +44,13 @@ void main() {
     );
 
     final original = SimpleStruct()
-      ..f2 = Int32(1)
-      ..f7 = Int32(2)
-      ..f8 = Int32(3)
-      ..last = Int32(4)
+      ..f2 = (1)
+      ..f7 = (2)
+      ..f8 = (3)
+      ..last = (4)
       ..f4 = 'outer'
       ..f6 = <String>['a', 'b']
-      ..f1 = <Int32?, double?>{Int32(7): 9.5}
+      ..f1 = <int?, double?>{(7): 9.5}
       ..f3 = (Item()..name = 'inner')
       ..f5 = Color.blue;
 
@@ -62,13 +62,13 @@ void main() {
     final secondBytes = fory.serialize(decoded);
     final roundTrip = fory.deserialize<SimpleStruct>(secondBytes);
 
-    expect(roundTrip.f2, equals(Int32(1)));
-    expect(roundTrip.f7, equals(Int32(2)));
-    expect(roundTrip.f8, equals(Int32(3)));
-    expect(roundTrip.last, equals(Int32(4)));
+    expect(roundTrip.f2, equals((1)));
+    expect(roundTrip.f7, equals((2)));
+    expect(roundTrip.f8, equals((3)));
+    expect(roundTrip.last, equals((4)));
     expect(roundTrip.f4, equals('outer'));
     expect(roundTrip.f6, equals(<String>['a', 'b']));
-    expect(roundTrip.f1[Int32(7)], equals(9.5));
+    expect(roundTrip.f1[(7)], equals(9.5));
     expect(roundTrip.f3.name, equals('inner'));
     expect(roundTrip.f5, equals(Color.blue));
   });
diff --git a/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart b/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart
index a90e86a9e..f10848f35 100644
--- a/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart
+++ b/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart
@@ -75,6 +75,25 @@ void _roundTripFory(Fory fory, {bool trackRef = false}) {
   _writeFile(output.takeBytes());
 }
 
+void _runIntegerCase() {
+  final fory = _newFory(compatible: true);
+  registerXlangType(fory, Item1, id: 101);
+
+  final input = Buffer.wrap(_readFile());
+  final output = BytesBuilder(copy: false);
+
+  output.add(fory.serialize(fory.deserializeFrom<Item1>(input)));
+  for (var i = 0; i < 6; i += 1) {
+    final value = fory.deserializeFrom<int>(input);
+    output.add(fory.serializeBuiltin(value, wireTypeId: TypeIds.varInt32));
+  }
+
+  if (input.readableBytes != 0) {
+    throw StateError('Unexpected trailing bytes after integer payload.');
+  }
+  _writeFile(output.takeBytes());
+}
+
 void _verifyBufferCase() {
   final input = Buffer.wrap(_readFile());
   final output = Buffer();
@@ -335,7 +354,7 @@ void _runCollectionElementRefRemoteTracking() {
   registerXlangType(fory, RefOverrideContainer, id: 702);
 
   final shared = RefOverrideElement()
-    ..id = Int32(7)
+    ..id = 7
     ..name = 'shared_element';
   // IMPORTANT: this peer intentionally writes a shared-reference payload with
   // its default local ref-tracked schema. The Java reader uses ref-disabled
@@ -424,9 +443,7 @@ void _runCase(String caseName) {
       _roundTripFory(fory);
       return;
     case 'test_integer':
-      final fory = _newFory(compatible: true);
-      registerXlangType(fory, Item1, id: 101);
-      _roundTripFory(fory);
+      _runIntegerCase();
       return;
     case 'test_decimal':
       _verifyDecimalCase();
@@ -452,6 +469,17 @@ void _runCase(String caseName) {
       registerXlangType(fory, StructWithMap, id: 202);
       _roundTripFory(fory);
       return;
+    case 'test_nested_annotated_container_schema_consistent':
+      final fory = _newFory();
+      registerXlangType(fory, NestedAnnotatedContainerSchemaConsistent,
+          id: 801);
+      _roundTripFory(fory);
+      return;
+    case 'test_nested_annotated_container_compatible':
+      final fory = _newFory(compatible: true);
+      registerXlangType(fory, NestedAnnotatedContainerCompatible, id: 802);
+      _roundTripFory(fory);
+      return;
     case 'test_skip_id_custom':
       final fory = _newFory(compatible: true);
       registerXlangType(fory, Color, id: 101);
diff --git a/dart/packages/fory-test/test/generated_round_trip_test.dart b/dart/packages/fory-test/test/generated_round_trip_test.dart
index 129912544..7d498fdd2 100644
--- a/dart/packages/fory-test/test/generated_round_trip_test.dart
+++ b/dart/packages/fory-test/test/generated_round_trip_test.dart
@@ -44,23 +44,23 @@ void main() {
 
       final person = Person()
         ..name = 'Ada'
-        ..age = Int32(36)
+        ..age = (36)
         ..favoriteColor = Color.blue
         ..tags = <String?>['engineer', null]
-        ..scores = <String, Int32>{
-          'math': Int32(100),
-          'logic': Int32(99),
+        ..scores = <String, int>{
+          'math': (100),
+          'logic': (99),
         };
 
       final bytes = fory.serialize(person);
       final roundTrip = fory.deserialize<Person>(bytes);
 
       expect(roundTrip.name, equals('Ada'));
-      expect(roundTrip.age, equals(Int32(36)));
+      expect(roundTrip.age, equals((36)));
       expect(roundTrip.favoriteColor, equals(Color.blue));
       expect(roundTrip.tags, equals(<String?>['engineer', null]));
-      expect(roundTrip.scores['math'], equals(Int32(100)));
-      expect(roundTrip.scores['logic'], equals(Int32(99)));
+      expect(roundTrip.scores['math'], equals((100)));
+      expect(roundTrip.scores['logic'], equals((99)));
     });
 
     test('supports root trackRef for top-level graphs', () {
@@ -171,14 +171,14 @@ void main() {
 
       final person = Person()
         ..name = 'Ada'
-        ..age = Int32(36)
+        ..age = (36)
         ..favoriteColor = Color.blue;
 
       final bytes = fory.serialize(person);
       final roundTrip = fory.deserialize<Person>(bytes);
 
       expect(roundTrip.name, equals('Ada'));
-      expect(roundTrip.age, equals(Int32(36)));
+      expect(roundTrip.age, equals((36)));
       expect(roundTrip.favoriteColor, equals(Color.blue));
     });
   });
diff --git a/dart/packages/fory/README.md b/dart/packages/fory/README.md
index a44cbdd23..bacf4a2b4 100644
--- a/dart/packages/fory/README.md
+++ b/dart/packages/fory/README.md
@@ -14,9 +14,8 @@ cases.
 - Compatible mode for schema evolution
 - Optional reference tracking for shared and circular object graphs
 - Manual serializers for external types, custom payloads, and unions
-- Explicit xlang value class such as `Int32`, `Int64`, `Uint32`, `Uint64`,
-  `Float16`, `Bfloat16`, `Float32`, `LocalDate`, and `Timestamp`, plus `Duration`
-  support
+- Explicit exact-width value classes for `Int64`, `Uint64`, `Float16`,
+  `Bfloat16`, `Float32`, `LocalDate`, and `Timestamp`, plus `Duration` support
 
 ## Getting Started
 
@@ -50,7 +49,9 @@ class Person {
   Person();
 
   String name = '';
-  Int32 age = Int32(0);
+
+  @ForyField(type: Int32Type())
+  int age = 0;
   Color favoriteColor = Color.red;
   List<String> tags = <String>[];
 }
@@ -73,7 +74,7 @@ void main() {
 
   final person = Person()
     ..name = 'Ada'
-    ..age = Int32(36)
+    ..age = 36
     ..favoriteColor = Color.blue
     ..tags = <String>['engineer', 'mathematician'];
 
@@ -172,6 +173,17 @@ class NodeList {
 | `ref`      | Enable reference tracking for this field         |
 | `dynamic`  | Control whether runtime type metadata is written |
 
+`type:` is the canonical override surface for nested field semantics:
+
+```dart
+@MapField(
+  value: ListType(
+    element: Int32Type(encoding: Encoding.fixed),
+  ),
+)
+Map<String, List<int?>> nested = <String, List<int?>>{};
+```
+
 ## Customized Serializers
 
 Use `Serializer<T>` when a type cannot use generated struct support or when you
@@ -222,49 +234,49 @@ void main() {
 ## Type Mapping
 
 Dart has no native fixed-width 8/16/32-bit integer, unsigned 64-bit integer,
-or single-precision float types. Fory Dart provides thin wrapper types
-(`Int8`, `Int16`, `Int32`, `Int64`, `Uint8`, `Uint16`, `Uint32`, `Uint64`,
-`Float16`, `Bfloat16`, `Float32`) imported from `package:fory/fory.dart` to
-represent these xlang wire types. For 16-bit floating-point arrays, Dart exposes
-`Float16List` and `Bfloat16List` as contiguous fixed-length buffers.
-
-| Fory xlang type | Dart type                 |
-| --------------- | ------------------------- |
-| bool            | `bool`                    |
-| int8            | `fory.Int8` (wrapper)     |
-| int16           | `fory.Int16` (wrapper)    |
-| int32           | `fory.Int32` (wrapper)    |
-| int64           | `int` or `fory.Int64`     |
-| uint8           | `fory.Uint8` (wrapper)    |
-| uint16          | `fory.Uint16` (wrapper)   |
-| uint32          | `fory.Uint32` (wrapper)   |
-| uint64          | `fory.Uint64` (wrapper)   |
-| float16         | `fory.Float16` (wrapper)  |
-| bfloat16        | `fory.Bfloat16` (wrapper) |
-| float32         | `fory.Float32` (wrapper)  |
-| float64         | `double`                  |
-| string          | `String`                  |
-| binary          | `Uint8List`               |
-| duration        | `Duration`                |
-| local_date      | `LocalDate`               |
-| timestamp       | `Timestamp`               |
-| list            | `List`                    |
-| set             | `Set`                     |
-| map             | `Map`                     |
-| enum            | `enum`                    |
-| named_struct    | `class`                   |
-| bool_array      | `List<bool>`              |
-| int8_array      | `Int8List`                |
-| int16_array     | `Int16List`               |
-| int32_array     | `Int32List`               |
-| int64_array     | `Int64List`               |
-| uint16_array    | `Uint16List`              |
-| uint32_array    | `Uint32List`              |
-| uint64_array    | `Uint64List`              |
-| float16_array   | `Float16List`             |
-| bfloat16_array  | `Bfloat16List`            |
-| float32_array   | `Float32List`             |
-| float64_array   | `Float64List`             |
+or single-precision float types. Fory Dart uses plain Dart `int` plus field
+annotations for 8/16/32-bit integer fields, keeps `Int64` and `Uint64` for
+full-range 64-bit values, and uses wrapper value types for reduced-width
+floating point. For 16-bit floating-point arrays, Dart exposes `Float16List`
+and `Bfloat16List` as contiguous fixed-length buffers.
+
+| Fory xlang type | Dart type                                |
+| --------------- | ---------------------------------------- |
+| bool            | `bool`                                   |
+| int8            | `int` + `@ForyField(type: Int8Type())`   |
+| int16           | `int` + `@ForyField(type: Int16Type())`  |
+| int32           | `int` + `@ForyField(type: Int32Type())`  |
+| int64           | `int` or `fory.Int64`                    |
+| uint8           | `int` + `@ForyField(type: Uint8Type())`  |
+| uint16          | `int` + `@ForyField(type: Uint16Type())` |
+| uint32          | `int` + `@ForyField(type: Uint32Type())` |
+| uint64          | `fory.Uint64` (wrapper)                  |
+| float16         | `fory.Float16` (wrapper)                 |
+| bfloat16        | `fory.Bfloat16` (wrapper)                |
+| float32         | `fory.Float32` (wrapper)                 |
+| float64         | `double`                                 |
+| string          | `String`                                 |
+| binary          | `Uint8List`                              |
+| duration        | `Duration`                               |
+| local_date      | `LocalDate`                              |
+| timestamp       | `Timestamp`                              |
+| list            | `List`                                   |
+| set             | `Set`                                    |
+| map             | `Map`                                    |
+| enum            | `enum`                                   |
+| named_struct    | `class`                                  |
+| bool_array      | `List<bool>`                             |
+| int8_array      | `Int8List`                               |
+| int16_array     | `Int16List`                              |
+| int32_array     | `Int32List`                              |
+| int64_array     | `Int64List`                              |
+| uint16_array    | `Uint16List`                             |
+| uint32_array    | `Uint32List`                             |
+| uint64_array    | `Uint64List`                             |
+| float16_array   | `Float16List`                            |
+| bfloat16_array  | `Bfloat16List`                           |
+| float32_array   | `Float32List`                            |
+| float64_array   | `Float64List`                            |
 
 ## Public API
 
@@ -272,16 +284,16 @@ The main exported API includes:
 
 - `Fory` — main serialization facade
 - `Config` — runtime configuration
-- `ForyStruct`, `ForyField` — struct annotations
+- `ForyStruct`, `ForyField`, `ListField`, `SetField`, `MapField` — struct annotations
 - `ForyUnion` — union type annotation
 - `Serializer`, `UnionSerializer`, `EnumSerializer` — serializer base classes
 - `Buffer`, `WriteContext`, `ReadContext` — low-level I/O
-- `TypeSpec`, `ListType`, `MapType`, `ValueType` — nested container type
+- `TypeSpec`, `DeclaredType`, `ListType`, `SetType`, `MapType` — nested type
   annotations
-- `Int32Type`, `Int64Type`, `Uint32Type`, `Uint64Type` — numeric encoding
-  overrides
-- Numeric wrappers: `Int8`, `Int16`, `Int32`, `Int64`, `Uint8`, `Uint16`,
-  `Uint32`, `Uint64`, `Float16`, `Bfloat16`, `Float32`
+- `Int8Type`, `Int16Type`, `Int32Type`, `Int64Type`, `Uint8Type`, `Uint16Type`,
+  `Uint32Type`, `Uint64Type`, `Float16Type`, `Bfloat16Type`, `Float32Type` —
+  scalar wire-type overrides
+- Numeric value wrappers: `Int64`, `Uint64`, `Float16`, `Bfloat16`, `Float32`
 - Temporal types: `LocalDate`, `Timestamp`, `Duration`
 
 ## Cross-Language Notes
@@ -290,8 +302,8 @@ The main exported API includes:
 - Register user-defined types before serialization or deserialization.
 - Keep numeric IDs or `namespace + typeName` mappings consistent across
   languages.
-- Use wrappers or numeric field annotations when the exact xlang wire type
-  matters.
+- Use Dart `int` plus `@ForyField(type: ...)` for 8/16/32-bit integer fields,
+  and `Int64` / `Uint64` when full-range 64-bit values matter.
 
 For the xlang wire format and type mapping details, see the
 [Apache Fory specification](https://github.com/apache/fory/tree/main/docs/specification).
diff --git a/dart/packages/fory/example/example.dart b/dart/packages/fory/example/example.dart
index 6bb058e2f..c4214cbe2 100644
--- a/dart/packages/fory/example/example.dart
+++ b/dart/packages/fory/example/example.dart
@@ -31,7 +31,8 @@ class Person {
   Person();
 
   String name = '';
-  Int32 age = Int32(0);
+  @ForyField(type: Int32Type())
+  int age = 0;
   Color favoriteColor = Color.red;
   List<String> tags = <String>[];
 }
@@ -53,7 +54,7 @@ void main() {
 
   final person = Person()
     ..name = 'Ada'
-    ..age = Int32(36)
+    ..age = 36
     ..favoriteColor = Color.blue
     ..tags = <String>['engineer', 'mathematician'];
 
diff --git a/dart/packages/fory/lib/fory.dart b/dart/packages/fory/lib/fory.dart
index 5bc276e9b..74a7ba2b3 100644
--- a/dart/packages/fory/lib/fory.dart
+++ b/dart/packages/fory/lib/fory.dart
@@ -29,7 +29,6 @@ export 'src/annotation/fory_field.dart';
 export 'src/annotation/fory_struct.dart';
 export 'src/annotation/fory_union.dart';
 export 'src/annotation/type_spec.dart';
-export 'src/annotation/numeric_types.dart';
 export 'src/memory/buffer.dart'
     hide
         bufferByteData,
@@ -53,13 +52,7 @@ export 'src/types/bfloat16.dart';
 export 'src/types/decimal.dart';
 export 'src/types/float16.dart';
 export 'src/types/float32.dart';
-export 'src/types/int16.dart';
-export 'src/types/int32.dart';
 export 'src/types/int64.dart';
-export 'src/types/int8.dart';
 export 'src/types/local_date.dart';
 export 'src/types/timestamp.dart';
-export 'src/types/uint16.dart';
-export 'src/types/uint32.dart';
 export 'src/types/uint64.dart';
-export 'src/types/uint8.dart';
diff --git a/dart/packages/fory/lib/src/annotation/fory_field.dart b/dart/packages/fory/lib/src/annotation/fory_field.dart
index 555144c96..217689166 100644
--- a/dart/packages/fory/lib/src/annotation/fory_field.dart
+++ b/dart/packages/fory/lib/src/annotation/fory_field.dart
@@ -17,39 +17,78 @@
  * under the License.
  */
 
-/// Field-level code generation options for [ForyStruct].
+import 'package:fory/src/annotation/type_spec.dart';
+
 final class ForyField {
-  /// Skips this field entirely in generated serializers.
   final bool skip;
-
-  /// Stable field identifier used by compatible structs.
-  ///
-  /// When omitted, the generator derives an identifier from the field name.
   final int? id;
+  final bool? nullable;
+  final bool ref;
+  final bool? dynamic;
+  final TypeSpec? type;
+
+  const ForyField({
+    this.skip = false,
+    this.id,
+    this.nullable,
+    this.ref = false,
+    this.dynamic,
+    this.type,
+  });
+}
 
-  /// Overrides the generator's inferred nullability.
-  ///
-  /// `null` means "use the Dart type as written".
+final class ListField {
+  final bool skip;
+  final int? id;
   final bool? nullable;
+  final bool ref;
+  final bool? dynamic;
+  final TypeSpec? element;
 
-  /// Enables reference tracking for this field.
-  ///
-  /// Basic scalar types never track references even if this flag is `true`.
+  const ListField({
+    this.skip = false,
+    this.id,
+    this.nullable,
+    this.ref = false,
+    this.dynamic,
+    this.element,
+  });
+}
+
+final class SetField {
+  final bool skip;
+  final int? id;
+  final bool? nullable;
   final bool ref;
+  final bool? dynamic;
+  final TypeSpec? element;
 
-  /// Controls whether generated code writes runtime type metadata for this
-  /// field.
-  ///
-  /// `null` means "auto", `false` means "use the declared type", and `true`
-  /// means "write runtime type information".
+  const SetField({
+    this.skip = false,
+    this.id,
+    this.nullable,
+    this.ref = false,
+    this.dynamic,
+    this.element,
+  });
+}
+
+final class MapField {
+  final bool skip;
+  final int? id;
+  final bool? nullable;
+  final bool ref;
   final bool? dynamic;
+  final TypeSpec? key;
+  final TypeSpec? value;
 
-  /// Creates field-level generation overrides.
-  const ForyField({
+  const MapField({
     this.skip = false,
     this.id,
     this.nullable,
     this.ref = false,
     this.dynamic,
+    this.key,
+    this.value,
   });
 }
diff --git a/dart/packages/fory/lib/src/annotation/numeric_types.dart b/dart/packages/fory/lib/src/annotation/numeric_types.dart
deleted file mode 100644
index d965b34ca..000000000
--- a/dart/packages/fory/lib/src/annotation/numeric_types.dart
+++ /dev/null
@@ -1,80 +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.
- */
-
-/// Encodings available for 64-bit signed and unsigned integer fields.
-enum LongEncoding {
-  /// Always use the fixed-width 64-bit wire form.
-  fixed,
-
-  /// Use variable-length varint encoding.
-  varint,
-
-  /// Use the tagged compact-or-wide 64-bit encoding.
-  tagged,
-}
-
-/// Overrides the generated wire encoding for a 32-bit signed integer field.
-final class Int32Type {
-  /// Whether to use the compressed varint representation instead of fixed-width
-  /// `int32`.
-  final bool compress;
-
-  /// Creates an `int32` encoding override.
-  const Int32Type({this.compress = true});
-}
-
-/// Overrides the generated wire encoding for a 64-bit signed integer field.
-final class Int64Type {
-  /// Selected encoding for the field.
-  final LongEncoding encoding;
-
-  /// Creates an `int64` encoding override.
-  const Int64Type({this.encoding = LongEncoding.varint});
-}
-
-/// Marks a field as xlang `uint8`.
-final class Uint8Type {
-  /// Creates a `uint8` encoding override.
-  const Uint8Type();
-}
-
-/// Marks a field as xlang `uint16`.
-final class Uint16Type {
-  /// Creates a `uint16` encoding override.
-  const Uint16Type();
-}
-
-/// Overrides the generated wire encoding for a 32-bit unsigned integer field.
-final class Uint32Type {
-  /// Whether to use the compressed varuint representation instead of fixed-width
-  /// `uint32`.
-  final bool compress;
-
-  /// Creates a `uint32` encoding override.
-  const Uint32Type({this.compress = true});
-}
-
-/// Overrides the generated wire encoding for a 64-bit unsigned integer field.
-final class Uint64Type {
-  /// Selected encoding for the field.
-  final LongEncoding encoding;
-
-  /// Creates a `uint64` encoding override.
-  const Uint64Type({this.encoding = LongEncoding.varint});
-}
diff --git a/dart/packages/fory/lib/src/annotation/type_spec.dart b/dart/packages/fory/lib/src/annotation/type_spec.dart
index 189ab4159..5649e0339 100644
--- a/dart/packages/fory/lib/src/annotation/type_spec.dart
+++ b/dart/packages/fory/lib/src/annotation/type_spec.dart
@@ -17,95 +17,173 @@
  * under the License.
  */
 
-/// Type-level annotations for configuring nested container elements.
-///
-/// Use [ListType] and [MapType] on fields to override default ref-tracking
-/// and nullability for container elements, keys, and values.
 library;
 
-/// Option that modifies a type's serialization behavior.
-abstract class TypeOption {
-  const TypeOption();
+enum Encoding { fixed, varint, tagged }
 
-  const factory TypeOption.ref([bool tracked]) = RefOption;
-  const factory TypeOption.nullable([bool value]) = NullableOption;
+abstract class TypeSpec {
+  final bool? nullable;
+  final bool? ref;
+  final bool? dynamic;
+
+  const TypeSpec({
+    this.nullable,
+    this.ref,
+    this.dynamic,
+  });
 }
 
-/// Enables or disables reference tracking for a type.
-final class RefOption extends TypeOption {
-  final bool tracked;
-  const RefOption([this.tracked = true]);
+abstract class ScalarTypeSpec extends TypeSpec {
+  const ScalarTypeSpec({
+    super.nullable,
+    super.ref,
+    super.dynamic,
+  });
 }
 
-/// Overrides nullability for a type.
-final class NullableOption extends TypeOption {
-  final bool value;
-  const NullableOption([this.value = true]);
+final class DeclaredType extends TypeSpec {
+  const DeclaredType({
+    super.nullable,
+    super.ref,
+    super.dynamic,
+  });
 }
 
-/// Base class for type specifications that carry [TypeOption]s.
-abstract class TypeSpec {
-  final List<TypeOption> options;
-  const TypeSpec([this.options = const []]);
+final class BoolType extends ScalarTypeSpec {
+  const BoolType({super.nullable, super.ref, super.dynamic});
+}
+
+final class Int8Type extends ScalarTypeSpec {
+  const Int8Type({super.nullable, super.ref, super.dynamic});
+}
+
+final class Int16Type extends ScalarTypeSpec {
+  const Int16Type({super.nullable, super.ref, super.dynamic});
 }
 
-/// Specifies options for a scalar or object value type.
-final class ValueType extends TypeSpec {
-  const ValueType([super.options]);
+final class Int32Type extends ScalarTypeSpec {
+  final Encoding encoding;
+
+  const Int32Type({
+    this.encoding = Encoding.varint,
+    super.nullable,
+    super.ref,
+    super.dynamic,
+  });
+}
+
+final class Int64Type extends ScalarTypeSpec {
+  final Encoding encoding;
+
+  const Int64Type({
+    this.encoding = Encoding.varint,
+    super.nullable,
+    super.ref,
+    super.dynamic,
+  });
+}
+
+final class Uint8Type extends ScalarTypeSpec {
+  const Uint8Type({super.nullable, super.ref, super.dynamic});
+}
+
+final class Uint16Type extends ScalarTypeSpec {
+  const Uint16Type({super.nullable, super.ref, super.dynamic});
+}
+
+final class Uint32Type extends ScalarTypeSpec {
+  final Encoding encoding;
+
+  const Uint32Type({
+    this.encoding = Encoding.varint,
+    super.nullable,
+    super.ref,
+    super.dynamic,
+  });
+}
+
+final class Uint64Type extends ScalarTypeSpec {
+  final Encoding encoding;
+
+  const Uint64Type({
+    this.encoding = Encoding.varint,
+    super.nullable,
+    super.ref,
+    super.dynamic,
+  });
+}
 
-  const ValueType.ref() : super(const [TypeOption.ref()]);
-  const ValueType.noRef() : super(const [TypeOption.ref(false)]);
-  const ValueType.nullable() : super(const [TypeOption.nullable()]);
-  const ValueType.nonNullable() : super(const [TypeOption.nullable(false)]);
-  const ValueType.refNullable()
-      : super(const [TypeOption.ref(), TypeOption.nullable()]);
+final class Float16Type extends ScalarTypeSpec {
+  const Float16Type({super.nullable, super.ref, super.dynamic});
+}
+
+final class Bfloat16Type extends ScalarTypeSpec {
+  const Bfloat16Type({super.nullable, super.ref, super.dynamic});
+}
+
+final class Float32Type extends ScalarTypeSpec {
+  const Float32Type({super.nullable, super.ref, super.dynamic});
+}
+
+final class Float64Type extends ScalarTypeSpec {
+  const Float64Type({super.nullable, super.ref, super.dynamic});
+}
+
+final class StringType extends ScalarTypeSpec {
+  const StringType({super.nullable, super.ref, super.dynamic});
+}
+
+final class DecimalType extends ScalarTypeSpec {
+  const DecimalType({super.nullable, super.ref, super.dynamic});
+}
+
+final class TimestampType extends ScalarTypeSpec {
+  const TimestampType({super.nullable, super.ref, super.dynamic});
+}
+
+final class DateType extends ScalarTypeSpec {
+  const DateType({super.nullable, super.ref, super.dynamic});
+}
+
+final class DurationType extends ScalarTypeSpec {
+  const DurationType({super.nullable, super.ref, super.dynamic});
+}
+
+final class BinaryType extends ScalarTypeSpec {
+  const BinaryType({super.nullable, super.ref, super.dynamic});
 }
 
-/// Specifies options for a list or set field, including its element type.
 final class ListType extends TypeSpec {
-  final TypeSpec element;
+  final TypeSpec? element;
 
   const ListType({
-    this.element = const ValueType(),
-    List<TypeOption> options = const [],
-  }) : super(options);
-
-  const ListType.ref({
-    this.element = const ValueType(),
-  }) : super(const [TypeOption.ref()]);
+    this.element,
+    super.nullable,
+    super.ref,
+    super.dynamic,
+  });
+}
 
-  const ListType.noRef({
-    this.element = const ValueType(),
-  }) : super(const [TypeOption.ref(false)]);
+final class SetType extends TypeSpec {
+  final TypeSpec? element;
 
-  const ListType.nullable({
-    this.element = const ValueType(),
-  }) : super(const [TypeOption.nullable()]);
+  const SetType({
+    this.element,
+    super.nullable,
+    super.ref,
+    super.dynamic,
+  });
 }
 
-/// Specifies options for a map field, including its key and value types.
 final class MapType extends TypeSpec {
-  final TypeSpec key;
-  final TypeSpec value;
+  final TypeSpec? key;
+  final TypeSpec? value;
 
   const MapType({
-    this.key = const ValueType(),
-    this.value = const ValueType(),
-    List<TypeOption> options = const [],
-  }) : super(options);
-
-  const MapType.ref({
-    this.key = const ValueType(),
-    this.value = const ValueType(),
-  }) : super(const [TypeOption.ref()]);
-
-  const MapType.noRef({
-    this.key = const ValueType(),
-    this.value = const ValueType(),
-  }) : super(const [TypeOption.ref(false)]);
-
-  const MapType.nullable({
-    this.key = const ValueType(),
-    this.value = const ValueType(),
-  }) : super(const [TypeOption.nullable()]);
+    this.key,
+    this.value,
+    super.nullable,
+    super.ref,
+    super.dynamic,
+  });
 }
diff --git a/dart/packages/fory/lib/src/codegen/fory_generator.dart b/dart/packages/fory/lib/src/codegen/fory_generator.dart
index ae20f05ab..0b1d9683d 100644
--- a/dart/packages/fory/lib/src/codegen/fory_generator.dart
+++ b/dart/packages/fory/lib/src/codegen/fory_generator.dart
@@ -19,6 +19,7 @@
 
 import 'dart:async';
 
+import 'package:analyzer/dart/constant/value.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/nullability_suffix.dart';
 import 'package:analyzer/dart/element/type.dart';
@@ -53,23 +54,21 @@ final class ForyGenerator extends Generator {
   static final TypeChecker _foryFieldChecker = TypeChecker.fromRuntime(
     ForyField,
   );
-  static final TypeChecker _foryUnionChecker = TypeChecker.fromRuntime(
-    ForyUnion,
+  static final TypeChecker _listFieldChecker = TypeChecker.fromRuntime(
+    ListField,
   );
-  static final TypeChecker _listTypeChecker = TypeChecker.fromRuntime(ListType);
-  static final TypeChecker _mapTypeChecker = TypeChecker.fromRuntime(MapType);
-  static final TypeChecker _refOptionChecker = TypeChecker.fromRuntime(
-    RefOption,
+  static final TypeChecker _setFieldChecker = TypeChecker.fromRuntime(
+    SetField,
   );
-  static final TypeChecker _nullableOptionChecker = TypeChecker.fromRuntime(
-    NullableOption,
+  static final TypeChecker _mapFieldChecker = TypeChecker.fromRuntime(
+    MapField,
+  );
+  static final TypeChecker _typeSpecChecker = TypeChecker.fromRuntime(
+    TypeSpec,
+  );
+  static final TypeChecker _foryUnionChecker = TypeChecker.fromRuntime(
+    ForyUnion,
   );
-  static final TypeChecker _int32Checker = TypeChecker.fromRuntime(Int32Type);
-  static final TypeChecker _int64Checker = TypeChecker.fromRuntime(Int64Type);
-  static final TypeChecker _uint8Checker = TypeChecker.fromRuntime(Uint8Type);
-  static final TypeChecker _uint16Checker = TypeChecker.fromRuntime(Uint16Type);
-  static final TypeChecker _uint32Checker = TypeChecker.fromRuntime(Uint32Type);
-  static final TypeChecker _uint64Checker = TypeChecker.fromRuntime(Uint64Type);
 
   final Map<String, String> _importPrefixByLibraryIdentifier =
       <String, String>{};
@@ -159,7 +158,7 @@ final class ForyGenerator extends Generator {
   }
 
   _GeneratedFieldSpec _analyzeField(FieldElement field) {
-    final annotation = _foryFieldChecker.firstAnnotationOf(field);
+    final annotation = _fieldAnnotationOf(field);
     final reader = annotation == null ? null : ConstantReader(annotation);
     final idValue = reader?.peek('id');
     final nullableValue = reader?.peek('nullable');
@@ -172,9 +171,7 @@ final class ForyGenerator extends Generator {
         ? _autoDynamic(field.type)
         : dynamicValue.boolValue;
     final ref = reader?.peek('ref')?.boolValue ?? false;
-
-    // Read container TypeSpec annotations (@ListType / @MapType).
-    final typeSpec = _analyzeTypeSpecAnnotation(field);
+    final typeSpec = _analyzeTypeSpecAnnotation(field, reader);
 
     return _GeneratedFieldSpec(
       name: field.displayName,
@@ -194,7 +191,7 @@ final class ForyGenerator extends Generator {
         ref: ref,
         dynamic: dynamic,
         typeSpec: typeSpec,
-        integerAnnotation: _analyzeIntegerAnnotation(field),
+        errorElement: field,
       ),
     );
   }
@@ -205,70 +202,107 @@ final class ForyGenerator extends Generator {
     required bool ref,
     required bool? dynamic,
     _TypeSpecInfo? typeSpec,
-    _IntegerAnnotationSpec? integerAnnotation,
+    required Element errorElement,
   }) {
-    // If a TypeSpec annotation overrides this level, apply it.
     if (typeSpec != null) {
       final specRef = typeSpec.ref;
       if (specRef != null) ref = specRef;
       final specNullable = typeSpec.nullable;
       if (specNullable != null) nullable = specNullable;
+      final specDynamic = typeSpec.dynamic;
+      if (specDynamic != null) dynamic = specDynamic;
+    }
+    if (typeSpec == null && _isDefaultBoolArrayCarrier(type)) {
+      return _GeneratedFieldTypeSpec(
+        typeLiteral: _typeReferenceLiteral(type),
+        declaredTypeName: _typeReferenceLiteral(type),
+        typeId: TypeIds.boolArray,
+        nullable: nullable,
+        ref: ref,
+        dynamic: dynamic,
+        arguments: const <_GeneratedFieldTypeSpec>[],
+      );
     }
     if (_isList(type) || _isSet(type)) {
+      final expectedTypeId = _isSet(type) ? TypeIds.set : TypeIds.list;
+      if (typeSpec?.typeId != null && typeSpec!.typeId != expectedTypeId) {
+        throw InvalidGenerationSourceError(
+          'Type override ${_typeSpecName(typeSpec.typeId!)} does not match '
+          'the declared ${_isSet(type) ? 'Set' : 'List'} carrier.',
+          element: errorElement,
+        );
+      }
       final argument = (type as InterfaceType).typeArguments.single;
-      final elementSpec =
-          typeSpec is _ListTypeSpecInfo ? typeSpec.element : null;
+      final elementSpec = typeSpec?.element;
+      final child = _fieldTypeForType(
+        argument,
+        nullable: _isNullable(argument),
+        ref: false,
+        dynamic: _autoDynamic(argument),
+        typeSpec: elementSpec,
+        errorElement: errorElement,
+      );
+      _validatePackedListCarrier(type, child, errorElement);
       return _GeneratedFieldTypeSpec(
         typeLiteral: _typeReferenceLiteral(type),
         declaredTypeName: _typeReferenceLiteral(type),
-        typeId: _typeIdFor(type),
+        typeId: expectedTypeId,
         nullable: nullable,
         ref: ref,
         dynamic: dynamic,
-        arguments: <_GeneratedFieldTypeSpec>[
-          _fieldTypeForType(
-            argument,
-            nullable: true,
-            ref: elementSpec?.ref ?? false,
-            dynamic: _autoDynamic(argument),
-            typeSpec: elementSpec,
-          ),
-        ],
+        arguments: <_GeneratedFieldTypeSpec>[child],
       );
     }
     if (_isMap(type)) {
+      if (typeSpec?.typeId != null && typeSpec!.typeId != TypeIds.map) {
+        throw InvalidGenerationSourceError(
+          'Type override ${_typeSpecName(typeSpec.typeId!)} does not match '
+          'the declared Map carrier.',
+          element: errorElement,
+        );
+      }
       final arguments = (type as InterfaceType).typeArguments;
-      final keySpec = typeSpec is _MapTypeSpecInfo ? typeSpec.key : null;
-      final valueSpec = typeSpec is _MapTypeSpecInfo ? typeSpec.value : null;
+      final keySpec = typeSpec?.key;
+      final valueSpec = typeSpec?.value;
       return _GeneratedFieldTypeSpec(
         typeLiteral: _typeReferenceLiteral(type),
         declaredTypeName: _typeReferenceLiteral(type),
-        typeId: _typeIdFor(type),
+        typeId: TypeIds.map,
         nullable: nullable,
         ref: ref,
         dynamic: dynamic,
         arguments: <_GeneratedFieldTypeSpec>[
           _fieldTypeForType(
             arguments[0],
-            nullable: true,
-            ref: keySpec?.ref ?? false,
+            nullable: _isNullable(arguments[0]),
+            ref: false,
             dynamic: _autoDynamic(arguments[0]),
             typeSpec: keySpec,
+            errorElement: errorElement,
           ),
           _fieldTypeForType(
             arguments[1],
-            nullable: true,
-            ref: valueSpec?.ref ?? false,
+            nullable: _isNullable(arguments[1]),
+            ref: false,
             dynamic: _autoDynamic(arguments[1]),
             typeSpec: valueSpec,
+            errorElement: errorElement,
           ),
         ],
       );
     }
+    final typeId = typeSpec?.typeId ?? _typeIdFor(type);
+    _validateScalarTypeOverride(
+      type,
+      typeId,
+      dynamic,
+      typeSpec,
+      errorElement,
+    );
     return _GeneratedFieldTypeSpec(
       typeLiteral: _typeReferenceLiteral(type),
       declaredTypeName: _typeReferenceLiteral(type),
-      typeId: _typeIdFor(type, integerAnnotation: integerAnnotation),
+      typeId: typeId,
       nullable: nullable,
       ref: ref,
       dynamic: dynamic,
@@ -870,7 +904,7 @@ final class ForyGenerator extends Generator {
   }
 
   bool _isSkipped(FieldElement field) {
-    final annotation = _foryFieldChecker.firstAnnotationOf(field);
+    final annotation = _fieldAnnotationOf(field);
     if (annotation == null) {
       return false;
     }
@@ -992,6 +1026,9 @@ GeneratedFieldType(
     String valueExpression,
   ) {
     if (_isList(type)) {
+      if (fieldType.typeId != TypeIds.list) {
+        return '$valueExpression as ${_typeCodeString(type)}';
+      }
       final elementType = (type as InterfaceType).typeArguments.single;
       final elementFieldType = fieldType.arguments.single;
       if (_supportsDirectContainerCast(elementType, elementFieldType)) {
@@ -1006,6 +1043,9 @@ GeneratedFieldType(
       return 'List<${_typeCodeString(elementType)}>.of((($valueExpression as List)).map((item) => $convertedElement))';
     }
     if (_isSet(type)) {
+      if (fieldType.typeId != TypeIds.set) {
+        return '$valueExpression as ${_typeCodeString(type)}';
+      }
       final elementType = (type as InterfaceType).typeArguments.single;
       final elementFieldType = fieldType.arguments.single;
       if (_supportsDirectContainerCast(elementType, elementFieldType)) {
@@ -1045,24 +1085,10 @@ GeneratedFieldType(
     }
     if (type.isDartCoreInt) {
       switch (fieldType.typeId) {
-        case TypeIds.int8:
-          return 'switch ($valueExpression) { int typed => typed, Int8 typed => typed.value, _ => throw StateError(\'Expected int or Int8.\') }';
-        case TypeIds.int16:
-          return 'switch ($valueExpression) { int typed => typed, Int16 typed => typed.value, _ => throw StateError(\'Expected int or Int16.\') }';
-        case TypeIds.int32:
-        case TypeIds.varInt32:
-          return 'switch ($valueExpression) { int typed => typed, Int32 typed => typed.value, _ => throw StateError(\'Expected int or Int32.\') }';
         case TypeIds.int64:
         case TypeIds.varInt64:
         case TypeIds.taggedInt64:
           return 'switch ($valueExpression) { Int64 typed => typed.toInt(), int typed => typed, _ => throw StateError(\'Expected int or Int64.\') }';
-        case TypeIds.uint8:
-          return 'switch ($valueExpression) { int typed => typed, Uint8 typed => typed.value, _ => throw StateError(\'Expected int or Uint8.\') }';
-        case TypeIds.uint16:
-          return 'switch ($valueExpression) { int typed => typed, Uint16 typed => typed.value, _ => throw StateError(\'Expected int or Uint16.\') }';
-        case TypeIds.uint32:
-        case TypeIds.varUint32:
-          return 'switch ($valueExpression) { int typed => typed, Uint32 typed => typed.value, _ => throw StateError(\'Expected int or Uint32.\') }';
         case TypeIds.uint64:
         case TypeIds.varUint64:
         case TypeIds.taggedUint64:
@@ -1100,8 +1126,7 @@ GeneratedFieldType(
       return false;
     }
     if (type.isDartCoreInt) {
-      return fieldType.typeId != TypeIds.int32 &&
-          fieldType.typeId != TypeIds.varInt32;
+      return true;
     }
     return true;
   }
@@ -1444,17 +1469,13 @@ GeneratedFieldType(
       case TypeIds.boolType:
         return 'buffer.readBool()';
       case TypeIds.int8:
-        return 'Int8(buffer.readByte())';
+        return 'buffer.readByte()';
       case TypeIds.int16:
-        return 'Int16(buffer.readInt16())';
+        return 'buffer.readInt16()';
       case TypeIds.int32:
-        return field.type.isDartCoreInt
-            ? 'buffer.readInt32()'
-            : 'Int32(buffer.readInt32())';
+        return 'buffer.readInt32()';
       case TypeIds.varInt32:
-        return field.type.isDartCoreInt
-            ? 'buffer.readVarInt32()'
-            : 'Int32(buffer.readVarInt32())';
+        return 'buffer.readVarInt32()';
       case TypeIds.int64:
         return field.type.isDartCoreInt
             ? 'buffer.readInt64AsInt()'
@@ -1468,21 +1489,13 @@ GeneratedFieldType(
             ? 'buffer.readTaggedInt64AsInt()'
             : 'buffer.readTaggedInt64()';
       case TypeIds.uint8:
-        return field.type.isDartCoreInt
-            ? 'buffer.readUint8()'
-            : 'Uint8(buffer.readUint8())';
+        return 'buffer.readUint8()';
       case TypeIds.uint16:
-        return field.type.isDartCoreInt
-            ? 'buffer.readUint16()'
-            : 'Uint16(buffer.readUint16())';
+        return 'buffer.readUint16()';
       case TypeIds.uint32:
-        return field.type.isDartCoreInt
-            ? 'buffer.readUint32()'
-            : 'Uint32(buffer.readUint32())';
+        return 'buffer.readUint32()';
       case TypeIds.varUint32:
-        return field.type.isDartCoreInt
-            ? 'buffer.readVarUint32()'
-            : 'Uint32(buffer.readVarUint32())';
+        return 'buffer.readVarUint32()';
       case TypeIds.uint64:
         return field.type.isDartCoreInt
             ? 'buffer.readUint64().toInt()'
@@ -1562,17 +1575,13 @@ GeneratedFieldType(
       case TypeIds.boolType:
         return '$cursorExpression.readBool()';
       case TypeIds.int8:
-        return 'Int8($cursorExpression.readByte())';
+        return '$cursorExpression.readByte()';
       case TypeIds.int16:
-        return 'Int16($cursorExpression.readInt16())';
+        return '$cursorExpression.readInt16()';
       case TypeIds.int32:
-        return field.type.isDartCoreInt
-            ? '$cursorExpression.readInt32()'
-            : 'Int32($cursorExpression.readInt32())';
+        return '$cursorExpression.readInt32()';
       case TypeIds.varInt32:
-        return field.type.isDartCoreInt
-            ? '$cursorExpression.readVarInt32()'
-            : 'Int32($cursorExpression.readVarInt32())';
+        return '$cursorExpression.readVarInt32()';
       case TypeIds.int64:
         return field.type.isDartCoreInt
             ? '$cursorExpression.readInt64AsInt()'
@@ -1586,21 +1595,13 @@ GeneratedFieldType(
             ? '$cursorExpression.readTaggedInt64AsInt()'
             : '$cursorExpression.readTaggedInt64()';
       case TypeIds.uint8:
-        return field.type.isDartCoreInt
-            ? '$cursorExpression.readUint8()'
-            : 'Uint8($cursorExpression.readUint8())';
+        return '$cursorExpression.readUint8()';
       case TypeIds.uint16:
-        return field.type.isDartCoreInt
-            ? '$cursorExpression.readUint16()'
-            : 'Uint16($cursorExpression.readUint16())';
+        return '$cursorExpression.readUint16()';
       case TypeIds.uint32:
-        return field.type.isDartCoreInt
-            ? '$cursorExpression.readUint32()'
-            : 'Uint32($cursorExpression.readUint32())';
+        return '$cursorExpression.readUint32()';
       case TypeIds.varUint32:
-        return field.type.isDartCoreInt
-            ? '$cursorExpression.readVarUint32()'
-            : 'Uint32($cursorExpression.readVarUint32())';
+        return '$cursorExpression.readVarUint32()';
       case TypeIds.uint64:
         return field.type.isDartCoreInt
             ? '$cursorExpression.readUint64AsInt()'
@@ -1677,7 +1678,8 @@ GeneratedFieldType(
         case TypeIds.taggedUint64:
           return 'Uint64($valueExpression)';
         default:
-          return valueExpression;
+          return _checkedGeneratedScalarExpression(
+              field.fieldType.typeId, valueExpression);
       }
     }
     if (field.type.isDartCoreDouble ||
@@ -1703,24 +1705,8 @@ GeneratedFieldType(
   String _generatedFieldInfoWriteValueExpression(
     _GeneratedFieldSpec field,
     String valueExpression,
-  ) {
-    if (!_withoutNullability(field.type).isDartCoreInt) {
-      return valueExpression;
-    }
-    final wrapper = switch (field.fieldType.typeId) {
-      TypeIds.int8 => 'Int8',
-      TypeIds.int16 => 'Int16',
-      TypeIds.int32 || TypeIds.varInt32 => 'Int32',
-      _ => null,
-    };
-    if (wrapper == null) {
-      return valueExpression;
-    }
-    if (_isNullable(field.type)) {
-      return '$valueExpression == null ? null : $wrapper($valueExpression!)';
-    }
-    return '$wrapper($valueExpression)';
-  }
+  ) =>
+      valueExpression;
 
   String _nullExpression(
     DartType type, {
@@ -2031,148 +2017,566 @@ GeneratedFieldType(
     }
   }
 
-  _IntegerAnnotationSpec? _analyzeIntegerAnnotation(FieldElement field) {
-    final int32Annotation = _int32Checker.firstAnnotationOf(field);
-    if (int32Annotation != null) {
-      final reader = ConstantReader(int32Annotation);
-      final compress = reader.peek('compress')?.boolValue ?? true;
-      return _IntegerAnnotationSpec(
-        typeId: compress ? TypeIds.varInt32 : TypeIds.int32,
+  DartObject? _fieldAnnotationOf(FieldElement field) {
+    for (final metadata in field.metadata) {
+      final annotation = metadata.computeConstantValue();
+      final annotationType = annotation?.type;
+      if (annotationType != null &&
+          _typeSpecChecker.isAssignableFromType(annotationType)) {
+        throw InvalidGenerationSourceError(
+          'Standalone type-spec annotations like @${annotationType.element?.displayName ?? 'TypeSpec'}() '
+          'are not supported. Use @ForyField(type: ...) or container field sugar instead.',
+          element: field,
+        );
+      }
+    }
+    final annotations = <DartObject?>[
+      _foryFieldChecker.firstAnnotationOf(field),
+      _listFieldChecker.firstAnnotationOf(field),
+      _setFieldChecker.firstAnnotationOf(field),
+      _mapFieldChecker.firstAnnotationOf(field),
+    ].whereType<DartObject>().toList(growable: false);
+    if (annotations.length > 1) {
+      throw InvalidGenerationSourceError(
+        'Use only one of @ForyField, @ListField, @SetField, or @MapField on a field.',
+        element: field,
       );
     }
-    final int64Annotation = _int64Checker.firstAnnotationOf(field);
-    if (int64Annotation != null) {
-      final reader = ConstantReader(int64Annotation);
-      final encodingReader = reader.peek('encoding');
-      final encodingValue = encodingReader == null || encodingReader.isNull
-          ? 'varint'
-          : encodingReader.revive().accessor.split('.').last;
-      final typeId = switch (encodingValue) {
-        'varint' => TypeIds.varInt64,
-        'tagged' => TypeIds.taggedInt64,
-        'fixed' => TypeIds.int64,
-        _ => throw InvalidGenerationSourceError(
-            'Unsupported Int64Type encoding: $encodingValue.',
-            element: field,
-          ),
-      };
-      return _IntegerAnnotationSpec(typeId: typeId);
-    }
-    final uint8Annotation = _uint8Checker.firstAnnotationOf(field);
-    if (uint8Annotation != null) {
-      return const _IntegerAnnotationSpec(typeId: TypeIds.uint8);
-    }
-    final uint16Annotation = _uint16Checker.firstAnnotationOf(field);
-    if (uint16Annotation != null) {
-      return const _IntegerAnnotationSpec(typeId: TypeIds.uint16);
-    }
-    final uint32Annotation = _uint32Checker.firstAnnotationOf(field);
-    if (uint32Annotation != null) {
-      final reader = ConstantReader(uint32Annotation);
-      final compress = reader.peek('compress')?.boolValue ?? true;
-      return _IntegerAnnotationSpec(
-        typeId: compress ? TypeIds.varUint32 : TypeIds.uint32,
+    return annotations.isEmpty ? null : annotations.single;
+  }
+
+  _TypeSpecInfo? _analyzeTypeSpecAnnotation(
+    FieldElement field,
+    ConstantReader? reader,
+  ) {
+    final annotation = _fieldAnnotationOf(field);
+    if (annotation == null || reader == null) {
+      return null;
+    }
+    final annotationType = annotation.type;
+    if (annotationType != null &&
+        _foryFieldChecker.isExactlyType(annotationType)) {
+      final typeReader = reader.peek('type');
+      if (typeReader == null || typeReader.isNull) {
+        return null;
+      }
+      final typeSpec = _readTypeSpecObj(typeReader, field);
+      _validateRootTypeSpecConflicts(field, reader, typeSpec);
+      return typeSpec;
+    }
+    if (annotationType != null &&
+        _listFieldChecker.isExactlyType(annotationType)) {
+      return _TypeSpecInfo(
+        typeId: TypeIds.list,
+        element: _readOptionalTypeSpec(reader.peek('element'), field),
       );
     }
-    final uint64Annotation = _uint64Checker.firstAnnotationOf(field);
-    if (uint64Annotation != null) {
-      final reader = ConstantReader(uint64Annotation);
-      final encodingReader = reader.peek('encoding');
-      final encodingValue = encodingReader == null || encodingReader.isNull
-          ? 'varint'
-          : encodingReader.revive().accessor.split('.').last;
-      final typeId = switch (encodingValue) {
-        'varint' => TypeIds.varUint64,
-        'tagged' => TypeIds.taggedUint64,
-        'fixed' => TypeIds.uint64,
-        _ => throw InvalidGenerationSourceError(
-            'Unsupported Uint64Type encoding: $encodingValue.',
-            element: field,
-          ),
-      };
-      return _IntegerAnnotationSpec(typeId: typeId);
+    if (annotationType != null &&
+        _setFieldChecker.isExactlyType(annotationType)) {
+      return _TypeSpecInfo(
+        typeId: TypeIds.set,
+        element: _readOptionalTypeSpec(reader.peek('element'), field),
+      );
+    }
+    if (annotationType != null &&
+        _mapFieldChecker.isExactlyType(annotationType)) {
+      return _TypeSpecInfo(
+        typeId: TypeIds.map,
+        key: _readOptionalTypeSpec(reader.peek('key'), field),
+        value: _readOptionalTypeSpec(reader.peek('value'), field),
+      );
     }
     return null;
   }
 
-  _TypeSpecInfo? _analyzeTypeSpecAnnotation(FieldElement field) {
-    final listAnnotation = _listTypeChecker.firstAnnotationOf(field);
-    if (listAnnotation != null) {
-      return _readListTypeSpec(ConstantReader(listAnnotation));
+  _TypeSpecInfo? _readOptionalTypeSpec(ConstantReader? reader, Element field) {
+    if (reader == null || reader.isNull) {
+      return null;
     }
-    final mapAnnotation = _mapTypeChecker.firstAnnotationOf(field);
-    if (mapAnnotation != null) {
-      return _readMapTypeSpec(ConstantReader(mapAnnotation));
+    return _readTypeSpecObj(reader, field);
+  }
+
+  _TypeSpecInfo _readTypeSpecObj(ConstantReader reader, Element field) {
+    final objType = reader.objectValue.type;
+    final typeName = objType?.element?.displayName;
+    final nullable = _readBoolOverride(reader.peek('nullable'));
+    final ref = _readBoolOverride(reader.peek('ref'));
+    final dynamic = _readBoolOverride(reader.peek('dynamic'));
+    switch (typeName) {
+      case 'DeclaredType':
+        return _TypeSpecInfo(
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'ListType':
+        return _TypeSpecInfo(
+          typeId: TypeIds.list,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+          element: _readOptionalTypeSpec(reader.peek('element'), field),
+        );
+      case 'SetType':
+        return _TypeSpecInfo(
+          typeId: TypeIds.set,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+          element: _readOptionalTypeSpec(reader.peek('element'), field),
+        );
+      case 'MapType':
+        return _TypeSpecInfo(
+          typeId: TypeIds.map,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+          key: _readOptionalTypeSpec(reader.peek('key'), field),
+          value: _readOptionalTypeSpec(reader.peek('value'), field),
+        );
+      case 'BoolType':
+        return _TypeSpecInfo(
+          typeId: TypeIds.boolType,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'Int8Type':
+        return _TypeSpecInfo(
+          typeId: TypeIds.int8,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'Int16Type':
+        return _TypeSpecInfo(
+          typeId: TypeIds.int16,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'Int32Type':
+        return _TypeSpecInfo(
+          typeId: _encodingTypeId(
+            reader,
+            fixed: TypeIds.int32,
+            varint: TypeIds.varInt32,
+            field: field,
+          ),
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'Int64Type':
+        return _TypeSpecInfo(
+          typeId: _encodingTypeId(
+            reader,
+            fixed: TypeIds.int64,
+            varint: TypeIds.varInt64,
+            tagged: TypeIds.taggedInt64,
+            field: field,
+          ),
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'Uint8Type':
+        return _TypeSpecInfo(
+          typeId: TypeIds.uint8,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'Uint16Type':
+        return _TypeSpecInfo(
+          typeId: TypeIds.uint16,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'Uint32Type':
+        return _TypeSpecInfo(
+          typeId: _encodingTypeId(
+            reader,
+            fixed: TypeIds.uint32,
+            varint: TypeIds.varUint32,
+            field: field,
+          ),
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'Uint64Type':
+        return _TypeSpecInfo(
+          typeId: _encodingTypeId(
+            reader,
+            fixed: TypeIds.uint64,
+            varint: TypeIds.varUint64,
+            tagged: TypeIds.taggedUint64,
+            field: field,
+          ),
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'Float16Type':
+        return _TypeSpecInfo(
+          typeId: TypeIds.float16,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'Bfloat16Type':
+        return _TypeSpecInfo(
+          typeId: TypeIds.bfloat16,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'Float32Type':
+        return _TypeSpecInfo(
+          typeId: TypeIds.float32,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'Float64Type':
+        return _TypeSpecInfo(
+          typeId: TypeIds.float64,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'StringType':
+        return _TypeSpecInfo(
+          typeId: TypeIds.string,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'DecimalType':
+        return _TypeSpecInfo(
+          typeId: TypeIds.decimal,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'TimestampType':
+        return _TypeSpecInfo(
+          typeId: TypeIds.timestamp,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'DateType':
+        return _TypeSpecInfo(
+          typeId: TypeIds.date,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'DurationType':
+        return _TypeSpecInfo(
+          typeId: TypeIds.duration,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      case 'BinaryType':
+        return _TypeSpecInfo(
+          typeId: TypeIds.binary,
+          nullable: nullable,
+          ref: ref,
+          dynamic: dynamic,
+        );
+      default:
+        throw InvalidGenerationSourceError(
+          'Unsupported type spec ${typeName ?? reader.objectValue.toString()}.',
+          element: field,
+        );
     }
-    return null;
   }
 
-  _ListTypeSpecInfo _readListTypeSpec(ConstantReader reader) {
-    final options = _readTypeOptions(reader);
-    final elementObj = reader.peek('element');
-    final element = elementObj != null && !elementObj.isNull
-        ? _readTypeSpecObj(elementObj)
-        : null;
-    return _ListTypeSpecInfo(
-      ref: options.ref,
-      nullable: options.nullable,
-      element: element,
-    );
+  bool? _readBoolOverride(ConstantReader? reader) {
+    if (reader == null || reader.isNull) {
+      return null;
+    }
+    return reader.boolValue;
   }
 
-  _MapTypeSpecInfo _readMapTypeSpec(ConstantReader reader) {
-    final options = _readTypeOptions(reader);
-    final keyObj = reader.peek('key');
-    final valueObj = reader.peek('value');
-    final key =
-        keyObj != null && !keyObj.isNull ? _readTypeSpecObj(keyObj) : null;
-    final value = valueObj != null && !valueObj.isNull
-        ? _readTypeSpecObj(valueObj)
-        : null;
-    return _MapTypeSpecInfo(
-      ref: options.ref,
-      nullable: options.nullable,
-      key: key,
-      value: value,
+  int _encodingTypeId(
+    ConstantReader reader, {
+    required int fixed,
+    required int varint,
+    int? tagged,
+    required Element field,
+  }) {
+    final encodingReader = reader.peek('encoding');
+    final encodingValue = encodingReader == null || encodingReader.isNull
+        ? 'varint'
+        : encodingReader.revive().accessor.split('.').last;
+    return switch (encodingValue) {
+      'fixed' => fixed,
+      'varint' => varint,
+      'tagged' when tagged != null => tagged,
+      _ => throw InvalidGenerationSourceError(
+          'Unsupported encoding $encodingValue for type spec.',
+          element: field,
+        ),
+    };
+  }
+
+  void _validateRootTypeSpecConflicts(
+    FieldElement field,
+    ConstantReader reader,
+    _TypeSpecInfo typeSpec,
+  ) {
+    final fieldNullable = reader.peek('nullable');
+    if (fieldNullable != null &&
+        !fieldNullable.isNull &&
+        typeSpec.nullable != null &&
+        fieldNullable.boolValue != typeSpec.nullable) {
+      throw InvalidGenerationSourceError(
+        'Field nullable conflicts with root type nullable override.',
+        element: field,
+      );
+    }
+    final fieldRef = reader.peek('ref');
+    if (fieldRef != null &&
+        !fieldRef.isNull &&
+        typeSpec.ref != null &&
+        fieldRef.boolValue != typeSpec.ref) {
+      throw InvalidGenerationSourceError(
+        'Field ref conflicts with root type ref override.',
+        element: field,
+      );
+    }
+    final fieldDynamic = reader.peek('dynamic');
+    if (fieldDynamic != null &&
+        !fieldDynamic.isNull &&
+        typeSpec.dynamic != null &&
+        fieldDynamic.boolValue != typeSpec.dynamic) {
+      throw InvalidGenerationSourceError(
+        'Field dynamic conflicts with root type dynamic override.',
+        element: field,
+      );
+    }
+  }
+
+  bool _isDefaultBoolArrayCarrier(DartType type) {
+    if (!_isList(type) || _isNullable(type)) {
+      return false;
+    }
+    final argument = (type as InterfaceType).typeArguments.single;
+    return !_isNullable(argument) &&
+        _withoutNullability(argument).isDartCoreBool;
+  }
+
+  void _validatePackedListCarrier(
+    DartType listOrSetType,
+    _GeneratedFieldTypeSpec elementType,
+    Element errorElement,
+  ) {
+    if (!_isList(listOrSetType)) {
+      return;
+    }
+    if (elementType.nullable ||
+        elementType.ref ||
+        elementType.dynamic == true) {
+      return;
+    }
+    final suggestedCarrier = switch (elementType.typeId) {
+      TypeIds.int8 => 'Int8List',
+      TypeIds.int16 => 'Int16List',
+      TypeIds.int32 => 'Int32List',
+      TypeIds.int64 => 'Int64List',
+      TypeIds.uint8 => 'Uint8List',
+      TypeIds.uint16 => 'Uint16List',
+      TypeIds.uint32 => 'Uint32List',
+      TypeIds.uint64 => 'Uint64List',
+      TypeIds.float16 => 'Float16List',
+      TypeIds.bfloat16 => 'Bfloat16List',
+      TypeIds.float32 => 'Float32List',
+      TypeIds.float64 => 'Float64List',
+      _ => null,
+    };
+    if (suggestedCarrier == null) {
+      return;
+    }
+    throw InvalidGenerationSourceError(
+      'Generic List fields with non-null fixed-width ${_typeSpecName(elementType.typeId)} '
+      'elements must use $suggestedCarrier instead.',
+      element: errorElement,
     );
   }
 
-  _TypeSpecInfo _readTypeSpecObj(ConstantReader reader) {
-    final objType = reader.objectValue.type;
-    if (objType != null && _listTypeChecker.isExactlyType(objType)) {
-      return _readListTypeSpec(reader);
-    }
-    if (objType != null && _mapTypeChecker.isExactlyType(objType)) {
-      return _readMapTypeSpec(reader);
-    }
-    // ValueType or fallback
-    final options = _readTypeOptions(reader);
-    return _TypeSpecInfo(ref: options.ref, nullable: options.nullable);
-  }
-
-  ({bool? ref, bool? nullable}) _readTypeOptions(ConstantReader reader) {
-    bool? ref;
-    bool? nullable;
-    final optionsReader = reader.peek('options');
-    if (optionsReader != null && !optionsReader.isNull) {
-      for (final optionObj in optionsReader.listValue) {
-        final optionReader = ConstantReader(optionObj);
-        final optionType = optionObj.type;
-        if (optionType != null && _refOptionChecker.isExactlyType(optionType)) {
-          ref = optionReader.peek('tracked')?.boolValue ?? true;
-        } else if (optionType != null &&
-            _nullableOptionChecker.isExactlyType(optionType)) {
-          nullable = optionReader.peek('value')?.boolValue ?? true;
-        }
-      }
+  void _validateScalarTypeOverride(
+    DartType type,
+    int typeId,
+    bool? dynamic,
+    _TypeSpecInfo? typeSpec,
+    Element errorElement,
+  ) {
+    final declaredOnlyOverride = typeSpec != null && typeSpec.typeId == null;
+    if (dynamic == true &&
+        (TypeIds.isPrimitive(typeId) || _isBuiltInTypeId(typeId))) {
+      throw InvalidGenerationSourceError(
+        'dynamic: true is not valid for fixed scalar or built-in leaf type ${_typeSpecName(typeId)}.',
+        element: errorElement,
+      );
+    }
+    if (typeId == TypeIds.list ||
+        typeId == TypeIds.set ||
+        typeId == TypeIds.map) {
+      throw InvalidGenerationSourceError(
+        'Type override ${_typeSpecName(typeId)} does not match the declared ${_typeCodeString(type)} carrier.',
+        element: errorElement,
+      );
+    }
+    final nonNullable = _withoutNullability(type);
+    final valid = switch (_typeLiteral(nonNullable)) {
+      'bool' => typeId == TypeIds.boolType,
+      'int' => _isSupportedIntTypeId(typeId),
+      'double' => typeId == TypeIds.float32 || typeId == TypeIds.float64,
+      'String' => typeId == TypeIds.string,
+      'Int64' => _isSigned64TypeId(typeId),
+      'Uint64' => _isUnsigned64TypeId(typeId),
+      'Float16' => typeId == TypeIds.float16,
+      'Bfloat16' => typeId == TypeIds.bfloat16,
+      'Float32' => typeId == TypeIds.float32,
+      'Decimal' => typeId == TypeIds.decimal,
+      'Timestamp' || 'DateTime' => typeId == TypeIds.timestamp,
+      'LocalDate' => typeId == TypeIds.date,
+      'Duration' => typeId == TypeIds.duration,
+      'Uint8List' => typeId == TypeIds.binary || typeId == TypeIds.uint8Array,
+      'Int8List' => typeId == TypeIds.int8Array,
+      'Int16List' => typeId == TypeIds.int16Array,
+      'Int32List' => typeId == TypeIds.int32Array,
+      'Int64List' => typeId == TypeIds.int64Array,
+      'Uint16List' => typeId == TypeIds.uint16Array,
+      'Uint32List' => typeId == TypeIds.uint32Array,
+      'Uint64List' => typeId == TypeIds.uint64Array,
+      'Float16List' => typeId == TypeIds.float16Array,
+      'Bfloat16List' => typeId == TypeIds.bfloat16Array,
+      'Float32List' => typeId == TypeIds.float32Array,
+      'Float64List' => typeId == TypeIds.float64Array,
+      _ => typeSpec == null || declaredOnlyOverride,
+    };
+    if (!valid) {
+      throw InvalidGenerationSourceError(
+        'Type override ${_typeSpecName(typeId)} is not valid for declared Dart type ${_typeCodeString(type)}.',
+        element: errorElement,
+      );
+    }
+  }
+
+  bool _isSupportedIntTypeId(int typeId) =>
+      typeId == TypeIds.int8 ||
+      typeId == TypeIds.int16 ||
+      typeId == TypeIds.int32 ||
+      typeId == TypeIds.varInt32 ||
+      _isSigned64TypeId(typeId) ||
+      typeId == TypeIds.uint8 ||
+      typeId == TypeIds.uint16 ||
+      typeId == TypeIds.uint32 ||
+      typeId == TypeIds.varUint32 ||
+      _isUnsigned64TypeId(typeId);
+
+  bool _isSigned64TypeId(int typeId) =>
+      typeId == TypeIds.int64 ||
+      typeId == TypeIds.varInt64 ||
+      typeId == TypeIds.taggedInt64;
+
+  bool _isUnsigned64TypeId(int typeId) =>
+      typeId == TypeIds.uint64 ||
+      typeId == TypeIds.varUint64 ||
+      typeId == TypeIds.taggedUint64;
+
+  String _checkedGeneratedScalarExpression(int typeId, String valueExpression) {
+    switch (typeId) {
+      case TypeIds.int8:
+        return 'generatedCheckedInt8($valueExpression)';
+      case TypeIds.int16:
+        return 'generatedCheckedInt16($valueExpression)';
+      case TypeIds.int32:
+      case TypeIds.varInt32:
+        return 'generatedCheckedInt32($valueExpression)';
+      case TypeIds.uint8:
+        return 'generatedCheckedUint8($valueExpression)';
+      case TypeIds.uint16:
+        return 'generatedCheckedUint16($valueExpression)';
+      case TypeIds.uint32:
+      case TypeIds.varUint32:
+        return 'generatedCheckedUint32($valueExpression)';
+      default:
+        return valueExpression;
     }
-    return (ref: ref, nullable: nullable);
   }
 
-  int _typeIdFor(DartType type, {_IntegerAnnotationSpec? integerAnnotation}) {
-    if (integerAnnotation != null) {
-      return integerAnnotation.typeId;
+  String _typeSpecName(int typeId) {
+    switch (typeId) {
+      case TypeIds.boolType:
+        return 'BoolType';
+      case TypeIds.int8:
+        return 'Int8Type';
+      case TypeIds.int16:
+        return 'Int16Type';
+      case TypeIds.int32:
+        return 'Int32Type(encoding: Encoding.fixed)';
+      case TypeIds.varInt32:
+        return 'Int32Type(encoding: Encoding.varint)';
+      case TypeIds.int64:
+        return 'Int64Type(encoding: Encoding.fixed)';
+      case TypeIds.varInt64:
+        return 'Int64Type(encoding: Encoding.varint)';
+      case TypeIds.taggedInt64:
+        return 'Int64Type(encoding: Encoding.tagged)';
+      case TypeIds.uint8:
+        return 'Uint8Type';
+      case TypeIds.uint16:
+        return 'Uint16Type';
+      case TypeIds.uint32:
+        return 'Uint32Type(encoding: Encoding.fixed)';
+      case TypeIds.varUint32:
+        return 'Uint32Type(encoding: Encoding.varint)';
+      case TypeIds.uint64:
+        return 'Uint64Type(encoding: Encoding.fixed)';
+      case TypeIds.varUint64:
+        return 'Uint64Type(encoding: Encoding.varint)';
+      case TypeIds.taggedUint64:
+        return 'Uint64Type(encoding: Encoding.tagged)';
+      case TypeIds.float16:
+        return 'Float16Type';
+      case TypeIds.bfloat16:
+        return 'Bfloat16Type';
+      case TypeIds.float32:
+        return 'Float32Type';
+      case TypeIds.float64:
+        return 'Float64Type';
+      case TypeIds.string:
+        return 'StringType';
+      case TypeIds.binary:
+        return 'BinaryType';
+      case TypeIds.decimal:
+        return 'DecimalType';
+      case TypeIds.timestamp:
+        return 'TimestampType';
+      case TypeIds.date:
+        return 'DateType';
+      case TypeIds.duration:
+        return 'DurationType';
+      case TypeIds.list:
+        return 'ListType';
+      case TypeIds.set:
+        return 'SetType';
+      case TypeIds.map:
+        return 'MapType';
+      default:
+        return 'type id $typeId';
     }
+  }
+
+  int _typeIdFor(DartType type) {
     final nonNullable = _withoutNullability(type);
     if (nonNullable.isDartCoreBool) {
       return TypeIds.boolType;
@@ -2226,18 +2630,6 @@ GeneratedFieldType(
     }
     final typeLiteral = _typeLiteral(nonNullable);
     switch (typeLiteral) {
-      case 'Int8':
-        return TypeIds.int8;
-      case 'Int16':
-        return TypeIds.int16;
-      case 'Int32':
-        return TypeIds.varInt32;
-      case 'Uint8':
-        return TypeIds.uint8;
-      case 'Uint16':
-        return TypeIds.uint16;
-      case 'Uint32':
-        return TypeIds.varUint32;
       case 'Uint64':
         return TypeIds.varUint64;
       case 'Int64':
@@ -2570,29 +2962,22 @@ final class _ConstructorArgumentSpec {
   });
 }
 
-final class _IntegerAnnotationSpec {
-  final int typeId;
-
-  const _IntegerAnnotationSpec({required this.typeId});
-}
-
-/// Parsed representation of a [TypeSpec] annotation hierarchy.
 class _TypeSpecInfo {
-  final bool? ref;
+  final int? typeId;
   final bool? nullable;
-
-  const _TypeSpecInfo({this.ref, this.nullable});
-}
-
-final class _ListTypeSpecInfo extends _TypeSpecInfo {
+  final bool? ref;
+  final bool? dynamic;
   final _TypeSpecInfo? element;
-
-  const _ListTypeSpecInfo({super.ref, super.nullable, this.element});
-}
-
-final class _MapTypeSpecInfo extends _TypeSpecInfo {
   final _TypeSpecInfo? key;
   final _TypeSpecInfo? value;
 
-  const _MapTypeSpecInfo({super.ref, super.nullable, this.key, this.value});
+  const _TypeSpecInfo({
+    this.typeId,
+    this.nullable,
+    this.ref,
+    this.dynamic,
+    this.element,
+    this.key,
+    this.value,
+  });
 }
diff --git a/dart/packages/fory/lib/src/codegen/generated_support.dart b/dart/packages/fory/lib/src/codegen/generated_support.dart
index d3ff7290d..c2cf44252 100644
--- a/dart/packages/fory/lib/src/codegen/generated_support.dart
+++ b/dart/packages/fory/lib/src/codegen/generated_support.dart
@@ -37,6 +37,7 @@ import 'package:fory/src/serializer/struct_serializer.dart';
 import 'package:fory/src/serializer/struct_slots.dart';
 import 'package:fory/src/serializer/time_serializers.dart';
 import 'package:fory/src/serializer/typed_array_serializers.dart';
+import 'package:fory/src/util/int_validation.dart';
 
 @internal
 final class GeneratedFieldType {
@@ -247,6 +248,30 @@ Uint8List readGeneratedBinaryValue(ReadContext context) {
   return BinarySerializer.readPayload(context);
 }
 
+@internal
+@pragma('vm:prefer-inline')
+int generatedCheckedInt8(int value) => checkedInt8(value);
+
+@internal
+@pragma('vm:prefer-inline')
+int generatedCheckedInt16(int value) => checkedInt16(value);
+
+@internal
+@pragma('vm:prefer-inline')
+int generatedCheckedInt32(int value) => checkedInt32(value);
+
+@internal
+@pragma('vm:prefer-inline')
+int generatedCheckedUint8(int value) => checkedUint8(value);
+
+@internal
+@pragma('vm:prefer-inline')
+int generatedCheckedUint16(int value) => checkedUint16(value);
+
+@internal
+@pragma('vm:prefer-inline')
+int generatedCheckedUint32(int value) => checkedUint32(value);
+
 @internal
 void writeGeneratedBoolArrayValue(WriteContext context, List<bool> value) {
   const BoolArraySerializer().write(context, value);
diff --git a/dart/packages/fory/lib/src/context/write_context.dart b/dart/packages/fory/lib/src/context/write_context.dart
index a9399818c..ee65523ba 100644
--- a/dart/packages/fory/lib/src/context/write_context.dart
+++ b/dart/packages/fory/lib/src/context/write_context.dart
@@ -220,6 +220,28 @@ final class WriteContext {
     writeResolvedValue(resolved, value, null);
   }
 
+  @internal
+  void writeRootBuiltinValue(
+    Object value, {
+    required int wireTypeId,
+    required bool trackRef,
+  }) {
+    final resolved = _typeResolver.resolveBuiltinWireType(wireTypeId);
+    final effectiveTrackRef = trackRef && resolved.supportsRef;
+    if (_refWriter.writeRefOrNull(
+      _buffer,
+      value,
+      trackRef: effectiveTrackRef,
+    )) {
+      return;
+    }
+    if (!effectiveTrackRef && resolved.supportsRef) {
+      _refWriter.reference(value);
+    }
+    _writeTypeMeta(resolved, value);
+    writeResolvedValue(resolved, value, null);
+  }
+
   void _writeRef(Object? value, {required bool trackRef}) {
     final resolved = value == null ? null : _typeResolver.resolveValue(value);
     final effectiveTrackRef =
diff --git a/dart/packages/fory/lib/src/fory.dart b/dart/packages/fory/lib/src/fory.dart
index 72b2e64b9..f082a4eab 100644
--- a/dart/packages/fory/lib/src/fory.dart
+++ b/dart/packages/fory/lib/src/fory.dart
@@ -93,6 +93,27 @@ final class Fory {
     return Uint8List.fromList(_buffer.toBytes());
   }
 
+  /// Serializes a non-null builtin [value] using the explicit xlang
+  /// [wireTypeId].
+  ///
+  /// This is the advanced escape hatch for root values whose wire type must
+  /// stay narrower than their Dart carrier type, such as writing a Dart `int`
+  /// as xlang `VAR_INT32`. [wireTypeId] must identify a builtin xlang type.
+  Uint8List serializeBuiltin(
+    Object value, {
+    required int wireTypeId,
+    bool trackRef = false,
+  }) {
+    _buffer.clear();
+    serializeBuiltinTo(
+      value,
+      _buffer,
+      wireTypeId: wireTypeId,
+      trackRef: trackRef,
+    );
+    return Uint8List.fromList(_buffer.toBytes());
+  }
+
   /// Serializes [value] into [buffer].
   ///
   /// The target [buffer] is cleared before bytes are written. [trackRef] has
@@ -112,6 +133,31 @@ final class Fory {
     }
   }
 
+  /// Serializes a non-null builtin [value] into [buffer] using the explicit
+  /// xlang [wireTypeId].
+  ///
+  /// The target [buffer] is cleared before bytes are written. [wireTypeId]
+  /// must identify a builtin xlang type.
+  void serializeBuiltinTo(
+    Object value,
+    Buffer buffer, {
+    required int wireTypeId,
+    bool trackRef = false,
+  }) {
+    buffer.clear();
+    _writeContext.prepare(buffer, trackRef: trackRef);
+    try {
+      buffer.writeUint8(_xlangHeaderFlag);
+      _writeContext.writeRootBuiltinValue(
+        value,
+        wireTypeId: wireTypeId,
+        trackRef: trackRef,
+      );
+    } finally {
+      _writeContext.reset();
+    }
+  }
+
   /// Deserializes [bytes] and then checks that the result is assignable to [T].
   ///
   /// The payload is decoded from its wire metadata first. `T` is used as a
diff --git a/dart/packages/fory/lib/src/resolver/type_resolver.dart b/dart/packages/fory/lib/src/resolver/type_resolver.dart
index 8e826275f..b37562b22 100644
--- a/dart/packages/fory/lib/src/resolver/type_resolver.dart
+++ b/dart/packages/fory/lib/src/resolver/type_resolver.dart
@@ -46,16 +46,10 @@ import 'package:fory/src/types/bfloat16.dart';
 import 'package:fory/src/types/decimal.dart';
 import 'package:fory/src/types/float16.dart';
 import 'package:fory/src/types/float32.dart';
-import 'package:fory/src/types/int16.dart';
-import 'package:fory/src/types/int32.dart';
 import 'package:fory/src/types/int64.dart';
-import 'package:fory/src/types/int8.dart';
 import 'package:fory/src/types/local_date.dart';
 import 'package:fory/src/types/timestamp.dart';
-import 'package:fory/src/types/uint16.dart';
-import 'package:fory/src/types/uint32.dart';
 import 'package:fory/src/types/uint64.dart';
-import 'package:fory/src/types/uint8.dart';
 import 'package:fory/src/util/hash_util.dart';
 
 enum RegistrationKind { builtin, struct, enumType, ext, union }
@@ -315,15 +309,6 @@ final class TypeResolver {
     if (value is bool) {
       return _builtin(bool, TypeIds.boolType);
     }
-    if (value is Int8) {
-      return _builtin(Int8, TypeIds.int8);
-    }
-    if (value is Int16) {
-      return _builtin(Int16, TypeIds.int16);
-    }
-    if (value is Int32) {
-      return _builtin(Int32, TypeIds.varInt32);
-    }
     // Native Int64 is represented as `int`; web still needs this branch to
     // keep explicit wrapper values on the Int64 API while using varint64 wire
     // format by default, the same as plain Dart int.
@@ -334,15 +319,6 @@ final class TypeResolver {
     if (value is int) {
       return _builtin(int, TypeIds.varInt64);
     }
-    if (value is Uint8) {
-      return _builtin(Uint8, TypeIds.uint8);
-    }
-    if (value is Uint16) {
-      return _builtin(Uint16, TypeIds.uint16);
-    }
-    if (value is Uint32) {
-      return _builtin(Uint32, TypeIds.varUint32);
-    }
     if (value is Uint64) {
       return _builtin(Uint64, TypeIds.varUint64);
     }
@@ -1030,13 +1006,13 @@ final class TypeResolver {
       case TypeIds.boolType:
         return _builtin(bool, TypeIds.boolType);
       case TypeIds.int8:
-        return _builtin(Int8, TypeIds.int8);
+        return _builtin(int, TypeIds.int8);
       case TypeIds.int16:
-        return _builtin(Int16, TypeIds.int16);
+        return _builtin(int, TypeIds.int16);
       case TypeIds.int32:
-        return _builtin(Int32, TypeIds.int32);
+        return _builtin(int, TypeIds.int32);
       case TypeIds.varInt32:
-        return _builtin(Int32, TypeIds.varInt32);
+        return _builtin(int, TypeIds.varInt32);
       case TypeIds.int64:
         return _builtin(Int64, TypeIds.int64);
       case TypeIds.varInt64:
@@ -1044,13 +1020,13 @@ final class TypeResolver {
       case TypeIds.taggedInt64:
         return _builtin(Int64, TypeIds.taggedInt64);
       case TypeIds.uint8:
-        return _builtin(Uint8, TypeIds.uint8);
+        return _builtin(int, TypeIds.uint8);
       case TypeIds.uint16:
-        return _builtin(Uint16, TypeIds.uint16);
+        return _builtin(int, TypeIds.uint16);
       case TypeIds.uint32:
-        return _builtin(Uint32, TypeIds.uint32);
+        return _builtin(int, TypeIds.uint32);
       case TypeIds.varUint32:
-        return _builtin(Uint32, TypeIds.varUint32);
+        return _builtin(int, TypeIds.varUint32);
       case TypeIds.uint64:
         return _builtin(Uint64, TypeIds.uint64);
       case TypeIds.varUint64:
@@ -1256,14 +1232,8 @@ final class TypeResolver {
     if (type == bool) {
       return TypeIds.boolType;
     }
-    if (type == Int8) {
-      return TypeIds.int8;
-    }
-    if (type == Int16) {
-      return TypeIds.int16;
-    }
-    if (type == Int32) {
-      return TypeIds.varInt32;
+    if (type == int) {
+      return TypeIds.varInt64;
     }
     if (type == Int64) {
       return TypeIds.varInt64;
@@ -1271,15 +1241,6 @@ final class TypeResolver {
     if (type == Int64List) {
       return TypeIds.int64Array;
     }
-    if (type == Uint8) {
-      return TypeIds.uint8;
-    }
-    if (type == Uint16) {
-      return TypeIds.uint16;
-    }
-    if (type == Uint32) {
-      return TypeIds.varUint32;
-    }
     if (type == Uint64) {
       return TypeIds.varUint64;
     }
diff --git a/dart/packages/fory/lib/src/serializer/primitive_serializers.dart b/dart/packages/fory/lib/src/serializer/primitive_serializers.dart
index af8e249ee..31e1c1384 100644
--- a/dart/packages/fory/lib/src/serializer/primitive_serializers.dart
+++ b/dart/packages/fory/lib/src/serializer/primitive_serializers.dart
@@ -24,14 +24,9 @@ import 'package:fory/src/serializer/serializer.dart';
 import 'package:fory/src/types/bfloat16.dart';
 import 'package:fory/src/types/float16.dart';
 import 'package:fory/src/types/float32.dart';
-import 'package:fory/src/types/int16.dart';
-import 'package:fory/src/types/int32.dart';
 import 'package:fory/src/types/int64.dart';
-import 'package:fory/src/types/int8.dart';
-import 'package:fory/src/types/uint16.dart';
-import 'package:fory/src/types/uint32.dart';
 import 'package:fory/src/types/uint64.dart';
-import 'package:fory/src/types/uint8.dart';
+import 'package:fory/src/util/int_validation.dart';
 
 const int _jsSafeUint64IntMax = 9007199254740991;
 const bool _isWeb = bool.fromEnvironment('dart.library.js_interop');
@@ -69,16 +64,16 @@ final class PrimitiveSerializer<T> extends Serializer<T> {
         buffer.writeBool(value as bool);
         return;
       case TypeIds.int8:
-        buffer.writeByte((value as Int8).value);
+        buffer.writeByte(checkedInt8(value as int));
         return;
       case TypeIds.int16:
-        buffer.writeInt16((value as Int16).value);
+        buffer.writeInt16(checkedInt16(value as int));
         return;
       case TypeIds.int32:
-        buffer.writeInt32((value as Int32).value);
+        buffer.writeInt32(checkedInt32(value as int));
         return;
       case TypeIds.varInt32:
-        buffer.writeVarInt32(value is Int32 ? value.value : value as int);
+        buffer.writeVarInt32(checkedInt32(value as int));
         return;
       case TypeIds.int64:
         if (value is Int64) {
@@ -102,16 +97,16 @@ final class PrimitiveSerializer<T> extends Serializer<T> {
         }
         return;
       case TypeIds.uint8:
-        buffer.writeUint8(value is Uint8 ? value.value : value as int);
+        buffer.writeUint8(checkedUint8(value as int));
         return;
       case TypeIds.uint16:
-        buffer.writeUint16(value is Uint16 ? value.value : value as int);
+        buffer.writeUint16(checkedUint16(value as int));
         return;
       case TypeIds.uint32:
-        buffer.writeUint32(value is Uint32 ? value.value : value as int);
+        buffer.writeUint32(checkedUint32(value as int));
         return;
       case TypeIds.varUint32:
-        buffer.writeVarUint32(value is Uint32 ? value.value : value as int);
+        buffer.writeVarUint32(checkedUint32(value as int));
         return;
       case TypeIds.uint64:
         buffer.writeUint64(_uint64Value(value));
@@ -148,13 +143,13 @@ final class PrimitiveSerializer<T> extends Serializer<T> {
       case TypeIds.boolType:
         return buffer.readBool();
       case TypeIds.int8:
-        return Int8(buffer.readByte());
+        return buffer.readByte();
       case TypeIds.int16:
-        return Int16(buffer.readInt16());
+        return buffer.readInt16();
       case TypeIds.int32:
-        return Int32(buffer.readInt32());
+        return buffer.readInt32();
       case TypeIds.varInt32:
-        return Int32(buffer.readVarInt32());
+        return buffer.readVarInt32();
       case TypeIds.int64:
         return buffer.readInt64();
       case TypeIds.varInt64:
@@ -162,13 +157,13 @@ final class PrimitiveSerializer<T> extends Serializer<T> {
       case TypeIds.taggedInt64:
         return buffer.readTaggedInt64();
       case TypeIds.uint8:
-        return Uint8(buffer.readUint8());
+        return buffer.readUint8();
       case TypeIds.uint16:
-        return Uint16(buffer.readUint16());
+        return buffer.readUint16();
       case TypeIds.uint32:
-        return Uint32(buffer.readUint32());
+        return buffer.readUint32();
       case TypeIds.varUint32:
-        return Uint32(buffer.readVarUint32());
+        return buffer.readVarUint32();
       case TypeIds.uint64:
         return buffer.readUint64();
       case TypeIds.varUint64:
@@ -208,20 +203,19 @@ const PrimitiveSerializer<bool> boolSerializer = PrimitiveSerializer<bool>(
   TypeIds.boolType,
   supportsRef: false,
 );
-const PrimitiveSerializer<Int8> int8Serializer = PrimitiveSerializer<Int8>(
+const PrimitiveSerializer<int> int8Serializer = PrimitiveSerializer<int>(
   TypeIds.int8,
   supportsRef: false,
 );
-const PrimitiveSerializer<Int16> int16Serializer = PrimitiveSerializer<Int16>(
+const PrimitiveSerializer<int> int16Serializer = PrimitiveSerializer<int>(
   TypeIds.int16,
   supportsRef: false,
 );
-const PrimitiveSerializer<Int32> int32Serializer = PrimitiveSerializer<Int32>(
+const PrimitiveSerializer<int> int32Serializer = PrimitiveSerializer<int>(
   TypeIds.int32,
   supportsRef: false,
 );
-const PrimitiveSerializer<Int32> varInt32Serializer =
-    PrimitiveSerializer<Int32>(
+const PrimitiveSerializer<int> varInt32Serializer = PrimitiveSerializer<int>(
   TypeIds.varInt32,
   supportsRef: false,
 );
@@ -239,22 +233,19 @@ const PrimitiveSerializer<Int64> taggedInt64Serializer =
   TypeIds.taggedInt64,
   supportsRef: false,
 );
-const PrimitiveSerializer<Uint8> uint8Serializer = PrimitiveSerializer<Uint8>(
+const PrimitiveSerializer<int> uint8Serializer = PrimitiveSerializer<int>(
   TypeIds.uint8,
   supportsRef: false,
 );
-const PrimitiveSerializer<Uint16> uint16Serializer =
-    PrimitiveSerializer<Uint16>(
+const PrimitiveSerializer<int> uint16Serializer = PrimitiveSerializer<int>(
   TypeIds.uint16,
   supportsRef: false,
 );
-const PrimitiveSerializer<Uint32> uint32Serializer =
-    PrimitiveSerializer<Uint32>(
+const PrimitiveSerializer<int> uint32Serializer = PrimitiveSerializer<int>(
   TypeIds.uint32,
   supportsRef: false,
 );
-const PrimitiveSerializer<Uint32> varUint32Serializer =
-    PrimitiveSerializer<Uint32>(
+const PrimitiveSerializer<int> varUint32Serializer = PrimitiveSerializer<int>(
   TypeIds.varUint32,
   supportsRef: false,
 );
diff --git a/dart/packages/fory/lib/src/serializer/serializer_support.dart b/dart/packages/fory/lib/src/serializer/serializer_support.dart
index 32b1dccd7..50a90b717 100644
--- a/dart/packages/fory/lib/src/serializer/serializer_support.dart
+++ b/dart/packages/fory/lib/src/serializer/serializer_support.dart
@@ -26,14 +26,8 @@ import 'package:fory/src/meta/type_ids.dart';
 import 'package:fory/src/resolver/type_resolver.dart';
 import 'package:fory/src/serializer/serialization_field_info.dart';
 import 'package:fory/src/types/float32.dart';
-import 'package:fory/src/types/int16.dart';
-import 'package:fory/src/types/int32.dart';
 import 'package:fory/src/types/int64.dart';
-import 'package:fory/src/types/int8.dart';
-import 'package:fory/src/types/uint16.dart';
-import 'package:fory/src/types/uint32.dart';
 import 'package:fory/src/types/uint64.dart';
-import 'package:fory/src/types/uint8.dart';
 
 final class DeferredReadRef {
   final int id;
@@ -58,26 +52,12 @@ bool fieldUsesDeclaredType(
 Object convertPrimitiveFieldValue(Object value, FieldType fieldType) {
   if (fieldType.type == int) {
     switch (fieldType.typeId) {
-      case TypeIds.int8:
-        return (value as Int8).value;
-      case TypeIds.int16:
-        return (value as Int16).value;
-      case TypeIds.int32:
-      case TypeIds.varInt32:
-        return (value as Int32).value;
       case TypeIds.int64:
       case TypeIds.varInt64:
       case TypeIds.taggedInt64:
         return _declares64BitWrapper(fieldType)
             ? value
             : (value as Int64).toInt();
-      case TypeIds.uint8:
-        return (value as Uint8).value;
-      case TypeIds.uint16:
-        return (value as Uint16).value;
-      case TypeIds.uint32:
-      case TypeIds.varUint32:
-        return (value as Uint32).value;
       case TypeIds.uint64:
       case TypeIds.varUint64:
       case TypeIds.taggedUint64:
@@ -122,24 +102,10 @@ Object convertResolvedPrimitiveValue(
   }
   if (resolved.type == int) {
     switch (resolved.typeId) {
-      case TypeIds.int8:
-        return (value as Int8).value;
-      case TypeIds.int16:
-        return (value as Int16).value;
-      case TypeIds.int32:
-      case TypeIds.varInt32:
-        return (value as Int32).value;
       case TypeIds.int64:
       case TypeIds.varInt64:
       case TypeIds.taggedInt64:
         return (value as Int64).toInt();
-      case TypeIds.uint8:
-        return (value as Uint8).value;
-      case TypeIds.uint16:
-        return (value as Uint16).value;
-      case TypeIds.uint32:
-      case TypeIds.varUint32:
-        return (value as Uint32).value;
       case TypeIds.uint64:
       case TypeIds.varUint64:
       case TypeIds.taggedUint64:
diff --git a/dart/packages/fory/lib/src/types/int16.dart b/dart/packages/fory/lib/src/types/int16.dart
deleted file mode 100644
index ddef75b88..000000000
--- a/dart/packages/fory/lib/src/types/int16.dart
+++ /dev/null
@@ -1,145 +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.
- */
-
-/// Signed 16-bit integer wrapper used by the xlang type system.
-///
-/// The constructor truncates [value] to 16 bits using two's-complement
-/// semantics, and arithmetic operators normalize every result back to the
-/// signed 16-bit range.
-final class Int16 implements Comparable<Int16> {
-  /// The normalized signed 16-bit value.
-  final int value;
-
-  /// Creates a signed 16-bit value by truncating [value] to 16 bits.
-  Int16(int value) : value = value.toSigned(16);
-
-  @override
-  int compareTo(Int16 other) => value.compareTo(other.value);
-
-  Int16 operator +(Object other) => switch (other) {
-        int otherValue => Int16(value + otherValue),
-        Int16 otherValue => Int16(value + otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  Int16 operator -(Object other) => switch (other) {
-        int otherValue => Int16(value - otherValue),
-        Int16 otherValue => Int16(value - otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  Int16 operator *(Object other) => switch (other) {
-        int otherValue => Int16(value * otherValue),
-        Int16 otherValue => Int16(value * otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  Int16 operator ~/(Object other) => switch (other) {
-        int otherValue => Int16(value ~/ otherValue),
-        Int16 otherValue => Int16(value ~/ otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  Int16 operator %(Object other) => switch (other) {
-        int otherValue => Int16(value % otherValue),
-        Int16 otherValue => Int16(value % otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  double operator /(Object other) => switch (other) {
-        int otherValue => value / otherValue,
-        Int16 otherValue => value / otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  Int16 operator -() => Int16(-value);
-
-  Int16 operator ~() => Int16(~value);
-
-  Int16 operator &(Object other) => switch (other) {
-        int otherValue => Int16(value & otherValue),
-        Int16 otherValue => Int16(value & otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  Int16 operator |(Object other) => switch (other) {
-        int otherValue => Int16(value | otherValue),
-        Int16 otherValue => Int16(value | otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  Int16 operator ^(Object other) => switch (other) {
-        int otherValue => Int16(value ^ otherValue),
-        Int16 otherValue => Int16(value ^ otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  Int16 operator <<(int shift) => Int16(value << shift);
-
-  Int16 operator >>(int shift) => Int16(value >> shift);
-
-  bool operator <(Object other) => switch (other) {
-        int otherValue => value < otherValue,
-        Int16 otherValue => value < otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  bool operator <=(Object other) => switch (other) {
-        int otherValue => value <= otherValue,
-        Int16 otherValue => value <= otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  bool operator >(Object other) => switch (other) {
-        int otherValue => value > otherValue,
-        Int16 otherValue => value > otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  bool operator >=(Object other) => switch (other) {
-        int otherValue => value >= otherValue,
-        Int16 otherValue => value >= otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int16.'),
-      };
-
-  int toInt() => value;
-
-  @override
-  bool operator ==(Object other) =>
-      identical(this, other) || other is Int16 && other.value == value;
-
-  @override
-  int get hashCode => value.hashCode;
-
-  @override
-  String toString() => value.toString();
-}
diff --git a/dart/packages/fory/lib/src/types/int32.dart b/dart/packages/fory/lib/src/types/int32.dart
deleted file mode 100644
index 4bfa350f1..000000000
--- a/dart/packages/fory/lib/src/types/int32.dart
+++ /dev/null
@@ -1,145 +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.
- */
-
-/// Signed 32-bit integer wrapper used by the xlang type system.
-///
-/// This wrapper preserves xlang `int32` and `varint32` semantics on the Dart
-/// side. Arithmetic and bitwise operators normalize every result back to the
-/// signed 32-bit range.
-final class Int32 implements Comparable<Int32> {
-  /// The normalized signed 32-bit value.
-  final int value;
-
-  /// Creates a signed 32-bit value by truncating [value] to 32 bits.
-  Int32(int value) : value = value.toSigned(32);
-
-  @override
-  int compareTo(Int32 other) => value.compareTo(other.value);
-
-  Int32 operator +(Object other) => switch (other) {
-        int otherValue => Int32(value + otherValue),
-        Int32 otherValue => Int32(value + otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  Int32 operator -(Object other) => switch (other) {
-        int otherValue => Int32(value - otherValue),
-        Int32 otherValue => Int32(value - otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  Int32 operator *(Object other) => switch (other) {
-        int otherValue => Int32(value * otherValue),
-        Int32 otherValue => Int32(value * otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  Int32 operator ~/(Object other) => switch (other) {
-        int otherValue => Int32(value ~/ otherValue),
-        Int32 otherValue => Int32(value ~/ otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  Int32 operator %(Object other) => switch (other) {
-        int otherValue => Int32(value % otherValue),
-        Int32 otherValue => Int32(value % otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  double operator /(Object other) => switch (other) {
-        int otherValue => value / otherValue,
-        Int32 otherValue => value / otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  Int32 operator -() => Int32(-value);
-
-  Int32 operator ~() => Int32(~value);
-
-  Int32 operator &(Object other) => switch (other) {
-        int otherValue => Int32(value & otherValue),
-        Int32 otherValue => Int32(value & otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  Int32 operator |(Object other) => switch (other) {
-        int otherValue => Int32(value | otherValue),
-        Int32 otherValue => Int32(value | otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  Int32 operator ^(Object other) => switch (other) {
-        int otherValue => Int32(value ^ otherValue),
-        Int32 otherValue => Int32(value ^ otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  Int32 operator <<(int shift) => Int32(value << shift);
-
-  Int32 operator >>(int shift) => Int32(value >> shift);
-
-  bool operator <(Object other) => switch (other) {
-        int otherValue => value < otherValue,
-        Int32 otherValue => value < otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  bool operator <=(Object other) => switch (other) {
-        int otherValue => value <= otherValue,
-        Int32 otherValue => value <= otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  bool operator >(Object other) => switch (other) {
-        int otherValue => value > otherValue,
-        Int32 otherValue => value > otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  bool operator >=(Object other) => switch (other) {
-        int otherValue => value >= otherValue,
-        Int32 otherValue => value >= otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Int32.'),
-      };
-
-  int toInt() => value;
-
-  @override
-  bool operator ==(Object other) =>
-      identical(this, other) || other is Int32 && other.value == value;
-
-  @override
-  int get hashCode => value.hashCode;
-
-  @override
-  String toString() => value.toString();
-}
diff --git a/dart/packages/fory/lib/src/types/int8.dart b/dart/packages/fory/lib/src/types/int8.dart
deleted file mode 100644
index 3d9531e86..000000000
--- a/dart/packages/fory/lib/src/types/int8.dart
+++ /dev/null
@@ -1,160 +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.
- */
-
-/// Signed 8-bit integer wrapper used by the xlang type system.
-///
-/// The constructor truncates [value] to 8 bits using two's-complement
-/// semantics, so arithmetic, shifts, and bitwise operators behave like native
-/// 8-bit machine operations. Operators that produce wrapped integers return a
-/// new normalized [Int8]; `/` returns a Dart [double].
-final class Int8 implements Comparable<Int8> {
-  /// The normalized signed 8-bit value.
-  final int value;
-
-  /// Creates a signed 8-bit value by truncating [value] to 8 bits.
-  Int8(int value) : value = value.toSigned(8);
-
-  @override
-  int compareTo(Int8 other) => value.compareTo(other.value);
-
-  /// Returns a normalized sum.
-  Int8 operator +(Object other) => switch (other) {
-        int otherValue => Int8(value + otherValue),
-        Int8 otherValue => Int8(value + otherValue.value),
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  /// Returns a normalized difference.
-  Int8 operator -(Object other) => switch (other) {
-        int otherValue => Int8(value - otherValue),
-        Int8 otherValue => Int8(value - otherValue.value),
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  /// Returns a normalized product.
-  Int8 operator *(Object other) => switch (other) {
-        int otherValue => Int8(value * otherValue),
-        Int8 otherValue => Int8(value * otherValue.value),
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  /// Returns a normalized integer quotient.
-  Int8 operator ~/(Object other) => switch (other) {
-        int otherValue => Int8(value ~/ otherValue),
-        Int8 otherValue => Int8(value ~/ otherValue.value),
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  /// Returns a normalized remainder.
-  Int8 operator %(Object other) => switch (other) {
-        int otherValue => Int8(value % otherValue),
-        Int8 otherValue => Int8(value % otherValue.value),
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  /// Returns a Dart [double] quotient.
-  double operator /(Object other) => switch (other) {
-        int otherValue => value / otherValue,
-        Int8 otherValue => value / otherValue.value,
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  /// Returns the normalized negated value.
-  Int8 operator -() => Int8(-value);
-
-  /// Returns the normalized bitwise inverse.
-  Int8 operator ~() => Int8(~value);
-
-  /// Returns a normalized bitwise AND.
-  Int8 operator &(Object other) => switch (other) {
-        int otherValue => Int8(value & otherValue),
-        Int8 otherValue => Int8(value & otherValue.value),
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  /// Returns a normalized bitwise OR.
-  Int8 operator |(Object other) => switch (other) {
-        int otherValue => Int8(value | otherValue),
-        Int8 otherValue => Int8(value | otherValue.value),
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  /// Returns a normalized bitwise XOR.
-  Int8 operator ^(Object other) => switch (other) {
-        int otherValue => Int8(value ^ otherValue),
-        Int8 otherValue => Int8(value ^ otherValue.value),
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  /// Returns a normalized left shift.
-  Int8 operator <<(int shift) => Int8(value << shift);
-
-  /// Returns a normalized arithmetic right shift.
-  Int8 operator >>(int shift) => Int8(value >> shift);
-
-  bool operator <(Object other) => switch (other) {
-        int otherValue => value < otherValue,
-        Int8 otherValue => value < otherValue.value,
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  bool operator <=(Object other) => switch (other) {
-        int otherValue => value <= otherValue,
-        Int8 otherValue => value <= otherValue.value,
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  bool operator >(Object other) => switch (other) {
-        int otherValue => value > otherValue,
-        Int8 otherValue => value > otherValue.value,
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  bool operator >=(Object other) => switch (other) {
-        int otherValue => value >= otherValue,
-        Int8 otherValue => value >= otherValue.value,
-        _ =>
-          throw ArgumentError.value(other, 'other', 'Expected an int or Int8.'),
-      };
-
-  /// Returns the normalized integer value as a Dart [int].
-  int toInt() => value;
-
-  @override
-  bool operator ==(Object other) =>
-      identical(this, other) || other is Int8 && other.value == value;
-
-  @override
-  int get hashCode => value.hashCode;
-
-  @override
-  String toString() => value.toString();
-}
diff --git a/dart/packages/fory/lib/src/types/uint16.dart b/dart/packages/fory/lib/src/types/uint16.dart
deleted file mode 100644
index 47bd69425..000000000
--- a/dart/packages/fory/lib/src/types/uint16.dart
+++ /dev/null
@@ -1,147 +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.
- */
-
-/// Unsigned 16-bit integer wrapper used by the xlang type system.
-///
-/// The constructor truncates [value] to 16 bits using unsigned wrap-around
-/// semantics. Arithmetic and bitwise operators normalize their results back to
-/// the `0..65535` range.
-final class Uint16 implements Comparable<Uint16> {
-  /// The normalized unsigned 16-bit value.
-  final int value;
-
-  /// Creates an unsigned 16-bit value by truncating [value] to 16 bits.
-  Uint16(int value) : value = value.toUnsigned(16);
-
-  @override
-  int compareTo(Uint16 other) => value.compareTo(other.value);
-
-  Uint16 operator +(Object other) => switch (other) {
-        int otherValue => Uint16(value + otherValue),
-        Uint16 otherValue => Uint16(value + otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  Uint16 operator -(Object other) => switch (other) {
-        int otherValue => Uint16(value - otherValue),
-        Uint16 otherValue => Uint16(value - otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  Uint16 operator *(Object other) => switch (other) {
-        int otherValue => Uint16(value * otherValue),
-        Uint16 otherValue => Uint16(value * otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  Uint16 operator ~/(Object other) => switch (other) {
-        int otherValue => Uint16(value ~/ otherValue),
-        Uint16 otherValue => Uint16(value ~/ otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  Uint16 operator %(Object other) => switch (other) {
-        int otherValue => Uint16(value % otherValue),
-        Uint16 otherValue => Uint16(value % otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  double operator /(Object other) => switch (other) {
-        int otherValue => value / otherValue,
-        Uint16 otherValue => value / otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  Uint16 operator -() => Uint16(-value);
-
-  Uint16 operator ~() => Uint16(~value);
-
-  Uint16 operator &(Object other) => switch (other) {
-        int otherValue => Uint16(value & otherValue),
-        Uint16 otherValue => Uint16(value & otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  Uint16 operator |(Object other) => switch (other) {
-        int otherValue => Uint16(value | otherValue),
-        Uint16 otherValue => Uint16(value | otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  Uint16 operator ^(Object other) => switch (other) {
-        int otherValue => Uint16(value ^ otherValue),
-        Uint16 otherValue => Uint16(value ^ otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  Uint16 operator <<(int shift) => Uint16(value << shift);
-
-  Uint16 operator >>(int shift) => Uint16(value >>> shift);
-
-  Uint16 operator >>>(int shift) => Uint16(value >>> shift);
-
-  bool operator <(Object other) => switch (other) {
-        int otherValue => value < otherValue,
-        Uint16 otherValue => value < otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  bool operator <=(Object other) => switch (other) {
-        int otherValue => value <= otherValue,
-        Uint16 otherValue => value <= otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  bool operator >(Object other) => switch (other) {
-        int otherValue => value > otherValue,
-        Uint16 otherValue => value > otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  bool operator >=(Object other) => switch (other) {
-        int otherValue => value >= otherValue,
-        Uint16 otherValue => value >= otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint16.'),
-      };
-
-  int toInt() => value;
-
-  @override
-  bool operator ==(Object other) =>
-      identical(this, other) || other is Uint16 && other.value == value;
-
-  @override
-  int get hashCode => value.hashCode;
-
-  @override
-  String toString() => value.toString();
-}
diff --git a/dart/packages/fory/lib/src/types/uint32.dart b/dart/packages/fory/lib/src/types/uint32.dart
deleted file mode 100644
index 7da4f5f31..000000000
--- a/dart/packages/fory/lib/src/types/uint32.dart
+++ /dev/null
@@ -1,147 +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.
- */
-
-/// Unsigned 32-bit integer wrapper used by the xlang type system.
-///
-/// This wrapper preserves xlang `uint32` and `varuint32` semantics on the Dart
-/// side. Arithmetic and bitwise operators normalize every result back to the
-/// unsigned 32-bit range.
-final class Uint32 implements Comparable<Uint32> {
-  /// The normalized unsigned 32-bit value.
-  final int value;
-
-  /// Creates an unsigned 32-bit value by truncating [value] to 32 bits.
-  Uint32(int value) : value = value.toUnsigned(32);
-
-  @override
-  int compareTo(Uint32 other) => value.compareTo(other.value);
-
-  Uint32 operator +(Object other) => switch (other) {
-        int otherValue => Uint32(value + otherValue),
-        Uint32 otherValue => Uint32(value + otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  Uint32 operator -(Object other) => switch (other) {
-        int otherValue => Uint32(value - otherValue),
-        Uint32 otherValue => Uint32(value - otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  Uint32 operator *(Object other) => switch (other) {
-        int otherValue => Uint32(value * otherValue),
-        Uint32 otherValue => Uint32(value * otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  Uint32 operator ~/(Object other) => switch (other) {
-        int otherValue => Uint32(value ~/ otherValue),
-        Uint32 otherValue => Uint32(value ~/ otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  Uint32 operator %(Object other) => switch (other) {
-        int otherValue => Uint32(value % otherValue),
-        Uint32 otherValue => Uint32(value % otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  double operator /(Object other) => switch (other) {
-        int otherValue => value / otherValue,
-        Uint32 otherValue => value / otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  Uint32 operator -() => Uint32(-value);
-
-  Uint32 operator ~() => Uint32(~value);
-
-  Uint32 operator &(Object other) => switch (other) {
-        int otherValue => Uint32(value & otherValue),
-        Uint32 otherValue => Uint32(value & otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  Uint32 operator |(Object other) => switch (other) {
-        int otherValue => Uint32(value | otherValue),
-        Uint32 otherValue => Uint32(value | otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  Uint32 operator ^(Object other) => switch (other) {
-        int otherValue => Uint32(value ^ otherValue),
-        Uint32 otherValue => Uint32(value ^ otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  Uint32 operator <<(int shift) => Uint32(value << shift);
-
-  Uint32 operator >>(int shift) => Uint32(value >>> shift);
-
-  Uint32 operator >>>(int shift) => Uint32(value >>> shift);
-
-  bool operator <(Object other) => switch (other) {
-        int otherValue => value < otherValue,
-        Uint32 otherValue => value < otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  bool operator <=(Object other) => switch (other) {
-        int otherValue => value <= otherValue,
-        Uint32 otherValue => value <= otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  bool operator >(Object other) => switch (other) {
-        int otherValue => value > otherValue,
-        Uint32 otherValue => value > otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  bool operator >=(Object other) => switch (other) {
-        int otherValue => value >= otherValue,
-        Uint32 otherValue => value >= otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint32.'),
-      };
-
-  int toInt() => value;
-
-  @override
-  bool operator ==(Object other) =>
-      identical(this, other) || other is Uint32 && other.value == value;
-
-  @override
-  int get hashCode => value.hashCode;
-
-  @override
-  String toString() => value.toString();
-}
diff --git a/dart/packages/fory/lib/src/types/uint8.dart b/dart/packages/fory/lib/src/types/uint8.dart
deleted file mode 100644
index bde29e661..000000000
--- a/dart/packages/fory/lib/src/types/uint8.dart
+++ /dev/null
@@ -1,147 +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.
- */
-
-/// Unsigned 8-bit integer wrapper used by the xlang type system.
-///
-/// The constructor truncates [value] to 8 bits using unsigned wrap-around
-/// semantics. Arithmetic and bitwise operators normalize their results back to
-/// the `0..255` range.
-final class Uint8 implements Comparable<Uint8> {
-  /// The normalized unsigned 8-bit value.
-  final int value;
-
-  /// Creates an unsigned 8-bit value by truncating [value] to 8 bits.
-  Uint8(int value) : value = value.toUnsigned(8);
-
-  @override
-  int compareTo(Uint8 other) => value.compareTo(other.value);
-
-  Uint8 operator +(Object other) => switch (other) {
-        int otherValue => Uint8(value + otherValue),
-        Uint8 otherValue => Uint8(value + otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  Uint8 operator -(Object other) => switch (other) {
-        int otherValue => Uint8(value - otherValue),
-        Uint8 otherValue => Uint8(value - otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  Uint8 operator *(Object other) => switch (other) {
-        int otherValue => Uint8(value * otherValue),
-        Uint8 otherValue => Uint8(value * otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  Uint8 operator ~/(Object other) => switch (other) {
-        int otherValue => Uint8(value ~/ otherValue),
-        Uint8 otherValue => Uint8(value ~/ otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  Uint8 operator %(Object other) => switch (other) {
-        int otherValue => Uint8(value % otherValue),
-        Uint8 otherValue => Uint8(value % otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  double operator /(Object other) => switch (other) {
-        int otherValue => value / otherValue,
-        Uint8 otherValue => value / otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  Uint8 operator -() => Uint8(-value);
-
-  Uint8 operator ~() => Uint8(~value);
-
-  Uint8 operator &(Object other) => switch (other) {
-        int otherValue => Uint8(value & otherValue),
-        Uint8 otherValue => Uint8(value & otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  Uint8 operator |(Object other) => switch (other) {
-        int otherValue => Uint8(value | otherValue),
-        Uint8 otherValue => Uint8(value | otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  Uint8 operator ^(Object other) => switch (other) {
-        int otherValue => Uint8(value ^ otherValue),
-        Uint8 otherValue => Uint8(value ^ otherValue.value),
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  Uint8 operator <<(int shift) => Uint8(value << shift);
-
-  Uint8 operator >>(int shift) => Uint8(value >>> shift);
-
-  Uint8 operator >>>(int shift) => Uint8(value >>> shift);
-
-  bool operator <(Object other) => switch (other) {
-        int otherValue => value < otherValue,
-        Uint8 otherValue => value < otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  bool operator <=(Object other) => switch (other) {
-        int otherValue => value <= otherValue,
-        Uint8 otherValue => value <= otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  bool operator >(Object other) => switch (other) {
-        int otherValue => value > otherValue,
-        Uint8 otherValue => value > otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  bool operator >=(Object other) => switch (other) {
-        int otherValue => value >= otherValue,
-        Uint8 otherValue => value >= otherValue.value,
-        _ => throw ArgumentError.value(
-            other, 'other', 'Expected an int or Uint8.'),
-      };
-
-  int toInt() => value;
-
-  @override
-  bool operator ==(Object other) =>
-      identical(this, other) || other is Uint8 && other.value == value;
-
-  @override
-  int get hashCode => value.hashCode;
-
-  @override
-  String toString() => value.toString();
-}
diff --git a/dart/packages/fory/lib/src/util/hash_util.dart b/dart/packages/fory/lib/src/util/hash_util.dart
index c49cdd018..0af369bca 100644
--- a/dart/packages/fory/lib/src/util/hash_util.dart
+++ b/dart/packages/fory/lib/src/util/hash_util.dart
@@ -19,7 +19,7 @@
 
 import 'dart:convert';
 
-import 'package:fory/src/meta/field_info.dart';
+import 'package:fory/src/meta/field_type.dart';
 import 'package:fory/src/meta/type_def.dart';
 import 'package:fory/src/meta/type_ids.dart';
 import 'package:fory/src/types/int64.dart';
@@ -175,27 +175,79 @@ Int64 typeDefHeader(
 }
 
 int schemaHash(TypeDef typeDef) {
+  return _murmurHash3X64_128Bits(utf8.encode(schemaFingerprint(typeDef)))
+      .$1
+      .low32;
+}
+
+String schemaFingerprint(TypeDef typeDef) {
   final parts = typeDef.fields
       .map(
         (field) => StringBuffer()
           ..write(field.identifier)
           ..write(',')
-          ..write(_fingerprintTypeId(field))
-          ..write(',')
-          ..write(field.fieldType.ref ? '1' : '0')
-          ..write(',')
-          ..write(field.fieldType.nullable ? '1' : '0')
+          ..write(
+            _fieldTypeFingerprint(
+              field.fieldType,
+              includeRef: true,
+              includeNullable: true,
+            ),
+          )
           ..write(';'),
       )
       .map((buffer) => buffer.toString())
       .toList(growable: false)
     ..sort();
-  return _murmurHash3X64_128Bits(utf8.encode(parts.join())).$1.low32;
+  return parts.join();
+}
+
+String _fieldTypeFingerprint(
+  FieldType fieldType, {
+  required bool includeRef,
+  required bool includeNullable,
+}) {
+  final buffer = StringBuffer()
+    ..write(_fingerprintTypeId(fieldType))
+    ..write(',')
+    ..write(includeRef && fieldType.ref ? '1' : '0')
+    ..write(',')
+    ..write(includeNullable && fieldType.nullable ? '1' : '0');
+  if (fieldType.arguments.isNotEmpty) {
+    buffer.write('[');
+    if (fieldType.typeId == TypeIds.map) {
+      buffer
+        ..write(
+          _fieldTypeFingerprint(
+            fieldType.arguments[0],
+            includeRef: false,
+            includeNullable: false,
+          ),
+        )
+        ..write('|')
+        ..write(
+          _fieldTypeFingerprint(
+            fieldType.arguments[1],
+            includeRef: false,
+            includeNullable: false,
+          ),
+        );
+    } else {
+      buffer.write(
+        _fieldTypeFingerprint(
+          fieldType.arguments.single,
+          includeRef: false,
+          includeNullable: false,
+        ),
+      );
+    }
+    buffer.write(']');
+  }
+  return buffer.toString();
 }
 
-int _fingerprintTypeId(FieldInfo field) {
-  final typeId = field.fieldType.typeId;
-  if (field.fieldType.isDynamic || typeId == TypeIds.unknown) {
+int _fingerprintTypeId(FieldType fieldType) {
+  final typeId = fieldType.typeId;
+  if (fieldType.isDynamic || typeId == TypeIds.unknown) {
     return TypeIds.unknown;
   }
   switch (typeId) {
diff --git a/dart/packages/fory/lib/src/util/int_validation.dart b/dart/packages/fory/lib/src/util/int_validation.dart
new file mode 100644
index 000000000..dc670b199
--- /dev/null
+++ b/dart/packages/fory/lib/src/util/int_validation.dart
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+@pragma('vm:prefer-inline')
+int checkedInt8(int value) {
+  if (value < -128 || value > 127) {
+    throw RangeError.range(value, -128, 127, 'value');
+  }
+  return value;
+}
+
+@pragma('vm:prefer-inline')
+int checkedInt16(int value) {
+  if (value < -32768 || value > 32767) {
+    throw RangeError.range(value, -32768, 32767, 'value');
+  }
+  return value;
+}
+
+@pragma('vm:prefer-inline')
+int checkedInt32(int value) {
+  if (value < -2147483648 || value > 2147483647) {
+    throw RangeError.range(value, -2147483648, 2147483647, 'value');
+  }
+  return value;
+}
+
+@pragma('vm:prefer-inline')
+int checkedUint8(int value) {
+  if (value < 0 || value > 255) {
+    throw RangeError.range(value, 0, 255, 'value');
+  }
+  return value;
+}
+
+@pragma('vm:prefer-inline')
+int checkedUint16(int value) {
+  if (value < 0 || value > 65535) {
+    throw RangeError.range(value, 0, 65535, 'value');
+  }
+  return value;
+}
+
+@pragma('vm:prefer-inline')
+int checkedUint32(int value) {
+  if (value < 0 || value > 4294967295) {
+    throw RangeError.range(value, 0, 4294967295, 'value');
+  }
+  return value;
+}
diff --git a/dart/packages/fory/test/container_ref_test.dart b/dart/packages/fory/test/container_ref_test.dart
index a58da73f8..4fbdf366f 100644
--- a/dart/packages/fory/test/container_ref_test.dart
+++ b/dart/packages/fory/test/container_ref_test.dart
@@ -29,16 +29,16 @@ class Node {
   String name = '';
 }
 
-/// Struct with @ListType that enables ref tracking on list elements.
+/// Struct with @ListField that enables ref tracking on list elements.
 @ForyStruct()
 class RefListContainer {
   RefListContainer();
 
-  @ListType(element: ValueType.ref())
+  @ListField(element: DeclaredType(ref: true))
   List<Node> items = <Node>[];
 }
 
-/// Struct with no @ListType annotation — list elements are NOT ref-tracked.
+/// Struct with no @ListField annotation — list elements are NOT ref-tracked.
 @ForyStruct()
 class NoRefListContainer {
   NoRefListContainer();
@@ -46,16 +46,16 @@ class NoRefListContainer {
   List<Node> items = <Node>[];
 }
 
-/// Struct with @MapType that enables ref tracking on map values.
+/// Struct with @MapField that enables ref tracking on map values.
 @ForyStruct()
 class RefMapValueContainer {
   RefMapValueContainer();
 
-  @MapType(value: ValueType.ref())
+  @MapField(value: DeclaredType(ref: true))
   Map<String, Node> entries = <String, Node>{};
 }
 
-/// Struct with no @MapType — map values are NOT ref-tracked.
+/// Struct with no @MapField — map values are NOT ref-tracked.
 @ForyStruct()
 class NoRefMapContainer {
   NoRefMapContainer();
@@ -63,12 +63,12 @@ class NoRefMapContainer {
   Map<String, Node> entries = <String, Node>{};
 }
 
-/// Struct with @MapType that enables ref tracking on map keys.
+/// Struct with @MapField that enables ref tracking on map keys.
 @ForyStruct()
 class RefMapKeyContainer {
   RefMapKeyContainer();
 
-  @MapType(key: ValueType.ref())
+  @MapField(key: DeclaredType(ref: true))
   Map<Node, String> entries = <Node, String>{};
 }
 
@@ -77,8 +77,8 @@ class RefMapKeyContainer {
 class NestedListOfMapContainer {
   NestedListOfMapContainer();
 
-  @ListType(
-    element: MapType(value: ValueType.ref()),
+  @ListField(
+    element: MapType(value: DeclaredType(ref: true)),
   )
   List<Map<String, Node>> groups = <Map<String, Node>>[];
 }
@@ -88,8 +88,8 @@ class NestedListOfMapContainer {
 class NestedMapOfListContainer {
   NestedMapOfListContainer();
 
-  @MapType(
-    value: ListType(element: ValueType.ref()),
+  @MapField(
+    value: ListType(element: DeclaredType(ref: true)),
   )
   Map<String, List<Node>> groups = <String, List<Node>>{};
 }
@@ -121,7 +121,7 @@ void main() {
     _registerAll(fory);
   });
 
-  group('list element ref via @ListType annotation', () {
+  group('list element ref via @ListField annotation', () {
     test('shared list elements preserve identity with element ref enabled', () {
       final shared = Node()..name = 'shared';
       final container = RefListContainer()
@@ -150,7 +150,7 @@ void main() {
     });
   });
 
-  group('map value ref via @MapType annotation', () {
+  group('map value ref via @MapField annotation', () {
     test('shared map values preserve identity with value ref enabled', () {
       final shared = Node()..name = 'val';
       final container = RefMapValueContainer()
@@ -177,7 +177,7 @@ void main() {
     });
   });
 
-  group('map key ref via @MapType annotation', () {
+  group('map key ref via @MapField annotation', () {
     test('shared map keys preserve identity with key ref enabled', () {
       final shared = Node()..name = 'key';
       final container = RefMapKeyContainer()
@@ -191,7 +191,7 @@ void main() {
     });
   });
 
-  group('nested container ref via @ListType/@MapType annotation', () {
+  group('nested container ref via @ListField/@MapField annotation', () {
     test('list of maps with ref-tracked values preserves identity across maps',
         () {
       final shared = Node()..name = 'deep';
@@ -208,7 +208,8 @@ void main() {
       expect(identical(result.groups[0]['x'], result.groups[1]['y']), isTrue);
     });
 
-    test('map of lists with ref-tracked elements preserves identity across lists',
+    test(
+        'map of lists with ref-tracked elements preserves identity across lists',
         () {
       final shared = Node()..name = 'maplist';
       final container = NestedMapOfListContainer()
diff --git a/dart/packages/fory/test/manual_registration_test.dart b/dart/packages/fory/test/manual_registration_test.dart
index 1558478e3..d18ff9d4f 100644
--- a/dart/packages/fory/test/manual_registration_test.dart
+++ b/dart/packages/fory/test/manual_registration_test.dart
@@ -194,7 +194,7 @@ void main() {
       maxCollectionSize: 1024,
       maxBinarySize: 4096,
     );
-    final bytes = fory.serialize(Int32(42));
-    expect(fory.deserialize<Int32>(bytes), equals(Int32(42)));
+    final bytes = fory.serialize((42));
+    expect(fory.deserialize<int>(bytes), equals((42)));
   });
 }
diff --git a/dart/packages/fory/test/nested_type_spec_test.dart b/dart/packages/fory/test/nested_type_spec_test.dart
new file mode 100644
index 000000000..4aa2929d9
--- /dev/null
+++ b/dart/packages/fory/test/nested_type_spec_test.dart
@@ -0,0 +1,274 @@
+/*
+ * 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.
+ */
+
+import 'package:fory/fory.dart';
+import 'package:fory/src/serializer/compatible_struct_metadata.dart';
+import 'package:test/test.dart';
+
+part 'nested_type_spec_test.fory.dart';
+
+@ForyStruct()
+class NestedFixedContainer {
+  NestedFixedContainer();
+
+  @MapField(
+    value: ListType(
+      element: Int32Type(encoding: Encoding.fixed),
+    ),
+  )
+  Map<String, List<int?>> nested = <String, List<int?>>{};
+}
+
+@ForyStruct()
+class NestedVarintContainer {
+  NestedVarintContainer();
+
+  @MapField(
+    value: ListType(
+      element: Int32Type(encoding: Encoding.varint),
+    ),
+  )
+  Map<String, List<int?>> nested = <String, List<int?>>{};
+}
+
+@ForyStruct()
+class NestedRefNode {
+  NestedRefNode();
+
+  String name = '';
+}
+
+@ForyStruct()
+class NestedRefWriterContainer {
+  NestedRefWriterContainer();
+
+  @ListField(element: DeclaredType(ref: true))
+  List<NestedRefNode> items = <NestedRefNode>[];
+}
+
+@ForyStruct()
+class NestedRefReaderContainer {
+  NestedRefReaderContainer();
+
+  List<NestedRefNode> items = <NestedRefNode>[];
+}
+
+@ForyStruct()
+class NestedNullableWriterContainer {
+  NestedNullableWriterContainer();
+
+  @ListField(element: StringType(nullable: true))
+  List<String?> items = <String?>[];
+}
+
+@ForyStruct()
+class NestedNullableReaderContainer {
+  NestedNullableReaderContainer();
+
+  List<String> items = <String>[];
+}
+
+void _registerFixedCompatible(Fory fory) {
+  NestedTypeSpecTestFory.register(
+    fory,
+    NestedFixedContainer,
+    namespace: 'nested',
+    typeName: 'NestedIntContainer',
+  );
+}
+
+void _registerVarintConsistent(Fory fory) {
+  NestedTypeSpecTestFory.register(
+    fory,
+    NestedVarintContainer,
+    namespace: 'nested',
+    typeName: 'NestedIntContainer',
+  );
+}
+
+void _registerRefWriter(Fory fory) {
+  NestedTypeSpecTestFory.register(
+    fory,
+    NestedRefNode,
+    namespace: 'nested',
+    typeName: 'NestedRefNode',
+  );
+  NestedTypeSpecTestFory.register(
+    fory,
+    NestedRefWriterContainer,
+    namespace: 'nested',
+    typeName: 'NestedRefContainer',
+  );
+}
+
+void _registerRefReader(Fory fory) {
+  NestedTypeSpecTestFory.register(
+    fory,
+    NestedRefNode,
+    namespace: 'nested',
+    typeName: 'NestedRefNode',
+  );
+  NestedTypeSpecTestFory.register(
+    fory,
+    NestedRefReaderContainer,
+    namespace: 'nested',
+    typeName: 'NestedRefContainer',
+  );
+}
+
+void _registerNullableWriter(Fory fory) {
+  NestedTypeSpecTestFory.register(
+    fory,
+    NestedNullableWriterContainer,
+    namespace: 'nested',
+    typeName: 'NestedNullableContainer',
+  );
+}
+
+void _registerNullableReader(Fory fory) {
+  NestedTypeSpecTestFory.register(
+    fory,
+    NestedNullableReaderContainer,
+    namespace: 'nested',
+    typeName: 'NestedNullableContainer',
+  );
+}
+
+void main() {
+  group('nested type specs', () {
+    test(
+        'compatible mode reads nested overridden fixed int32 list values from remote meta',
+        () {
+      final writer = Fory(compatible: true);
+      final reader = Fory(compatible: true);
+      _registerFixedCompatible(writer);
+      _registerVarintConsistent(reader);
+
+      final result = reader.deserialize<NestedVarintContainer>(
+        writer.serialize(
+          NestedFixedContainer()
+            ..nested = <String, List<int?>>{
+              'a': <int?>[1, null, -7],
+            },
+        ),
+      );
+
+      expect(result.nested['a'], orderedEquals(<int?>[1, null, -7]));
+
+      final remoteTypeDef = CompatibleStructMetadata.remoteTypeDefFor(result);
+      expect(remoteTypeDef, isNotNull);
+      final nestedField = remoteTypeDef!.fields.single;
+      expect(nestedField.fieldType.typeId, equals(TypeIds.map));
+      expect(nestedField.fieldType.arguments[0].typeId, equals(TypeIds.string));
+      final remoteList = nestedField.fieldType.arguments[1];
+      expect(remoteList.typeId, equals(TypeIds.list));
+      final remoteElement = remoteList.arguments.single;
+      expect(remoteElement.typeId, equals(TypeIds.int32));
+      expect(remoteElement.nullable, isTrue);
+    });
+
+    test('schema-consistent mode rejects nested encoding hash mismatches', () {
+      final writer = Fory();
+      final reader = Fory();
+      _registerFixedCompatible(writer);
+      _registerVarintConsistent(reader);
+
+      final bytes = writer.serialize(
+        NestedFixedContainer()
+          ..nested = <String, List<int?>>{
+            'a': <int?>[1, null, -7],
+          },
+      );
+
+      expect(
+        () => reader.deserialize<NestedVarintContainer>(bytes),
+        throwsA(
+          isA<StateError>().having(
+            (error) => error.toString(),
+            'message',
+            contains('Struct schema version mismatch'),
+          ),
+        ),
+      );
+    });
+
+    test(
+        'nested ref metadata stays out of schema hash but survives remote meta',
+        () {
+      final writer = Fory(compatible: true);
+      final reader = Fory(compatible: true);
+      _registerRefWriter(writer);
+      _registerRefReader(reader);
+
+      final shared = NestedRefNode()..name = 'shared';
+      final result = reader.deserialize<NestedRefReaderContainer>(
+        writer.serialize(
+          NestedRefWriterContainer()..items = <NestedRefNode>[shared, shared],
+          trackRef: true,
+        ),
+      );
+
+      expect(result.items, hasLength(2));
+      expect(result.items[0].name, equals('shared'));
+      expect(identical(result.items[0], result.items[1]), isTrue);
+
+      final remoteTypeDef = CompatibleStructMetadata.remoteTypeDefFor(result);
+      expect(remoteTypeDef, isNotNull);
+      final remoteElement =
+          remoteTypeDef!.fields.single.fieldType.arguments.single;
+      expect(remoteElement.ref, isTrue);
+    });
+
+    test('schema-consistent mode ignores nested ref-only schema differences',
+        () {
+      final writer = Fory();
+      final reader = Fory();
+      _registerRefWriter(writer);
+      _registerRefReader(reader);
+
+      final shared = NestedRefNode()..name = 'shared';
+      final result = reader.deserialize<NestedRefReaderContainer>(
+        writer.serialize(
+          NestedRefWriterContainer()..items = <NestedRefNode>[shared, shared],
+          trackRef: true,
+        ),
+      );
+
+      expect(result.items, hasLength(2));
+      expect(identical(result.items[0], result.items[1]), isTrue);
+    });
+
+    test(
+        'schema-consistent mode ignores nested nullable-only schema differences',
+        () {
+      final writer = Fory();
+      final reader = Fory();
+      _registerNullableWriter(writer);
+      _registerNullableReader(reader);
+
+      final result = reader.deserialize<NestedNullableReaderContainer>(
+        writer.serialize(
+          NestedNullableWriterContainer()..items = <String?>['a', 'b'],
+        ),
+      );
+
+      expect(result.items, orderedEquals(<String>['a', 'b']));
+    });
+  });
+}
diff --git a/dart/packages/fory/test/numeric_wrapper_test.dart b/dart/packages/fory/test/numeric_wrapper_test.dart
deleted file mode 100644
index f5f0436c8..000000000
--- a/dart/packages/fory/test/numeric_wrapper_test.dart
+++ /dev/null
@@ -1,475 +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.
- */
-
-import 'dart:typed_data';
-
-import 'package:fory/fory.dart';
-import 'package:fory/src/resolver/type_resolver.dart';
-import 'package:fory/src/serializer/compatible_struct_metadata.dart';
-import 'package:test/test.dart';
-
-part 'numeric_wrapper_test.fory.dart';
-
-Uint64 _u64Hex(String value) => Uint64.parseHex(value);
-
-double _float64FromWords(int low32, int high32) {
-  final bytes = ByteData(8)
-    ..setUint32(0, low32, Endian.little)
-    ..setUint32(4, high32, Endian.little);
-  return bytes.getFloat64(0, Endian.little);
-}
-
-@ForyStruct()
-class NumericWrappersEnvelope {
-  NumericWrappersEnvelope();
-
-  Int8 i8 = Int8(0);
-  Int16 i16 = Int16(0);
-  Int32 i32 = Int32(0);
-  Int64 i64 = Int64(0);
-  Uint8 u8 = Uint8(0);
-  Uint16 u16 = Uint16(0);
-  Uint32 u32 = Uint32(0);
-  Uint64 u64 = Uint64(0);
-  Float16 half = Float16(0);
-  Bfloat16 brain = Bfloat16(0);
-  Float32 single = Float32(0);
-  Int8? optionalI8;
-  Uint64? optionalU64;
-  Float16? optionalHalf;
-  Bfloat16? optionalBrain;
-  Float32? optionalSingle;
-}
-
-@ForyStruct()
-class NumericWrappersMetadataReader {
-  NumericWrappersMetadataReader();
-
-  Int8 i8 = Int8(0);
-}
-
-void _registerNumericWrappers(Fory fory) {
-  NumericWrapperTestFory.register(
-    fory,
-    NumericWrappersEnvelope,
-    namespace: 'test',
-    typeName: 'NumericWrappersEnvelope',
-  );
-}
-
-void _registerNumericWrappersMetadataReader(Fory fory) {
-  NumericWrapperTestFory.register(
-    fory,
-    NumericWrappersMetadataReader,
-    namespace: 'test',
-    typeName: 'NumericWrappersEnvelope',
-  );
-}
-
-T _roundTrip<T>(Fory fory, T value) =>
-    fory.deserialize<T>(fory.serialize(value));
-
-NumericWrappersEnvelope _sampleEnvelope() {
-  return NumericWrappersEnvelope()
-    ..i8 = Int8(-127)
-    ..i16 = Int16(0x7fff)
-    ..i32 = Int32(-2147483648)
-    ..i64 = Int64.parseHex('8000000000000000')
-    ..u8 = Uint8(0xff)
-    ..u16 = Uint16(0xffff)
-    ..u32 = Uint32(0xffffffff)
-    ..u64 = _u64Hex('ffffffffffffffff')
-    ..half = Float16.fromBits(0x3555)
-    ..brain = Bfloat16.fromBits(0x3eab)
-    ..single = Float32.fromBits(0x40490fdb)
-    ..optionalI8 = Int8(126)
-    ..optionalU64 = _u64Hex('8000000000000000')
-    ..optionalHalf = Float16.fromBits(0x8000)
-    ..optionalBrain = Bfloat16.fromBits(0x8000)
-    ..optionalSingle = Float32.fromBits(0x80000000);
-}
-
-void _expectEnvelopeEquals(
-  NumericWrappersEnvelope actual,
-  NumericWrappersEnvelope expected,
-) {
-  expect(actual.i8, equals(expected.i8));
-  expect(actual.i16, equals(expected.i16));
-  expect(actual.i32, equals(expected.i32));
-  expect(actual.i64, equals(expected.i64));
-  expect(actual.u8, equals(expected.u8));
-  expect(actual.u16, equals(expected.u16));
-  expect(actual.u32, equals(expected.u32));
-  expect(actual.u64, equals(expected.u64));
-  expect(actual.half.toBits(), equals(expected.half.toBits()));
-  expect(actual.brain.toBits(), equals(expected.brain.toBits()));
-  expect(actual.single.toBits(), equals(expected.single.toBits()));
-  expect(actual.optionalI8, equals(expected.optionalI8));
-  expect(actual.optionalU64, equals(expected.optionalU64));
-  expect(
-      actual.optionalHalf?.toBits(), equals(expected.optionalHalf?.toBits()));
-  expect(
-      actual.optionalBrain?.toBits(), equals(expected.optionalBrain?.toBits()));
-  expect(
-    actual.optionalSingle?.toBits(),
-    equals(expected.optionalSingle?.toBits()),
-  );
-}
-
-int _remoteFieldTypeId(Object value, String identifier) {
-  final remoteTypeDef = CompatibleStructMetadata.remoteTypeDefFor(value);
-  expect(remoteTypeDef, isNotNull);
-  final field = remoteTypeDef!.fields.firstWhere(
-    (field) => field.identifier == identifier,
-  );
-  return field.fieldType.typeId;
-}
-
-void main() {
-  group('numeric wrappers', () {
-    test('signed integer wrappers normalize arithmetic and bitwise operations',
-        () {
-      expect(Int8(127) + 1, equals(Int8(-128)));
-      expect(Int8(-128) - 1, equals(Int8(127)));
-      expect(Int8(12) * Int8(11), equals(Int8(-124)));
-      expect(Int8(7) ~/ 2, equals(Int8(3)));
-      expect(Int8(7) / 2, equals(3.5));
-      expect(Int8(0x0f) & Int8(0x33), equals(Int8(0x03)));
-      expect(Int8(0x0f) | Int8(0x33), equals(Int8(0x3f)));
-      expect(Int8(0x0f) ^ Int8(0x33), equals(Int8(0x3c)));
-      expect(Int8(0x40) >> 1, equals(Int8(0x20)));
-      expect(Int8(0x40) << 2, equals(Int8(0x00)));
-      expect(Int8(-1) < 0, isTrue);
-      expect(Int8(10) >= Int8(10), isTrue);
-
-      expect(Int16(0x7fff) + 1, equals(Int16(-0x8000)));
-      expect(Int16(-0x8000) - 1, equals(Int16(0x7fff)));
-      expect(Int32(0x7fffffff) + 1, equals(Int32(-0x80000000)));
-      expect(Int32(-0x80000000) - 1, equals(Int32(0x7fffffff)));
-      expect(Int32(0x12345678) >> 4, equals(Int32(0x01234567)));
-      expect(Int32(123).toInt(), equals(123));
-    });
-
-    test(
-        'unsigned integer wrappers normalize arithmetic and bitwise operations',
-        () {
-      expect(Uint8(0xff) + 1, equals(Uint8(0)));
-      expect(Uint8(0) - 1, equals(Uint8(0xff)));
-      expect(Uint8(0xf0) >> 4, equals(Uint8(0x0f)));
-      expect(Uint8(0xf0) >>> 4, equals(Uint8(0x0f)));
-      expect(Uint8(0xff) >> 1, equals(Uint8(0x7f)));
-      expect(Uint8(0xff) >>> 1, equals(Uint8(0x7f)));
-      expect(-Uint8(1), equals(Uint8(0xff)));
-
-      expect(Uint16(0xffff) + 1, equals(Uint16(0)));
-      expect(Uint16(0) - 1, equals(Uint16(0xffff)));
-      expect(Uint16(0xffff) >> 1, equals(Uint16(0x7fff)));
-      expect(Uint16(0xffff) >>> 1, equals(Uint16(0x7fff)));
-
-      expect(Uint32(0xffffffff) + 1, equals(Uint32(0)));
-      expect(Uint32(0) - 1, equals(Uint32(0xffffffff)));
-      expect(
-          Uint32(0xf0f0f0f0) & Uint32(0x0ff00ff0), equals(Uint32(0x00f000f0)));
-      expect(Uint32(0xffffffff) >> 1, equals(Uint32(0x7fffffff)));
-      expect(Uint32(0xffffffff) >>> 1, equals(Uint32(0x7fffffff)));
-
-      expect(_u64Hex('ffffffffffffffff') + 1, equals(Uint64(0)));
-      expect(Uint64(0) - 1, equals(_u64Hex('ffffffffffffffff')));
-      expect(
-        _u64Hex('123456789abcdef0') >> 4,
-        equals(_u64Hex('0123456789abcdef')),
-      );
-      expect(Uint64(0xff).toInt(), equals(0xff));
-    });
-
-    test('Float16 arithmetic rounds back to binary16 precision', () {
-      expect((Float16(1.5) + 2).toBits(), equals(Float16(3.5).toBits()));
-      expect(
-          (Float16(7) - Float16(2.5)).toBits(), equals(Float16(4.5).toBits()));
-      expect((Float16(1) / 3).toBits(), equals(Float16(1 / 3).toBits()));
-      expect((Float16(7.5) % 2).toBits(), equals(Float16(1.5).toBits()));
-      expect(Float16(7.5) ~/ 2, equals(3));
-      expect((-Float16(1.5)).toBits(), equals(Float16(-1.5).toBits()));
-      expect(Float16(-1) < 0, isTrue);
-      expect(Float16(3.5) >= Float16(3.5), isTrue);
-      expect(Float16.fromBits(0x3555).value, closeTo(0.333251953125, 1e-12));
-    });
-
-    test('Bfloat16 arithmetic rounds back to bfloat16 precision', () {
-      expect((Bfloat16(1.5) + 2).toBits(), equals(Bfloat16(3.5).toBits()));
-      expect(
-        (Bfloat16(7) - Bfloat16(2.5)).toBits(),
-        equals(Bfloat16(4.5).toBits()),
-      );
-      expect((Bfloat16(1) / 3).toBits(), equals(Bfloat16(1 / 3).toBits()));
-      expect((Bfloat16(7.5) % 2).toBits(), equals(Bfloat16(1.5).toBits()));
-      expect(Bfloat16(7.5) ~/ 2, equals(3));
-      expect((-Bfloat16(1.5)).toBits(), equals(Bfloat16(-1.5).toBits()));
-      expect(Bfloat16(-1) < 0, isTrue);
-      expect(Bfloat16(3.5) >= Bfloat16(3.5), isTrue);
-      expect(Bfloat16.fromBits(0x3eab).value, closeTo(0.333984375, 1e-12));
-    });
-
-    test(
-        'Bfloat16.fromDouble rounds directly from float64 and preserves NaN sign payload bits',
-        () {
-      final trickySubnormal = _float64FromWords(0x7e281cc1, 0x37da834f);
-      final payloadNaN = _float64FromWords(0x6789abcd, 0xfff12345);
-      final convertedNaN = Bfloat16.fromDouble(payloadNaN);
-
-      expect(Bfloat16.fromDouble(trickySubnormal).toBits(), equals(0x0007));
-      expect(convertedNaN.toBits() & 0x8000, equals(0x8000));
-      expect(convertedNaN.toBits() & 0x7f80, equals(0x7f80));
-      expect(convertedNaN.toBits() & 0x0040, equals(0x0040));
-      expect(convertedNaN.toBits() & 0x003f, isNot(0));
-    });
-
-    test('Float32 arithmetic rounds back to binary32 precision', () {
-      expect((Float32(1.5) + 2).toBits(), equals(Float32(3.5).toBits()));
-      expect(
-          (Float32(7) - Float32(2.5)).toBits(), equals(Float32(4.5).toBits()));
-      expect((Float32(1) / 3).toBits(), equals(Float32(1 / 3).toBits()));
-      expect((Float32(7.5) % 2).toBits(), equals(Float32(1.5).toBits()));
-      expect(Float32(7.5) ~/ 2, equals(3));
-      expect((-Float32(1.5)).toBits(), equals(Float32(-1.5).toBits()));
-      expect(Float32(-1) < 0, isTrue);
-      expect(Float32(3.5) >= Float32(3.5), isTrue);
-      expect(Float32.fromBits(0x3eaaaaab).value, closeTo(1 / 3, 1e-8));
-    });
-
-    test('round-trips root fixed-width wrappers', () {
-      final fory = Fory();
-
-      expect(_roundTrip<Int8>(fory, Int8(-129)), equals(Int8(127)));
-      expect(_roundTrip<Int16>(fory, Int16(-32769)), equals(Int16(32767)));
-      expect(
-        _roundTrip<Int32>(fory, Int32(0x80000000)),
-        equals(Int32(-0x80000000)),
-      );
-      expect(_roundTrip<Uint8>(fory, Uint8(-1)), equals(Uint8(0xff)));
-      expect(_roundTrip<Uint16>(fory, Uint16(-1)), equals(Uint16(0xffff)));
-      expect(_roundTrip<Uint32>(fory, Uint32(-1)), equals(Uint32(0xffffffff)));
-      expect(_roundTrip<Int64>(fory, Int64(-1)), equals(Int64(-1)));
-      expect(
-        _roundTrip<Int64>(fory, Int64.parseHex('8000000000000000')),
-        equals(Int64.parseHex('8000000000000000')),
-      );
-      expect(
-        _roundTrip<Int64>(fory, Int64.parseHex('7fffffffffffffff')),
-        equals(Int64.parseHex('7fffffffffffffff')),
-      );
-      expect(
-        _roundTrip<Uint64>(fory, Uint64(-1)),
-        equals(_u64Hex('ffffffffffffffff')),
-      );
-    });
-
-    test('resolves root numeric wrappers to compact varint wire ids', () {
-      final resolver = TypeResolver(Config());
-
-      expect(resolver.resolveValue(1).typeId, equals(TypeIds.varInt64));
-      expect(resolver.resolveValue(Int32(1)).typeId, equals(TypeIds.varInt32));
-      expect(resolver.resolveValue(Int64(1)).typeId, equals(TypeIds.varInt64));
-      expect(
-        resolver.resolveValue(Uint32(1)).typeId,
-        equals(TypeIds.varUint32),
-      );
-      if (identical(1, 1.0)) {
-        expect(
-          resolver.resolveValue(Uint64(1)).typeId,
-          equals(TypeIds.varUint64),
-        );
-      } else {
-        expect(
-          resolver.resolveValue(Uint64(1)).typeId,
-          equals(TypeIds.varInt64),
-          reason: 'Native Uint64 is an extension type represented as int.',
-        );
-      }
-    });
-
-    test('typed root Int64 reads preserve wrappers', () {
-      final fory = Fory();
-
-      expect(
-        fory.deserialize<int>(fory.serialize(1)),
-        equals(1),
-        reason: 'Plain int roots still decode as Dart int.',
-      );
-      expect(
-        fory.deserialize<Int64>(fory.serialize(Int64(-1))),
-        equals(Int64(-1)),
-      );
-      expect(
-        fory.deserialize<Int64>(
-          fory.serialize(Int64.parseHex('8000000000000000')),
-        ),
-        equals(Int64.parseHex('8000000000000000')),
-      );
-      expect(
-        fory.deserialize<Int64>(
-          fory.serialize(Int64.parseHex('7fffffffffffffff')),
-        ),
-        equals(Int64.parseHex('7fffffffffffffff')),
-      );
-      expect(
-        fory.deserialize<Int64?>(
-          fory.serialize(Int64.parseHex('8000000000000000')),
-        ),
-        equals(Int64.parseHex('8000000000000000')),
-      );
-      expect(
-        fory.deserialize<int?>(fory.serialize(Int64(-1))),
-        equals(-1),
-        reason: 'Nullable int roots still decode as Dart int.',
-      );
-    });
-
-    test('web dynamic Uint64 wrappers keep unsigned metadata', () {
-      if (!identical(1, 1.0)) {
-        return;
-      }
-
-      final fory = Fory();
-      final value = _u64Hex('ffffffffffffffff');
-
-      expect(
-        fory.deserialize<Uint64>(fory.serialize(value)),
-        equals(value),
-      );
-
-      final list = fory.deserialize<Object?>(
-        fory.serialize(<Object?>[value]),
-      ) as List;
-      expect(list.single, equals(value));
-
-      final map = fory.deserialize<Object?>(
-        fory.serialize(<Object?, Object?>{'value': value}),
-      ) as Map;
-      expect(map['value'], equals(value));
-    });
-
-    test('round-trips root Float16 payloads with exact bits', () {
-      final fory = Fory();
-      final cases = <Float16>[
-        Float16.fromBits(0x0000),
-        Float16.fromBits(0x8000),
-        Float16.fromBits(0x3555),
-        Float16.fromBits(0x7c00),
-        Float16.fromBits(0x7e00),
-      ];
-
-      for (final value in cases) {
-        expect(
-            _roundTrip<Float16>(fory, value).toBits(), equals(value.toBits()));
-      }
-    });
-
-    test('round-trips root Bfloat16 payloads with exact bits', () {
-      final fory = Fory();
-      final cases = <Bfloat16>[
-        Bfloat16.fromBits(0x0000),
-        Bfloat16.fromBits(0x8000),
-        Bfloat16.fromBits(0x3eab),
-        Bfloat16.fromBits(0x7f80),
-        Bfloat16.fromBits(0x7fc0),
-      ];
-
-      for (final value in cases) {
-        expect(
-          _roundTrip<Bfloat16>(fory, value).toBits(),
-          equals(value.toBits()),
-        );
-      }
-    });
-
-    test('round-trips root Float32 payloads with exact bits', () {
-      final fory = Fory();
-      final cases = <Float32>[
-        Float32.fromBits(0x00000000),
-        Float32.fromBits(0x80000000),
-        Float32.fromBits(0x3eaaaaab),
-        Float32.fromBits(0x7f800000),
-        Float32.fromBits(0x7fc00000),
-      ];
-
-      for (final value in cases) {
-        expect(
-            _roundTrip<Float32>(fory, value).toBits(), equals(value.toBits()));
-      }
-    });
-
-    test(
-        'round-trips generated numeric wrapper fields in schema-consistent mode',
-        () {
-      final fory = Fory();
-      _registerNumericWrappers(fory);
-
-      final value = _sampleEnvelope();
-      final roundTrip =
-          fory.deserialize<NumericWrappersEnvelope>(fory.serialize(value));
-
-      _expectEnvelopeEquals(roundTrip, value);
-    });
-
-    test('round-trips generated numeric wrapper fields in compatible mode', () {
-      final fory = Fory(compatible: true);
-      _registerNumericWrappers(fory);
-
-      final value = _sampleEnvelope();
-      final roundTrip =
-          fory.deserialize<NumericWrappersEnvelope>(fory.serialize(value));
-
-      _expectEnvelopeEquals(roundTrip, value);
-    });
-
-    test('compatible metadata records numeric wrapper varint defaults', () {
-      final writer = Fory(compatible: true);
-      final reader = Fory(compatible: true);
-      _registerNumericWrappers(writer);
-      _registerNumericWrappersMetadataReader(reader);
-
-      final roundTrip = reader.deserialize<NumericWrappersMetadataReader>(
-        writer.serialize(_sampleEnvelope()),
-      );
-
-      expect(roundTrip.i8, equals(Int8(-127)));
-      expect(_remoteFieldTypeId(roundTrip, 'i32'), equals(TypeIds.varInt32));
-      expect(_remoteFieldTypeId(roundTrip, 'i64'), equals(TypeIds.varInt64));
-      expect(_remoteFieldTypeId(roundTrip, 'u32'), equals(TypeIds.varUint32));
-      expect(_remoteFieldTypeId(roundTrip, 'u64'), equals(TypeIds.varUint64));
-    });
-
-    test('supports null optional numeric wrapper fields', () {
-      final fory = Fory();
-      _registerNumericWrappers(fory);
-
-      final roundTrip = _roundTrip<NumericWrappersEnvelope>(
-        fory,
-        NumericWrappersEnvelope(),
-      );
-
-      expect(roundTrip.optionalI8, isNull);
-      expect(roundTrip.optionalU64, isNull);
-      expect(roundTrip.optionalHalf, isNull);
-      expect(roundTrip.optionalBrain, isNull);
-      expect(roundTrip.optionalSingle, isNull);
-    });
-  });
-}
diff --git a/dart/packages/fory/test/runtime_validation_test.dart b/dart/packages/fory/test/runtime_validation_test.dart
index 4e1c39509..347bbc63b 100644
--- a/dart/packages/fory/test/runtime_validation_test.dart
+++ b/dart/packages/fory/test/runtime_validation_test.dart
@@ -282,12 +282,12 @@ void main() {
       final bytes = fory.serialize('value');
 
       expect(
-        () => fory.deserialize<Int32>(bytes),
+        () => fory.deserialize<int>(bytes),
         throwsA(
           isA<StateError>().having(
             (error) => error.toString(),
             'message',
-            contains('expected Int32'),
+            contains('expected int'),
           ),
         ),
       );
diff --git a/dart/packages/fory/test/signed_serializer_test.dart b/dart/packages/fory/test/signed_serializer_test.dart
index 176854f36..4be24b1e7 100644
--- a/dart/packages/fory/test/signed_serializer_test.dart
+++ b/dart/packages/fory/test/signed_serializer_test.dart
@@ -35,45 +35,45 @@ class SignedFields {
 
   int plainInt = 0;
 
-  @Int32Type(compress: true)
+  @ForyField(type: Int32Type(encoding: Encoding.varint))
   int i32Var = 0;
 
-  @Int32Type(compress: false)
+  @ForyField(type: Int32Type(encoding: Encoding.fixed))
   int i32Fixed = 0;
 
-  @Int64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Int64Type(encoding: Encoding.varint))
   int i64VarInt = 0;
 
-  @Int64Type(encoding: LongEncoding.fixed)
+  @ForyField(type: Int64Type(encoding: Encoding.fixed))
   int i64FixedInt = 0;
 
-  @Int64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Int64Type(encoding: Encoding.tagged))
   int i64TaggedInt = 0;
 
   Int64 i64Default = Int64(0);
 
-  @Int64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Int64Type(encoding: Encoding.varint))
   Int64 i64Var = Int64(0);
 
-  @Int64Type(encoding: LongEncoding.fixed)
+  @ForyField(type: Int64Type(encoding: Encoding.fixed))
   Int64 i64Fixed = Int64(0);
 
-  @Int64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Int64Type(encoding: Encoding.tagged))
   Int64 i64Tagged = Int64(0);
 
-  @Int64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Int64Type(encoding: Encoding.varint))
   int? optionalI64VarInt;
 
-  @Int32Type(compress: true)
+  @ForyField(type: Int32Type(encoding: Encoding.varint))
   int? optionalI32Var;
 
-  @Int32Type(compress: false)
+  @ForyField(type: Int32Type(encoding: Encoding.fixed))
   int? optionalI32Fixed;
 
-  @Int64Type(encoding: LongEncoding.fixed)
+  @ForyField(type: Int64Type(encoding: Encoding.fixed))
   Int64? optionalI64Fixed;
 
-  @Int64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Int64Type(encoding: Encoding.tagged))
   Int64? optionalI64Tagged;
 }
 
@@ -88,13 +88,13 @@ class SignedMetadataReader {
 class SignedIntFieldsReader {
   SignedIntFieldsReader();
 
-  @Int64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Int64Type(encoding: Encoding.varint))
   int i64Var = 0;
 
-  @Int64Type(encoding: LongEncoding.fixed)
+  @ForyField(type: Int64Type(encoding: Encoding.fixed))
   int i64Fixed = 0;
 
-  @Int64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Int64Type(encoding: Encoding.tagged))
   int i64Tagged = 0;
 }
 
@@ -272,7 +272,7 @@ void main() {
       }
     });
 
-    test('compatible mode round trips Int32 and Int64 encoding edge cases', () {
+    test('compatible mode round trips int and Int64 encoding edge cases', () {
       final fory = Fory(compatible: true);
       _registerSignedFields(fory);
 
@@ -372,6 +372,39 @@ void main() {
       expect(_remoteFieldNullable(roundTrip, 'optional_i64_tagged'), isTrue);
     });
 
+    test('rejects out-of-range annotated int32 fields', () {
+      final cases = <({String name, SignedFields value})>[
+        (
+          name: 'i32Var',
+          value: _smallSignedFields()..i32Var = -0x80000001,
+        ),
+        (
+          name: 'i32Fixed',
+          value: _smallSignedFields()..i32Fixed = 0x80000000,
+        ),
+        (
+          name: 'optionalI32Var',
+          value: _smallSignedFields()..optionalI32Var = 0x80000000,
+        ),
+        (
+          name: 'optionalI32Fixed',
+          value: _smallSignedFields()..optionalI32Fixed = -0x80000001,
+        ),
+      ];
+
+      for (final testCase in cases) {
+        for (final compatible in <bool>[false, true]) {
+          final fory = Fory(compatible: compatible);
+          _registerSignedFields(fory);
+          expect(
+            () => fory.serialize(testCase.value),
+            throwsA(isA<RangeError>()),
+            reason: '${testCase.name}, compatible=$compatible',
+          );
+        }
+      }
+    });
+
     test('web rejects JS-unsafe Dart int fields instead of corrupting bytes',
         () {
       final cases = <({String name, SignedFields value})>[
diff --git a/dart/packages/fory/test/type_spec_annotation_test.dart b/dart/packages/fory/test/type_spec_annotation_test.dart
index 717810b42..8362b5a92 100644
--- a/dart/packages/fory/test/type_spec_annotation_test.dart
+++ b/dart/packages/fory/test/type_spec_annotation_test.dart
@@ -21,158 +21,103 @@ import 'package:fory/fory.dart';
 import 'package:test/test.dart';
 
 void main() {
-  group('TypeOption', () {
-    test('RefOption defaults tracked to true', () {
-      const ref = RefOption();
-      expect(ref.tracked, isTrue);
-    });
-
-    test('RefOption accepts explicit false', () {
-      const ref = RefOption(false);
-      expect(ref.tracked, isFalse);
-    });
-
-    test('NullableOption defaults value to true', () {
-      const nullable = NullableOption();
-      expect(nullable.value, isTrue);
-    });
-
-    test('NullableOption accepts explicit false', () {
-      const nullable = NullableOption(false);
-      expect(nullable.value, isFalse);
-    });
-
-    test('TypeOption.ref factory creates RefOption', () {
-      const option = TypeOption.ref();
-      expect(option, isA<RefOption>());
-      expect((option as RefOption).tracked, isTrue);
-    });
-
-    test('TypeOption.nullable factory creates NullableOption', () {
-      const option = TypeOption.nullable();
-      expect(option, isA<NullableOption>());
-      expect((option as NullableOption).value, isTrue);
-    });
-  });
-
-  group('ValueType', () {
-    test('default constructor has empty options', () {
-      const vt = ValueType();
-      expect(vt.options, isEmpty);
-    });
-
-    test('ValueType.ref() sets ref option', () {
-      const vt = ValueType.ref();
-      expect(vt.options, hasLength(1));
-      expect(vt.options.first, isA<RefOption>());
-      expect((vt.options.first as RefOption).tracked, isTrue);
-    });
-
-    test('ValueType.noRef() sets ref(false)', () {
-      const vt = ValueType.noRef();
-      expect(vt.options, hasLength(1));
-      expect((vt.options.first as RefOption).tracked, isFalse);
-    });
-
-    test('ValueType.nullable() sets nullable option', () {
-      const vt = ValueType.nullable();
-      expect(vt.options, hasLength(1));
-      expect((vt.options.first as NullableOption).value, isTrue);
-    });
-
-    test('ValueType.nonNullable() sets nullable(false)', () {
-      const vt = ValueType.nonNullable();
-      expect(vt.options, hasLength(1));
-      expect((vt.options.first as NullableOption).value, isFalse);
+  group('scalar type specs', () {
+    test('DeclaredType stores leaf overrides without changing leaf kind', () {
+      const declared = DeclaredType(nullable: true, ref: true, dynamic: false);
+      expect(declared.nullable, isTrue);
+      expect(declared.ref, isTrue);
+      expect(declared.dynamic, isFalse);
+    });
+
+    test('Int32Type keeps encoding and node-local overrides separate', () {
+      const spec = Int32Type(
+        encoding: Encoding.fixed,
+        nullable: true,
+        ref: false,
+        dynamic: false,
+      );
+      expect(spec.encoding, equals(Encoding.fixed));
+      expect(spec.nullable, isTrue);
+      expect(spec.ref, isFalse);
+      expect(spec.dynamic, isFalse);
     });
 
-    test('ValueType.refNullable() sets both ref and nullable', () {
-      const vt = ValueType.refNullable();
-      expect(vt.options, hasLength(2));
-      expect(vt.options[0], isA<RefOption>());
-      expect(vt.options[1], isA<NullableOption>());
+    test('Uint64Type supports tagged encoding', () {
+      const spec = Uint64Type(encoding: Encoding.tagged);
+      expect(spec.encoding, equals(Encoding.tagged));
+      expect(spec.nullable, isNull);
+      expect(spec.ref, isNull);
+      expect(spec.dynamic, isNull);
     });
   });
 
-  group('ListType', () {
-    test('default constructor has empty options and default element', () {
-      const lt = ListType();
-      expect(lt.options, isEmpty);
-      expect(lt.element, isA<ValueType>());
-      expect(lt.element.options, isEmpty);
-    });
-
-    test('ListType.ref() sets ref option', () {
-      const lt = ListType.ref();
-      expect(lt.options, hasLength(1));
-      expect((lt.options.first as RefOption).tracked, isTrue);
-    });
-
-    test('ListType.noRef() sets ref(false)', () {
-      const lt = ListType.noRef();
-      expect(lt.options, hasLength(1));
-      expect((lt.options.first as RefOption).tracked, isFalse);
-    });
-
-    test('ListType.nullable() sets nullable option', () {
-      const lt = ListType.nullable();
-      expect(lt.options, hasLength(1));
-      expect((lt.options.first as NullableOption).value, isTrue);
-    });
-
-    test('accepts custom element TypeSpec', () {
-      const lt = ListType(element: ValueType.ref());
-      expect(lt.element.options, hasLength(1));
-      expect((lt.element.options.first as RefOption).tracked, isTrue);
-    });
-
-    test('nested MapType element', () {
+  group('container type specs', () {
+    test('ListType stores nested element specs', () {
       const lt = ListType(
-        options: [TypeOption.ref()],
         element: MapType(
-          value: ValueType.ref(),
+          value: DeclaredType(ref: true),
         ),
+        nullable: true,
       );
-      expect(lt.options, hasLength(1));
+      expect(lt.nullable, isTrue);
       final mapElement = lt.element as MapType;
-      expect(mapElement.value.options, hasLength(1));
-      expect((mapElement.value.options.first as RefOption).tracked, isTrue);
-    });
-  });
-
-  group('MapType', () {
-    test('default constructor has empty options and default key/value', () {
-      const mt = MapType();
-      expect(mt.options, isEmpty);
-      expect(mt.key, isA<ValueType>());
-      expect(mt.value, isA<ValueType>());
+      final declaredValue = mapElement.value as DeclaredType;
+      expect(declaredValue.ref, isTrue);
     });
 
-    test('MapType.ref() sets ref option', () {
-      const mt = MapType.ref();
-      expect(mt.options, hasLength(1));
-      expect((mt.options.first as RefOption).tracked, isTrue);
-    });
-
-    test('MapType.noRef() sets ref(false)', () {
-      const mt = MapType.noRef();
-      expect(mt.options, hasLength(1));
-      expect((mt.options.first as RefOption).tracked, isFalse);
+    test('MapType stores explicit key and value trees', () {
+      const mt = MapType(
+        key: StringType(),
+        value: ListType(
+          element: Int32Type(
+            nullable: true,
+            encoding: Encoding.fixed,
+          ),
+        ),
+        ref: true,
+      );
+      expect(mt.ref, isTrue);
+      expect(mt.key, isA<StringType>());
+      final value = mt.value as ListType;
+      final element = value.element as Int32Type;
+      expect(element.nullable, isTrue);
+      expect(element.encoding, equals(Encoding.fixed));
     });
+  });
 
-    test('MapType.nullable() sets nullable option', () {
-      const mt = MapType.nullable();
-      expect(mt.options, hasLength(1));
-      expect((mt.options.first as NullableOption).value, isTrue);
+  group('field annotations', () {
+    test('ForyField stores canonical root type overrides', () {
+      const field = ForyField(
+        id: 3,
+        nullable: true,
+        ref: false,
+        dynamic: true,
+        type: MapType(
+          key: StringType(),
+          value: ListType(
+            element: Int32Type(encoding: Encoding.fixed),
+          ),
+        ),
+      );
+      expect(field.id, equals(3));
+      expect(field.nullable, isTrue);
+      expect(field.dynamic, isTrue);
+      expect(field.type, isA<MapType>());
     });
 
-    test('accepts custom key and value TypeSpecs', () {
-      const mt = MapType(
-        key: ValueType.ref(),
-        value: ValueType.refNullable(),
+    test('container sugar stores nested element and value overrides', () {
+      const listField = ListField(
+        element: DeclaredType(ref: true),
+      );
+      const mapField = MapField(
+        value: ListType(
+          element: Int32Type(encoding: Encoding.fixed),
+        ),
       );
-      expect(mt.key.options, hasLength(1));
-      expect(mt.value.options, hasLength(2));
+      expect((listField.element as DeclaredType).ref, isTrue);
+      final nestedList = mapField.value as ListType;
+      final nestedElement = nestedList.element as Int32Type;
+      expect(nestedElement.encoding, equals(Encoding.fixed));
     });
   });
 }
diff --git a/dart/packages/fory/test/unsigned_serializer_test.dart b/dart/packages/fory/test/unsigned_serializer_test.dart
index 6459f0f60..bb1f9f23b 100644
--- a/dart/packages/fory/test/unsigned_serializer_test.dart
+++ b/dart/packages/fory/test/unsigned_serializer_test.dart
@@ -33,58 +33,58 @@ final Uint64 _uint64Max = Uint64.parseHex('ffffffffffffffff');
 class UnsignedFields {
   UnsignedFields();
 
-  @Uint8Type()
+  @ForyField(type: Uint8Type())
   int u8 = 0;
 
-  @Uint16Type()
+  @ForyField(type: Uint16Type())
   int u16 = 0;
 
-  @Uint32Type(compress: true)
+  @ForyField(type: Uint32Type(encoding: Encoding.varint))
   int u32Var = 0;
 
-  @Uint32Type(compress: false)
+  @ForyField(type: Uint32Type(encoding: Encoding.fixed))
   int u32Fixed = 0;
 
-  @Uint64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Uint64Type(encoding: Encoding.varint))
   Uint64 u64Var = Uint64(0);
 
-  @Uint64Type(encoding: LongEncoding.fixed)
+  @ForyField(type: Uint64Type(encoding: Encoding.fixed))
   Uint64 u64Fixed = Uint64(0);
 
-  @Uint64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Uint64Type(encoding: Encoding.tagged))
   Uint64 u64Tagged = Uint64(0);
 
-  @Uint64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Uint64Type(encoding: Encoding.varint))
   int u64VarInt = 0;
 
-  @Uint64Type(encoding: LongEncoding.fixed)
+  @ForyField(type: Uint64Type(encoding: Encoding.fixed))
   int u64FixedInt = 0;
 
-  @Uint64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Uint64Type(encoding: Encoding.tagged))
   int u64TaggedInt = 0;
 
-  @Uint8Type()
+  @ForyField(type: Uint8Type())
   int? u8Nullable;
 
-  @Uint16Type()
+  @ForyField(type: Uint16Type())
   int? u16Nullable;
 
-  @Uint32Type(compress: true)
+  @ForyField(type: Uint32Type(encoding: Encoding.varint))
   int? u32VarNullable;
 
-  @Uint32Type(compress: false)
+  @ForyField(type: Uint32Type(encoding: Encoding.fixed))
   int? u32FixedNullable;
 
-  @Uint64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Uint64Type(encoding: Encoding.varint))
   Uint64? u64VarNullable;
 
-  @Uint64Type(encoding: LongEncoding.fixed)
+  @ForyField(type: Uint64Type(encoding: Encoding.fixed))
   Uint64? u64FixedNullable;
 
-  @Uint64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Uint64Type(encoding: Encoding.tagged))
   Uint64? u64TaggedNullable;
 
-  @Uint64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Uint64Type(encoding: Encoding.varint))
   int? u64VarIntNullable;
 }
 
@@ -92,7 +92,7 @@ class UnsignedFields {
 class UnsignedMetadataReader {
   UnsignedMetadataReader();
 
-  @Uint8Type()
+  @ForyField(type: Uint8Type())
   int u8 = 0;
 
   int extra = 42;
@@ -102,13 +102,13 @@ class UnsignedMetadataReader {
 class UnsignedIntFieldsReader {
   UnsignedIntFieldsReader();
 
-  @Uint64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Uint64Type(encoding: Encoding.varint))
   int u64Var = 0;
 
-  @Uint64Type(encoding: LongEncoding.fixed)
+  @ForyField(type: Uint64Type(encoding: Encoding.fixed))
   int u64Fixed = 0;
 
-  @Uint64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Uint64Type(encoding: Encoding.tagged))
   int u64Tagged = 0;
 }
 
@@ -116,13 +116,13 @@ class UnsignedIntFieldsReader {
 class UnsignedWrapperFields {
   UnsignedWrapperFields();
 
-  @Uint64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Uint64Type(encoding: Encoding.varint))
   Uint64 u64Var = Uint64(0);
 
-  @Uint64Type(encoding: LongEncoding.fixed)
+  @ForyField(type: Uint64Type(encoding: Encoding.fixed))
   Uint64 u64Fixed = Uint64(0);
 
-  @Uint64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Uint64Type(encoding: Encoding.tagged))
   Uint64 u64Tagged = Uint64(0);
 }
 
@@ -130,13 +130,13 @@ class UnsignedWrapperFields {
 class UnsignedWrapperAsIntFields {
   UnsignedWrapperAsIntFields();
 
-  @Uint64Type(encoding: LongEncoding.varint)
+  @ForyField(type: Uint64Type(encoding: Encoding.varint))
   int u64Var = 0;
 
-  @Uint64Type(encoding: LongEncoding.fixed)
+  @ForyField(type: Uint64Type(encoding: Encoding.fixed))
   int u64Fixed = 0;
 
-  @Uint64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Uint64Type(encoding: Encoding.tagged))
   int u64Tagged = 0;
 }
 
@@ -422,7 +422,9 @@ void main() {
       expect(_remoteFieldTypeId(roundTrip, 'u8'), equals(TypeIds.uint8));
       expect(_remoteFieldTypeId(roundTrip, 'u16'), equals(TypeIds.uint16));
       expect(
-          _remoteFieldTypeId(roundTrip, 'u32_var'), equals(TypeIds.varUint32));
+        _remoteFieldTypeId(roundTrip, 'u32_var'),
+        equals(TypeIds.varUint32),
+      );
       expect(
           _remoteFieldTypeId(roundTrip, 'u32_fixed'), equals(TypeIds.uint32));
       expect(
@@ -458,6 +460,39 @@ void main() {
       expect(_remoteFieldNullable(roundTrip, 'u64_var_int_nullable'), isTrue);
     });
 
+    test('rejects out-of-range annotated unsigned fields', () {
+      final cases = <({String name, UnsignedFields value})>[
+        (
+          name: 'u8',
+          value: _smallUnsignedFields()..u8 = 0x100,
+        ),
+        (
+          name: 'u16',
+          value: _smallUnsignedFields()..u16 = 0x10000,
+        ),
+        (
+          name: 'u32Var',
+          value: _smallUnsignedFields()..u32Var = -1,
+        ),
+        (
+          name: 'u32FixedNullable',
+          value: _smallUnsignedFields()..u32FixedNullable = 0x100000000,
+        ),
+      ];
+
+      for (final testCase in cases) {
+        for (final compatible in <bool>[false, true]) {
+          final fory = Fory(compatible: compatible);
+          _registerUnsignedFields(fory);
+          expect(
+            () => fory.serialize(testCase.value),
+            throwsA(isA<RangeError>()),
+            reason: '${testCase.name}, compatible=$compatible',
+          );
+        }
+      }
+    });
+
     test('web rejects JS-unsafe uint64 Dart int fields', () {
       final cases = <({String name, UnsignedFields value})>[
         (
diff --git a/dart/packages/fory/test/xlang_protocol_test.dart b/dart/packages/fory/test/xlang_protocol_test.dart
index 1c34bc856..be3b9bb76 100644
--- a/dart/packages/fory/test/xlang_protocol_test.dart
+++ b/dart/packages/fory/test/xlang_protocol_test.dart
@@ -51,14 +51,17 @@ void main() {
 
     test('deserializes BFLOAT16 and BFLOAT16_ARRAY wire values', () {
       final fory = Fory();
-      final scalarBytes = Uint8List.fromList(fory.serialize(Uint16(0xbf60)));
-      scalarBytes[2] = TypeIds.bfloat16;
+      final scalarBytes =
+          Uint8List.fromList(fory.serialize(Bfloat16.fromBits(0xbf60)));
       final arrayBytes = Uint8List.fromList(
         fory.serialize(
-          Uint16List.fromList(<int>[0x3f80, 0xbf80, 0x7fc1]),
+          Bfloat16List.fromList(<Bfloat16>[
+            Bfloat16.fromBits(0x3f80),
+            Bfloat16.fromBits(0xbf80),
+            Bfloat16.fromBits(0x7fc1),
+          ]),
         ),
       );
-      arrayBytes[2] = TypeIds.bfloat16Array;
 
       expect(fory.deserialize<Bfloat16>(scalarBytes).toBits(), equals(0xbf60));
       expect(
@@ -70,6 +73,16 @@ void main() {
       );
     });
 
+    test('serializes root builtins with an explicit wire type', () {
+      final fory = Fory();
+      final bytes = fory.serializeBuiltin(7, wireTypeId: TypeIds.varInt32);
+
+      expect(bytes[0], equals(0x02));
+      expect(bytes[1], equals(0xff));
+      expect(bytes[2], equals(TypeIds.varInt32));
+      expect(fory.deserialize<int>(bytes), equals(7));
+    });
+
     test('rejects out-of-band xlang payload headers', () {
       final fory = Fory();
       final bytes = Uint8List.fromList(fory.serialize('value'));
diff --git a/dart/test/workspace_smoke_test.dart b/dart/test/workspace_smoke_test.dart
index b6e3ee02b..1cb84607d 100644
--- a/dart/test/workspace_smoke_test.dart
+++ b/dart/test/workspace_smoke_test.dart
@@ -23,8 +23,8 @@ import 'package:test/test.dart';
 void main() {
   test('workspace root can use the rewritten fory package', () {
     final fory = Fory();
-    final bytes = fory.serialize(Int32(42));
-    final roundTrip = fory.deserialize<Int32>(bytes);
-    expect(roundTrip, equals(Int32(42)));
+    final bytes = fory.serialize(42);
+    final roundTrip = fory.deserialize<int>(bytes);
+    expect(roundTrip, equals(42));
   });
 }
diff --git a/docs/compiler/generated-code.md b/docs/compiler/generated-code.md
index a517f4f95..3462ba02d 100644
--- a/docs/compiler/generated-code.md
+++ b/docs/compiler/generated-code.md
@@ -889,8 +889,8 @@ final class Person {
   @ForyField(id: 1)
   String name = '';
 
-  @ForyField(id: 2)
-  Int32 id = Int32(0);
+  @ForyField(id: 2, type: Int32Type())
+  int id = 0;
 
   @ForyField(id: 7)
   List<Person_PhoneNumber> phones = <Person_PhoneNumber>[];
@@ -956,14 +956,14 @@ Nested types use flat underscore naming (e.g., `Person_PhoneNumber`, `Person_Pho
 
 Non-optional, non-ref lists of primitive types use typed arrays for zero-copy performance (e.g., `list<int32>` → `Int32List`).
 
-Reference tracking on list elements or map values uses `@ListType` / `@MapType` annotations:
+Reference tracking on list elements or map values uses the container sugar annotations:
 
 ```dart
-@ListType(element: ValueType.ref())
+@ListField(element: DeclaredType(ref: true))
 @ForyField(id: 3)
 List<Node> children = <Node>[];
 
-@MapType(value: ValueType.ref())
+@MapField(value: DeclaredType(ref: true))
 @ForyField(id: 2)
 Map<String, Node> byName = <String, Node>{};
 ```
@@ -1007,7 +1007,7 @@ void main() {
 
   final person = Person()
     ..name = 'Alice'
-    ..id = Int32(1);
+    ..id = 1;
 
   final bytes = fory.serialize(person);
   final roundTrip = fory.deserialize<Person>(bytes);
diff --git a/docs/compiler/index.md b/docs/compiler/index.md
index 7790961ec..605a4e609 100644
--- a/docs/compiler/index.md
+++ b/docs/compiler/index.md
@@ -198,7 +198,7 @@ Fory IDL types map to native types in each language:
 
 | Fory IDL Type | Java      | Python         | Go       | Rust     | C++           | C#       | JavaScript | Swift    | Dart     |
 | ------------- | --------- | -------------- | -------- | -------- | ------------- | -------- | ---------- | -------- | -------- |
-| `int32`       | `int`     | `pyfory.int32` | `int32`  | `i32`    | `int32_t`     | `int`    | `number`   | `Int32`  | `Int32`  |
+| `int32`       | `int`     | `pyfory.int32` | `int32`  | `i32`    | `int32_t`     | `int`    | `number`   | `Int32`  | `int`    |
 | `string`      | `String`  | `str`          | `string` | `String` | `std::string` | `string` | `string`   | `String` | `String` |
 | `bool`        | `boolean` | `bool`         | `bool`   | `bool`   | `bool`        | `bool`   | `boolean`  | `Bool`   | `bool`   |
 
diff --git a/docs/compiler/schema-idl.md b/docs/compiler/schema-idl.md
index 9f514fd0d..315fc97e9 100644
--- a/docs/compiler/schema-idl.md
+++ b/docs/compiler/schema-idl.md
@@ -969,12 +969,12 @@ apply to elements. `repeated` is accepted as an alias for `list`.
 
 **List modifier mapping:**
 
-| Fory IDL                | Java                                           | Python                                  | Go                      | Rust                  | C++                                       | Dart                                                 |
-| ----------------------- | ---------------------------------------------- | --------------------------------------- | ----------------------- | --------------------- | ----------------------------------------- | ---------------------------------------------------- |
-| `optional list<string>` | `List<String>` + `@ForyField(nullable = true)` | `Optional[List[str]]`                   | `[]string` + `nullable` | `Option<Vec<String>>` | `std::optional<std::vector<std::string>>` | `List<String>?`                                      |
-| `list<optional string>` | `List<String>` (nullable elements)             | `List[Optional[str]]`                   | `[]*string`             | `Vec<Option<String>>` | `std::vector<std::optional<std::string>>` | `List<String?>`                                      |
-| `ref list<User>`        | `List<User>` + `@ForyField(ref = true)`        | `List[User]` + `pyfory.field(ref=True)` | `[]User` + `ref`        | `Arc<Vec<User>>`      | `std::shared_ptr<std::vector<User>>`      | `List<User>` + `@ForyField(ref: true)`               |
-| `list<ref User>`        | `List<User>`                                   | `List[User]`                            | `[]*User` + `ref=false` | `Vec<Arc<User>>`      | `std::vector<std::shared_ptr<User>>`      | `List<User>` + `@ListType(element: ValueType.ref())` |
+| Fory IDL                | Java                                           | Python                                  | Go                      | Rust                  | C++                                       | Dart                                                          |
+| ----------------------- | ---------------------------------------------- | --------------------------------------- | ----------------------- | --------------------- | ----------------------------------------- | ------------------------------------------------------------- |
+| `optional list<string>` | `List<String>` + `@ForyField(nullable = true)` | `Optional[List[str]]`                   | `[]string` + `nullable` | `Option<Vec<String>>` | `std::optional<std::vector<std::string>>` | `List<String>?`                                               |
+| `list<optional string>` | `List<String>` (nullable elements)             | `List[Optional[str]]`                   | `[]*string`             | `Vec<Option<String>>` | `std::vector<std::optional<std::string>>` | `List<String?>`                                               |
+| `ref list<User>`        | `List<User>` + `@ForyField(ref = true)`        | `List[User]` + `pyfory.field(ref=True)` | `[]User` + `ref`        | `Arc<Vec<User>>`      | `std::shared_ptr<std::vector<User>>`      | `List<User>` + `@ForyField(ref: true)`                        |
+| `list<ref User>`        | `List<User>`                                   | `List[User]`                            | `[]*User` + `ref=false` | `Vec<Arc<User>>`      | `std::vector<std::shared_ptr<User>>`      | `List<User>` + `@ListField(element: DeclaredType(ref: true))` |
 
 Use `ref(thread_safe=false)` in Fory IDL (or `[(fory).thread_safe_pointer = false]` in protobuf)
 to generate `Rc` instead of `Arc` in Rust.
@@ -1062,10 +1062,10 @@ Fory IDL provides fixed-width signed integers (varint encoding for 32/64-bit by
 
 | Fory IDL | Java    | Python         | Go      | Rust  | C++       | JavaScript         | Dart    |
 | -------- | ------- | -------------- | ------- | ----- | --------- | ------------------ | ------- |
-| `int8`   | `byte`  | `pyfory.int8`  | `int8`  | `i8`  | `int8_t`  | `number`           | `Int8`  |
-| `int16`  | `short` | `pyfory.int16` | `int16` | `i16` | `int16_t` | `number`           | `Int16` |
-| `int32`  | `int`   | `pyfory.int32` | `int32` | `i32` | `int32_t` | `number`           | `Int32` |
-| `int64`  | `long`  | `pyfory.int64` | `int64` | `i64` | `int64_t` | `bigint \| number` | `int`   |
+| `int8`   | `byte`  | `pyfory.int8`  | `int8`  | `i8`  | `int8_t`  | `number`           | `int`   |
+| `int16`  | `short` | `pyfory.int16` | `int16` | `i16` | `int16_t` | `number`           | `int`   |
+| `int32`  | `int`   | `pyfory.int32` | `int32` | `i32` | `int32_t` | `number`           | `int`   |
+| `int64`  | `long`  | `pyfory.int64` | `int64` | `i64` | `int64_t` | `bigint \| number` | `Int64` |
 
 Fory IDL provides fixed-width unsigned integers (varint encoding for 32/64-bit by default):
 
@@ -1080,10 +1080,10 @@ Fory IDL provides fixed-width unsigned integers (varint encoding for 32/64-bit b
 
 | Fory IDL | Java    | Python          | Go       | Rust  | C++        | JavaScript         | Dart     |
 | -------- | ------- | --------------- | -------- | ----- | ---------- | ------------------ | -------- |
-| `uint8`  | `short` | `pyfory.uint8`  | `uint8`  | `u8`  | `uint8_t`  | `number`           | `UInt8`  |
-| `uint16` | `int`   | `pyfory.uint16` | `uint16` | `u16` | `uint16_t` | `number`           | `UInt16` |
-| `uint32` | `long`  | `pyfory.uint32` | `uint32` | `u32` | `uint32_t` | `number`           | `UInt32` |
-| `uint64` | `long`  | `pyfory.uint64` | `uint64` | `u64` | `uint64_t` | `bigint \| number` | `int`    |
+| `uint8`  | `short` | `pyfory.uint8`  | `uint8`  | `u8`  | `uint8_t`  | `number`           | `int`    |
+| `uint16` | `int`   | `pyfory.uint16` | `uint16` | `u16` | `uint16_t` | `number`           | `int`    |
+| `uint32` | `long`  | `pyfory.uint32` | `uint32` | `u32` | `uint32_t` | `number`           | `int`    |
+| `uint64` | `long`  | `pyfory.uint64` | `uint64` | `u64` | `uint64_t` | `bigint \| number` | `Uint64` |
 
 #### Integer Encoding Variants
 
@@ -1257,10 +1257,10 @@ message Config {
 
 **Language Mapping:**
 
-| Fory IDL             | Java                   | Python            | Go                 | Rust                    | C++                              | JavaScript            | Dart                 |
-| -------------------- | ---------------------- | ----------------- | ------------------ | ----------------------- | -------------------------------- | --------------------- | -------------------- |
-| `map<string, int32>` | `Map<String, Integer>` | `Dict[str, int]`  | `map[string]int32` | `HashMap<String, i32>`  | `std::map<std::string, int32_t>` | `Map<string, number>` | `Map<String, Int32>` |
-| `map<string, User>`  | `Map<String, User>`    | `Dict[str, User]` | `map[string]User`  | `HashMap<String, User>` | `std::map<std::string, User>`    | `Map<string, User>`   | `Map<String, User>`  |
+| Fory IDL             | Java                   | Python            | Go                 | Rust                    | C++                              | JavaScript            | Dart                |
+| -------------------- | ---------------------- | ----------------- | ------------------ | ----------------------- | -------------------------------- | --------------------- | ------------------- |
+| `map<string, int32>` | `Map<String, Integer>` | `Dict[str, int]`  | `map[string]int32` | `HashMap<String, i32>`  | `std::map<std::string, int32_t>` | `Map<string, number>` | `Map<String, int>`  |
+| `map<string, User>`  | `Map<String, User>`    | `Dict[str, User]` | `map[string]User`  | `HashMap<String, User>` | `std::map<std::string, User>`    | `Map<string, User>`   | `Map<String, User>` |
 
 **Key Type Restrictions:**
 
diff --git a/docs/guide/dart/basic-serialization.md b/docs/guide/dart/basic-serialization.md
index 1f261937e..87ea752df 100644
--- a/docs/guide/dart/basic-serialization.md
+++ b/docs/guide/dart/basic-serialization.md
@@ -43,7 +43,9 @@ class Person {
   Person();
 
   String name = '';
-  Int32 age = Int32(0);
+
+  @ForyField(type: Int32Type())
+  int age = 0;
 }
 
 void main() {
@@ -57,7 +59,7 @@ void main() {
 
   final person = Person()
     ..name = 'Ada'
-    ..age = Int32(36);
+    ..age = 36;
 
   final bytes = fory.serialize(person);
   final roundTrip = fory.deserialize<Person>(bytes);
@@ -85,7 +87,7 @@ You can serialize collection values directly:
 final fory = Fory();
 final bytes = fory.serialize(<Object?>[
   'hello',
-  Int32(42),
+  42,
   true,
 ]);
 final value = fory.deserialize<List<Object?>>(bytes);
@@ -117,8 +119,8 @@ If you want to avoid allocating a new `Uint8List` on every call, use `serializeT
 final fory = Fory();
 final buffer = Buffer();
 
-fory.serializeTo(Int32(42), buffer);
-final value = fory.deserializeFrom<Int32>(buffer);
+fory.serializeTo('Ada', buffer);
+final value = fory.deserializeFrom<String>(buffer);
 ```
 
 This is an optimization. For most applications the default `serialize`/`deserialize` pair is fine.
diff --git a/docs/guide/dart/code-generation.md b/docs/guide/dart/code-generation.md
index 495a6dd89..d24d8c229 100644
--- a/docs/guide/dart/code-generation.md
+++ b/docs/guide/dart/code-generation.md
@@ -43,7 +43,9 @@ class User {
   User();
 
   String name = '';
-  Int32 age = Int32(0);
+
+  @ForyField(type: Int32Type())
+  int age = 0;
   Address address = Address();
 }
 ```
diff --git a/docs/guide/dart/cross-language.md b/docs/guide/dart/cross-language.md
index ed843d3d3..f5d7b5476 100644
--- a/docs/guide/dart/cross-language.md
+++ b/docs/guide/dart/cross-language.md
@@ -74,14 +74,16 @@ class Person {
   Person();
 
   String name = '';
-  Int32 age = Int32(0);
+
+  @ForyField(type: Int32Type())
+  int age = 0;
 }
 
 final fory = Fory();
 PersonFory.register(fory, Person, id: 100);
 final bytes = fory.serialize(Person()
   ..name = 'Alice'
-  ..age = Int32(30));
+  ..age = 30);
 ```
 
 ### Java
@@ -104,7 +106,7 @@ final fory = Fory(compatible: true);
 PersonFory.register(fory, Person, id: 100);
 final bytes = fory.serialize(Person()
   ..name = 'Alice'
-  ..age = Int32(30));
+  ..age = 30);
 ```
 
 ### CSharp
@@ -134,7 +136,7 @@ final fory = Fory();
 PersonFory.register(fory, Person, id: 100);
 final bytes = fory.serialize(Person()
   ..name = 'Alice'
-  ..age = Int32(30));
+  ..age = 30);
 ```
 
 ### Go
@@ -159,16 +161,18 @@ Fory matches fields by name or by stable field ID. For robust cross-language int
 1. Use the same type identity on every side (same numeric ID or same `namespace + typeName`).
 2. Assign stable `@ForyField(id: ...)` values to all fields before shipping the first payload.
 3. Keep field names consistent or rely on IDs, since Dart typically uses `lowerCamelCase` while Go uses `PascalCase` for exported fields and C# often uses `PascalCase` properties.
-4. Use compatible numeric types: `Int32` in Dart for Java `int`, Go `int32`, and C# `int`; `double` in Dart for 64-bit floats; `Float32` for 32-bit.
+4. Use explicit numeric field metadata: `@ForyField(type: Int32Type())` in Dart for Java `int`, Go `int32`, and C# `int`; `double` in Dart for 64-bit floats; `Float32` for 32-bit; `Int64` / `Uint64` for full-range 64-bit values.
 5. Use `Timestamp`, `LocalDate`, and `Duration` for temporal fields rather than raw `DateTime`.
 6. Validate real round trips across all languages before shipping.
 
 ## Type Mapping Notes for Dart
 
-Because Dart `int` is not itself a promise about the exact xlang wire width, prefer wrappers or numeric field annotations when exact cross-language interpretation matters:
+Because Dart `int` is not itself a promise about the exact xlang wire width, prefer explicit field metadata when exact cross-language interpretation matters:
 
-- `Int32` for xlang `int32`
-- `Uint32` for xlang `uint32`
+- `@ForyField(type: Int32Type())` for xlang `int32`
+- `@ForyField(type: Uint32Type())` for xlang `uint32`
+- `@ForyField(type: Int8Type())` / `@ForyField(type: Int16Type())` / `@ForyField(type: Uint8Type())` / `@ForyField(type: Uint16Type())` for narrower integer widths
+- `Int64` and `Uint64` for full-range 64-bit values on web
 - `Float16`, `Bfloat16`, and `Float32` for reduced-width floating point
 - `Float16List` and `Bfloat16List` for 16-bit floating-point array payloads
 - `Timestamp`, `LocalDate`, and `Duration` for explicit temporal semantics
diff --git a/docs/guide/dart/field-configuration.md b/docs/guide/dart/field-configuration.md
index 5e0b439f0..a7d02904b 100644
--- a/docs/guide/dart/field-configuration.md
+++ b/docs/guide/dart/field-configuration.md
@@ -88,32 +88,47 @@ Controls whether Fory writes the concrete runtime type of the field value into t
 Object? payload;  // can hold any registered type at runtime
 ```
 
-## Numeric Field Annotations
+## Numeric Field Types
 
-Dart `int` is a 64-bit value at runtime. When exchanging messages with Java, Go, or C#, the receiving side may expect a narrower integer. Use a numeric annotation to pin the exact wire format:
+Dart `int` is a 64-bit value at runtime. When exchanging messages with Java, Go, or C#, the receiving side may expect a narrower integer. Use `@ForyField(type: ...)` to pin the exact wire format:
 
 ```dart
 @ForyStruct()
 class Sample {
   Sample();
 
-  @Int32Type(compress: false)   // always writes 4 bytes
+  @ForyField(type: Int32Type(encoding: Encoding.fixed))
   int fixedWidthInt = 0;
 
-  @Int64Type(encoding: LongEncoding.tagged)  // variable-length encoding
-  int compactLong = 0;
+  @ForyField(type: Int64Type(encoding: Encoding.tagged))
+  Int64 compactLong = Int64(0);
 
-  @Uint32Type(compress: true)   // variable-length unsigned
+  @ForyField(type: Uint32Type())
   int smallUnsigned = 0;
 }
 ```
 
-Available annotations: `@Int32Type`, `@Int64Type`, `@Uint8Type`, `@Uint16Type`, `@Uint32Type`, `@Uint64Type`.
+Available scalar type nodes include `Int8Type`, `Int16Type`, `Int32Type`,
+`Int64Type`, `Uint8Type`, `Uint16Type`, `Uint32Type`, `Uint64Type`,
+`Float16Type`, `Bfloat16Type`, and `Float32Type`.
 
-Alternatively, use explicit wrapper types such as `Int32`, `Int64`, `Uint32`,
-and `Uint64` as described in [Supported Types](supported-types.md). Wrappers use
-compact varint encodings by default; use annotations or generated field
-metadata when a fixed-width or tagged encoding is required.
+For nested containers, use `ListField`, `SetField`, `MapField`, or a full
+`ForyField(type: ...)` tree:
+
+```dart
+@MapField(
+  value: ListType(
+    element: Int32Type(encoding: Encoding.fixed),
+  ),
+)
+Map<String, List<int?>> metrics = <String, List<int?>>{};
+```
+
+Generic `List<int>` still uses the `list` wire type even with primitive element
+specs. Packed `*_array` wire kinds come from dedicated carriers such as
+`Int32List`, `Uint32List`, `Int64List`, and `Uint64List`. If you annotate a
+generic `List<int>` with a non-null fixed-width primitive element spec, code
+generation rejects it and tells you to use the matching typed list carrier.
 
 ## Aligning Fields Across Languages
 
diff --git a/docs/guide/dart/index.md b/docs/guide/dart/index.md
index cab137c48..34e1da64e 100644
--- a/docs/guide/dart/index.md
+++ b/docs/guide/dart/index.md
@@ -68,7 +68,9 @@ class Person {
   Person();
 
   String name = '';
-  Int32 age = Int32(0);
+
+  @ForyField(type: Int32Type())
+  int age = 0;
   Color favoriteColor = Color.red;
   List<String> tags = <String>[];
 }
@@ -90,7 +92,7 @@ void main() {
 
   final person = Person()
     ..name = 'Ada'
-    ..age = Int32(36)
+    ..age = 36
     ..favoriteColor = Color.blue
     ..tags = <String>['engineer', 'mathematician'];
 
@@ -114,8 +116,9 @@ dart run build_runner build --delete-conflicting-outputs
 - `fory.serialize(value)` — returns `Uint8List` bytes
 - `fory.deserialize<T>(bytes)` — returns a `T`
 - `@ForyStruct()` — marks a class for code generation
-- `@ForyField(...)` — per-field options (skip, ID, nullability, references)
-- Integer wrappers: `Int8`, `Int16`, `Int32`, `Int64`, `Uint8`, `Uint16`, `Uint32`, `Uint64`
+- `@ForyField(...)` — per-field options and canonical `type:` overrides
+- `@ListField(...)`, `@SetField(...)`, `@MapField(...)` — container sugar for nested `type:` trees
+- Exact-value wrappers: `Int64`, `Uint64`, `Float16`, `Bfloat16`, `Float32`
 - Float wrappers: `Float16`, `Bfloat16`, `Float32`
 - 16-bit float arrays: `Float16List`, `Bfloat16List`
 - Time types: `LocalDate`, `Timestamp`, `Duration`
diff --git a/docs/guide/dart/schema-evolution.md b/docs/guide/dart/schema-evolution.md
index 8d33297a9..1afbbdeb4 100644
--- a/docs/guide/dart/schema-evolution.md
+++ b/docs/guide/dart/schema-evolution.md
@@ -71,7 +71,7 @@ If you add field IDs after payloads are already in production, existing stored m
 **Unsafe changes** (may break existing messages):
 
 - Reuse an existing field ID for a different field.
-- Change a field's type to an incompatible type (e.g., `Int32` → `String`).
+- Change a field's type to an incompatible type (e.g., `@ForyField(type: Int32Type()) int` → `String`).
 - Change the registration identity (`id`, `namespace`, or `typeName`) of a type after messages are in production.
 - Change a field's logical meaning without changing its ID.
 
diff --git a/docs/guide/dart/supported-types.md b/docs/guide/dart/supported-types.md
index e7ee82e73..4b43d10e8 100644
--- a/docs/guide/dart/supported-types.md
+++ b/docs/guide/dart/supported-types.md
@@ -25,17 +25,17 @@ This page lists the Dart types you can use in Fory messages, and flags where you
 
 The following Dart types serialize directly without any special handling:
 
-| Dart type            | Cross-language notes                                                                                        |
-| -------------------- | ----------------------------------------------------------------------------------------------------------- |
-| `bool`               | Direct mapping                                                                                              |
-| `int`                | Serialized as 64-bit by default. Use wrappers or `@Int32Type` etc. when the peer expects a narrower integer |
-| `double`             | Maps to 64-bit float. Use `Float32` wrapper when the peer expects 32-bit                                    |
-| `String`             | Direct mapping                                                                                              |
-| `Uint8List`          | Binary blob                                                                                                 |
-| `List`, `Set`, `Map` | Supported; element types must also be supported                                                             |
-| `DateTime`           | Use `Timestamp` or `LocalDate` wrappers for explicit semantics                                              |
-
-## Integer Wrappers
+| Dart type            | Cross-language notes                                                                                                                                           |
+| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `bool`               | Direct mapping                                                                                                                                                 |
+| `int`                | Serialized as 64-bit by default. Use `@ForyField(type: Int8Type/Int16Type/Int32Type/Uint8Type/Uint16Type/Uint32Type)` when the peer expects a narrower integer |
+| `double`             | Maps to 64-bit float. Use `Float32` wrapper when the peer expects 32-bit                                                                                       |
+| `String`             | Direct mapping                                                                                                                                                 |
+| `Uint8List`          | Binary blob                                                                                                                                                    |
+| `List`, `Set`, `Map` | Supported; element types must also be supported                                                                                                                |
+| `DateTime`           | Use `Timestamp` or `LocalDate` wrappers for explicit semantics                                                                                                 |
+
+## Integer Fields
 
 Dart VM/native `int` can represent signed 64-bit values, while Dart web `int`
 is limited to JavaScript-safe integer precision. If the peer language expects a
@@ -43,23 +43,31 @@ is limited to JavaScript-safe integer precision. If the peer language expects a
 the deserialization may fail or silently truncate. For browser and Flutter web
 precision rules, see [Web Platform Support](web-platform-support.md).
 
-Use an integer wrapper or field annotation to select the wire type explicitly:
+Use field metadata to select the wire type explicitly for 8/16/32-bit fields:
 
 ```dart
-final Int8 tiny = Int8(-1);        // 8-bit signed
-final Int16 shortValue = Int16(7); // 16-bit signed
-final Int32 age = Int32(36);       // 32-bit signed, varint by default
-final Int64 seq = Int64(0);        // signed 64-bit, varint by default
-final Uint8 flags = Uint8(255);    // 8-bit unsigned
-final Uint16 port = Uint16(65535); // 16-bit unsigned
-final Uint32 count = Uint32(4000000000); // 32-bit unsigned, varint by default
-final Uint64 offset = Uint64(0);   // unsigned 64-bit, varint by default
+@ForyStruct()
+class Metrics {
+  Metrics();
+
+  @ForyField(type: Int8Type())
+  int tiny = 0;
+
+  @ForyField(type: Int32Type(encoding: Encoding.fixed))
+  int age = 0;
+
+  @ForyField(type: Uint32Type())
+  int count = 0;
+
+  Int64 sequence = Int64(0);
+  Uint64 offset = Uint64(0);
+}
 ```
 
-Each wrapper clamps or normalizes the stored value to the target bit width.
-Root `Int32`, `Int64`, `Uint32`, and `Uint64` values use compact varint wire
-types by default. Use `@Int64Type`, `@Uint32Type`, `@Uint64Type`, or generated
-field metadata when a fixed-width or tagged encoding is required.
+Generated serializers range-check annotated `int` values before writing them.
+Use `Int64` and `Uint64` when you need full-range 64-bit values, especially on
+web. A plain root `int` value serializes as xlang `int64`; exact 8/16/32-bit
+wire widths are selected through field metadata or low-level `Buffer` APIs.
 
 On Dart VM, `Int64` and `Uint64` are extension types over `int`. Once a value is
 passed through an `Object`-typed dynamic/root boundary, the VM cannot recover
@@ -115,7 +123,9 @@ class User {
   User();
 
   String name = '';
-  Int32 age = Int32(0); // use Int32 when peers expect a 32-bit integer
+
+  @ForyField(type: Int32Type())
+  int age = 0; // use explicit field metadata when peers expect a 32-bit integer
 }
 ```
 
@@ -123,11 +133,22 @@ See [Code Generation](code-generation.md).
 
 ## Collections
 
-Fory supports `List<T>`, `Set<T>`, and `Map<K, V>`. Element and key types must also be serializable types. Avoid using mutable objects as map keys.
+Fory supports `List<T>`, `Set<T>`, and `Map<K, V>`. Element and key types must
+also be serializable types. Avoid using mutable objects as map keys.
+
+Generic `List<int>` with primitive element metadata still uses the `list` wire
+type. Dedicated array wire kinds come from dedicated carriers:
+
+- `List<bool>` for `bool_array`
+- `Int8List`, `Int16List`, `Int32List`, `Int64List`
+- `Uint8List`, `Uint16List`, `Uint32List`, `Uint64List`
+- `Float16List`, `Bfloat16List`, `Float32List`, `Float64List`
 
 ## Compatibility Tip
 
-When in doubt about whether a Dart type will match what the peer expects, use the explicit wrapper types. Guessing the wrong numeric width is one of the most common cross-language bugs.
+When in doubt about whether a Dart type will match what the peer expects, make
+the width explicit with `@ForyField(type: ...)`. Guessing the wrong numeric
+width is one of the most common cross-language bugs.
 
 ## Related Topics
 
diff --git a/docs/guide/dart/troubleshooting.md b/docs/guide/dart/troubleshooting.md
index 43df08e61..3938e95a5 100644
--- a/docs/guide/dart/troubleshooting.md
+++ b/docs/guide/dart/troubleshooting.md
@@ -74,7 +74,7 @@ Checklist:
 
 1. Same registration identity on both sides (same numeric ID **or** same `namespace + typeName`).
 2. Stable `@ForyField(id: ...)` assigned before the first payload was produced.
-3. Compatible numeric widths — use `Int32` in Dart when the peer field is `int` (Java), `int32` (Go), or `int` (C#).
+3. Compatible numeric widths — use `@ForyField(type: Int32Type())` in Dart when the peer field is `int` (Java), `int32` (Go), or `int` (C#).
 4. `Timestamp` / `LocalDate` instead of raw `DateTime` for date/time fields.
 5. `compatible: true` on **both** sides if using schema evolution.
 
@@ -114,9 +114,9 @@ class FileBlock {
 }
 ```
 
-`@Int64Type` changes the wire encoding for a Dart `int` field, but it does not
-remove the web integer precision limit. Use `Int64` for full-range signed
-values and `Uint64` for full-range unsigned values. See
+`@ForyField(type: Int64Type(...))` changes the wire encoding for a Dart `int`
+field, but it does not remove the web integer precision limit. Use `Int64` for
+full-range signed values and `Uint64` for full-range unsigned values. See
 [Web Platform Support](web-platform-support.md) for the full browser support
 matrix and migration guidance.
 
diff --git a/docs/guide/dart/web-platform-support.md b/docs/guide/dart/web-platform-support.md
index 3ca615426..4cb63da47 100644
--- a/docs/guide/dart/web-platform-support.md
+++ b/docs/guide/dart/web-platform-support.md
@@ -93,21 +93,22 @@ by JavaScript numbers and are precise only in the JS-safe integer range:
 
 Use this rule when choosing field types:
 
-| Logical value                            | Recommended Dart field type on web                                   | Notes                                                                |
-| ---------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- |
-| Signed 64-bit value within JS-safe range | `int`                                                                | Works with default `int64` mapping and `@Int64Type` encodings.       |
-| Full signed 64-bit range                 | `Int64`                                                              | Preserves values outside the JS-safe range.                          |
-| Unsigned 64-bit value                    | `Uint64`                                                             | Required for values that do not fit in signed or JS-safe Dart `int`. |
-| 8/16/32-bit integer                      | `Int8`, `Int16`, `Int32`, `Uint8`, `Uint16`, `Uint32` or annotations | Use wrappers or numeric annotations to match peer runtimes exactly.  |
+| Logical value                            | Recommended Dart field type on web | Notes                                                                                |
+| ---------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------ |
+| Signed 64-bit value within JS-safe range | `int`                              | Works with default `int64` mapping and `@ForyField(type: Int64Type(...))` encodings. |
+| Full signed 64-bit range                 | `Int64`                            | Preserves values outside the JS-safe range.                                          |
+| Unsigned 64-bit value                    | `Uint64`                           | Required for values that do not fit in signed or JS-safe Dart `int`.                 |
+| 8/16/32-bit integer                      | `int` + `@ForyField(type: ...)`    | Use explicit field metadata to match peer runtimes exactly.                          |
 
-`@Int64Type` controls the wire encoding of a Dart `int` field:
+`@ForyField(type: Int64Type(...))` controls the wire encoding of a Dart `int`
+field:
 
 ```dart
 @ForyStruct()
 class SafeCounter {
   SafeCounter();
 
-  @Int64Type(encoding: LongEncoding.tagged)
+  @ForyField(type: Int64Type(encoding: Encoding.tagged))
   int count = 0; // keep web values inside the JS-safe range
 }
 ```
diff --git a/docs/guide/xlang/field-reference-tracking.md b/docs/guide/xlang/field-reference-tracking.md
index 2aafbf2ee..d7bfb4ebf 100644
--- a/docs/guide/xlang/field-reference-tracking.md
+++ b/docs/guide/xlang/field-reference-tracking.md
@@ -132,11 +132,11 @@ public class Document {
     String title;
 
     // Enable ref tracking for this field
-    @ForyField(trackingRef = true)
+    @ForyField(ref = true)
     Author author;
 
     // Shared across documents, track refs to avoid duplicates
-    @ForyField(trackingRef = true)
+    @ForyField(ref = true)
     List<Tag> tags;
 }
 ```
diff --git a/docs/specification/xlang_serialization_spec.md b/docs/specification/xlang_serialization_spec.md
index 629d26860..c109a6d28 100644
--- a/docs/specification/xlang_serialization_spec.md
+++ b/docs/specification/xlang_serialization_spec.md
@@ -1437,9 +1437,14 @@ Object value layout:
 The schema hash is written only when class-version checking is enabled. It is the low 32 bits of a
 MurmurHash3 x64_128 of the struct fingerprint string:
 
-- For each field, build `<field_id_or_name>,<type_id>,<ref>,<nullable>;`.
+- For each field, build `<field_id_or_name>,<field_type_fingerprint>;`.
 - Field identifier is the tag ID if present, otherwise the snake_case field name.
 - Sort by field identifier lexicographically before concatenation.
+- `field_type_fingerprint` is recursive:
+  - Leaf: `<type_id>,<ref>,<nullable>`
+  - `LIST` / `SET`: `<type_id>,<ref>,<nullable>[<element_fingerprint>]`
+  - `MAP`: `<type_id>,<ref>,<nullable>[<key_fingerprint>|<value_fingerprint>]`
+- Nested container element/key/value fingerprints include nested type ID, container shape, and effective integer encoding, but nested `nullable` and `ref` policy are always hashed as `0`. Only the root field `nullable` and `ref` bits participate in schema hash, because nested reads honor the wire null/ref flags directly.
 
 Field values are serialized in Fory order. Primitive fields are written as raw values (nullable
 primitives include a null flag). Non-primitive fields write ref/null flags as needed and then the
diff --git a/go/fory/field_info.go b/go/fory/field_info.go
index c622a5280..ba3e52d72 100644
--- a/go/fory/field_info.go
+++ b/go/fory/field_info.go
@@ -529,6 +529,8 @@ type FieldFingerprintInfo struct {
 	FieldID int
 	// FieldName is the snake_case field name (used when FieldID < 0)
 	FieldName string
+	// TypeSpec is the effective nested field type specification when available.
+	TypeSpec *TypeSpec
 	// TypeID is the Fory type ID for the field
 	TypeID TypeId
 	// Ref is true if reference tracking is enabled for this field
@@ -541,7 +543,8 @@ type FieldFingerprintInfo struct {
 //
 // Fingerprint Format:
 //
-//	Each field contributes: "<field_id_or_name>,<type_id>,<ref>,<nullable>;"
+//	Each field contributes:
+//	"<field_id_or_name>,<type_id>,<ref>,<nullable>[<nested_type_fingerprint>];"
 //	Fields are sorted by field_id_or_name (lexicographically as strings)
 //
 // Field Components:
@@ -583,27 +586,80 @@ func ComputeStructFingerprint(fields []FieldFingerprintInfo) string {
 		// Field identifier
 		sb.WriteString(fw.sortKey)
 		sb.WriteString(",")
-		// Type ID
-		sb.WriteString(fmt.Sprintf("%d", fw.field.TypeID))
-		sb.WriteString(",")
-		// Ref flag
-		if fw.field.Ref {
-			sb.WriteString("1")
-		} else {
-			sb.WriteString("0")
-		}
-		sb.WriteString(",")
-		// Nullable flag
-		if fw.field.Nullable {
-			sb.WriteString("1")
-		} else {
-			sb.WriteString("0")
-		}
+		appendFieldTypeFingerprint(&sb, fw.field)
 		sb.WriteString(";")
 	}
 	return sb.String()
 }
 
+func appendFieldTypeFingerprint(sb *strings.Builder, field FieldFingerprintInfo) {
+	if field.TypeSpec != nil {
+		appendTypeSpecFingerprint(sb, field.TypeSpec, true, true)
+		return
+	}
+	sb.WriteString(fmt.Sprintf("%d", field.TypeID))
+	sb.WriteString(",")
+	if field.Ref {
+		sb.WriteString("1")
+	} else {
+		sb.WriteString("0")
+	}
+	sb.WriteString(",")
+	if field.Nullable {
+		sb.WriteString("1")
+	} else {
+		sb.WriteString("0")
+	}
+}
+
+func appendTypeSpecFingerprint(sb *strings.Builder, spec *TypeSpec, includeRef bool, includeNullable bool) {
+	if spec == nil {
+		if includeNullable {
+			sb.WriteString(fmt.Sprintf("%d,0,1", UNKNOWN))
+		} else {
+			sb.WriteString(fmt.Sprintf("%d,0,0", UNKNOWN))
+		}
+		return
+	}
+	sb.WriteString(fmt.Sprintf("%d", fingerprintTypeID(spec.TypeId())))
+	sb.WriteString(",")
+	if includeRef && spec.TrackRef {
+		sb.WriteString("1")
+	} else {
+		sb.WriteString("0")
+	}
+	sb.WriteString(",")
+	if includeNullable && spec.Nullable {
+		sb.WriteString("1")
+	} else {
+		sb.WriteString("0")
+	}
+	switch spec.TypeId() {
+	case LIST, SET:
+		sb.WriteString("[")
+		appendTypeSpecFingerprint(sb, spec.Element, false, false)
+		sb.WriteString("]")
+	case MAP:
+		sb.WriteString("[")
+		appendTypeSpecFingerprint(sb, spec.Key, false, false)
+		sb.WriteString("|")
+		appendTypeSpecFingerprint(sb, spec.Value, false, false)
+		sb.WriteString("]")
+	}
+}
+
+func fingerprintTypeID(typeID TypeId) TypeId {
+	switch typeID {
+	case ENUM, NAMED_ENUM,
+		STRUCT, COMPATIBLE_STRUCT, NAMED_STRUCT, NAMED_COMPATIBLE_STRUCT,
+		EXT, NAMED_EXT,
+		UNION, TYPED_UNION, NAMED_UNION:
+		return UNKNOWN
+	default:
+		return typeID
+	}
+}
+
 // Field sorting helpers
 
 type triple struct {
diff --git a/go/fory/struct_init.go b/go/fory/struct_init.go
index 95116ef43..9c242339b 100644
--- a/go/fory/struct_init.go
+++ b/go/fory/struct_init.go
@@ -881,7 +881,16 @@ func (s *structSerializer) computeHash() int32 {
 		fields = append(fields, FieldFingerprintInfo{
 			FieldID:   tagID,
 			FieldName: SnakeCase(field.Meta.Name),
-			TypeID:    typeId,
+			TypeSpec: func() *TypeSpec {
+				if field.Meta.Spec == nil || field.Meta.Spec.Type == nil {
+					return nil
+				}
+				projected := field.Meta.Spec.Type.Clone()
+				projected.Nullable = nullable
+				projected.TrackRef = explicitRef
+				return projected
+			}(),
+			TypeID: typeId,
 			// Ref is based on explicit tag annotation only, NOT runtime ref_tracking config
 			// This allows fingerprint to be computed at compile time for C++/Rust
 			Ref:      explicitRef,
diff --git a/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java b/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
index 4f28af89f..83dd1bae6 100644
--- a/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
+++ b/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
@@ -84,7 +84,7 @@ public class FieldTypes {
   }
 
   /** Build field type from generics, nested generics will be extracted too. */
-  static FieldType buildFieldType(TypeResolver resolver, Field field) {
+  public static FieldType buildFieldType(TypeResolver resolver, Field field) {
     Preconditions.checkNotNull(field);
     GenericType genericType = resolver.buildGenericType(TypeRef.of(field.getAnnotatedType()));
     return buildFieldType(resolver, field, genericType);
@@ -299,6 +299,10 @@ public class FieldTypes {
       return nullable;
     }
 
+    public int getTypeId() {
+      return typeId;
+    }
+
     private int typeKind() {
       if (this instanceof RegisteredFieldType) {
         return KIND_REGISTERED;
diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/struct/Fingerprint.java b/java/fory-core/src/main/java/org/apache/fory/serializer/struct/Fingerprint.java
index a7220e313..748dd5ea5 100644
--- a/java/fory-core/src/main/java/org/apache/fory/serializer/struct/Fingerprint.java
+++ b/java/fory-core/src/main/java/org/apache/fory/serializer/struct/Fingerprint.java
@@ -36,6 +36,7 @@ import org.apache.fory.collection.UInt64List;
 import org.apache.fory.collection.UInt8List;
 import org.apache.fory.logging.Logger;
 import org.apache.fory.logging.LoggerFactory;
+import org.apache.fory.meta.FieldTypes;
 import org.apache.fory.reflect.ReflectionUtils;
 import org.apache.fory.resolver.TypeInfo;
 import org.apache.fory.resolver.TypeResolver;
@@ -52,7 +53,8 @@ public class Fingerprint {
    *
    * <p><b>Fingerprint Format:</b>
    *
-   * <p>Each field contributes: {@code <field_id_or_name>,<type_id>,<ref>,<nullable>;}
+   * <p>Each field contributes: {@code
+   * <field_id_or_name>,<type_id>,<ref>,<nullable>[<nested_type_fingerprint>];}
    *
    * <p>Fields are sorted by their identifier (field ID or name) lexicographically as strings.
    *
@@ -91,7 +93,12 @@ public class Fingerprint {
     List<String[]> fieldInfos = new ArrayList<>(descriptors.size());
     for (Descriptor descriptor : descriptors) {
       Class<?> rawType = descriptor.getTypeRef().getRawType();
-      int typeId = getTypeId(resolver, descriptor);
+      FieldTypes.FieldType fieldType =
+          descriptor.getField() == null
+              ? null
+              : FieldTypes.buildFieldType(resolver, descriptor.getField());
+      int typeId =
+          fieldType != null ? fingerprintTypeId(fieldType) : getTypeId(resolver, descriptor);
 
       // Get field identifier: tag ID if configured, otherwise snake_case name
       String fieldIdentifier;
@@ -128,10 +135,12 @@ public class Fingerprint {
         nullable = descriptor.isNullable() ? '1' : '0';
       }
 
-      fieldInfos.add(
-          new String[] {
-            fieldIdentifier, String.valueOf(typeId), String.valueOf(ref), String.valueOf(nullable)
-          });
+      StringBuilder fieldFingerprint =
+          new StringBuilder().append(typeId).append(',').append(ref).append(',').append(nullable);
+      if (fieldType != null) {
+        appendNestedFingerprint(fieldFingerprint, fieldType);
+      }
+      fieldInfos.add(new String[] {fieldIdentifier, fieldFingerprint.toString()});
     }
 
     // Sort by field identifier (lexicographically as strings)
@@ -140,15 +149,7 @@ public class Fingerprint {
     // Build fingerprint string
     StringBuilder builder = new StringBuilder();
     for (String[] info : fieldInfos) {
-      builder
-          .append(info[0])
-          .append(',')
-          .append(info[1])
-          .append(',')
-          .append(info[2])
-          .append(',')
-          .append(info[3])
-          .append(';');
+      builder.append(info[0]).append(',').append(info[1]).append(';');
     }
     String fingerprint = builder.toString();
     if (Utils.DEBUG_OUTPUT_ENABLED) {
@@ -158,6 +159,59 @@ public class Fingerprint {
     return fingerprint;
   }
 
+  private static void appendNestedFingerprint(
+      StringBuilder builder, FieldTypes.FieldType fieldType) {
+    if (fieldType instanceof FieldTypes.CollectionFieldType) {
+      FieldTypes.CollectionFieldType collectionFieldType =
+          (FieldTypes.CollectionFieldType) fieldType;
+      builder.append('[');
+      appendFieldTypeFingerprint(builder, collectionFieldType.getElementType(), false, false);
+      builder.append(']');
+      return;
+    }
+    if (fieldType instanceof FieldTypes.MapFieldType) {
+      FieldTypes.MapFieldType mapFieldType = (FieldTypes.MapFieldType) fieldType;
+      builder.append('[');
+      appendFieldTypeFingerprint(builder, mapFieldType.getKeyType(), false, false);
+      builder.append('|');
+      appendFieldTypeFingerprint(builder, mapFieldType.getValueType(), false, false);
+      builder.append(']');
+    }
+  }
+
+  private static void appendFieldTypeFingerprint(
+      StringBuilder builder,
+      FieldTypes.FieldType fieldType,
+      boolean includeRef,
+      boolean includeNullable) {
+    builder
+        .append(fingerprintTypeId(fieldType))
+        .append(',')
+        .append(includeRef && fieldType.trackingRef() ? '1' : '0')
+        .append(',')
+        .append(includeNullable && fieldType.nullable() ? '1' : '0');
+    appendNestedFingerprint(builder, fieldType);
+  }
+
+  private static int fingerprintTypeId(FieldTypes.FieldType fieldType) {
+    int typeId = fieldType.getTypeId();
+    if (typeId == Types.UNKNOWN) {
+      return Types.UNKNOWN;
+    }
+    if (Types.isUnionType(typeId)
+        || typeId == Types.ENUM
+        || typeId == Types.NAMED_ENUM
+        || typeId == Types.STRUCT
+        || typeId == Types.COMPATIBLE_STRUCT
+        || typeId == Types.NAMED_STRUCT
+        || typeId == Types.NAMED_COMPATIBLE_STRUCT
+        || typeId == Types.EXT
+        || typeId == Types.NAMED_EXT) {
+      return Types.UNKNOWN;
+    }
+    return typeId;
+  }
+
   private static int getTypeId(TypeResolver resolver, Descriptor descriptor) {
     Class<?> cls = descriptor.getTypeRef().getRawType();
     Integer primitiveListTypeId = getPrimitiveListTypeId(cls);
diff --git a/java/fory-core/src/test/java/org/apache/fory/xlang/DartXlangTest.java b/java/fory-core/src/test/java/org/apache/fory/xlang/DartXlangTest.java
index 07ab430eb..de36a39da 100644
--- a/java/fory-core/src/test/java/org/apache/fory/xlang/DartXlangTest.java
+++ b/java/fory-core/src/test/java/org/apache/fory/xlang/DartXlangTest.java
@@ -336,4 +336,16 @@ public class DartXlangTest extends XlangTestBase {
   public void testUnsignedSchemaCompatible(boolean enableCodegen) throws java.io.IOException {
     super.testUnsignedSchemaCompatible(enableCodegen);
   }
+
+  @Test(groups = "xlang", dataProvider = "enableCodegenParallel")
+  public void testNestedAnnotatedContainerSchemaConsistent(boolean enableCodegen)
+      throws java.io.IOException {
+    super.testNestedAnnotatedContainerSchemaConsistent(enableCodegen);
+  }
+
+  @Test(groups = "xlang", dataProvider = "enableCodegenParallel")
+  public void testNestedAnnotatedContainerCompatible(boolean enableCodegen)
+      throws java.io.IOException {
+    super.testNestedAnnotatedContainerCompatible(enableCodegen);
+  }
 }
diff --git a/javascript/packages/core/lib/meta/TypeMeta.ts b/javascript/packages/core/lib/meta/TypeMeta.ts
index b04d38aa5..ef0770b0b 100644
--- a/javascript/packages/core/lib/meta/TypeMeta.ts
+++ b/javascript/packages/core/lib/meta/TypeMeta.ts
@@ -236,29 +236,43 @@ export class TypeMeta {
   computeStructFingerprint(fields: FieldInfo[]) {
     let fieldInfos = [];
     for (const field of fields) {
-      let typeId = field.getTypeId();
-      if (TypeId.userDefinedType(typeId)) {
-        typeId = TypeId.UNKNOWN;
-      }
       let fieldIdentifier = "";
       if (field.hasFieldId()) {
         fieldIdentifier = `${field.getFieldId()}`;
       } else {
         fieldIdentifier = TypeMeta.toSnakeCase(field.getFieldName());
       }
-      const ref = field.trackingRef ? "1" : "0";
-      const nullable = field.nullable ? "1" : "0";
-      fieldInfos.push([fieldIdentifier, `${typeId}`, ref, nullable]);
+      fieldInfos.push([fieldIdentifier, this.computeFieldTypeFingerprint(field, true, true)]);
     }
     fieldInfos = fieldInfos.sort((a, b) => a[0].localeCompare(b[0]));
     let result = "";
     for (const fieldInfo of fieldInfos) {
-      result += [fieldInfo[0], fieldInfo[1], fieldInfo[2], fieldInfo[3]].join(",");
-      result += ";";
+      result += `${fieldInfo[0]},${fieldInfo[1]};`;
+    }
+    return result;
+  }
+
+  private computeFieldTypeFingerprint(field: InnerFieldInfo, includeRef: boolean, includeNullable: boolean) {
+    const ref = includeRef && field.trackingRef ? "1" : "0";
+    const nullable = includeNullable && field.nullable ? "1" : "0";
+    let result = `${this.fingerprintTypeId(field.typeId)},${ref},${nullable}`;
+    if (field.typeId === TypeId.LIST) {
+      result += `[${this.computeFieldTypeFingerprint(field.options!.inner!, false, false)}]`;
+    } else if (field.typeId === TypeId.SET) {
+      result += `[${this.computeFieldTypeFingerprint(field.options!.key!, false, false)}]`;
+    } else if (field.typeId === TypeId.MAP) {
+      result += `[${this.computeFieldTypeFingerprint(field.options!.key!, false, false)}|${this.computeFieldTypeFingerprint(field.options!.value!, false, false)}]`;
     }
     return result;
   }
 
+  private fingerprintTypeId(typeId: number) {
+    if (TypeId.userDefinedType(typeId) || typeId === TypeId.UNION || typeId === TypeId.TYPED_UNION || typeId === TypeId.NAMED_UNION) {
+      return TypeId.UNKNOWN;
+    }
+    return typeId;
+  }
+
   computeStructHash() {
     const fields = TypeMeta.groupFieldsByType(this.fields);
     const fingerprint = this.computeStructFingerprint(fields);
diff --git a/python/pyfory/struct.py b/python/pyfory/struct.py
index ad4492e82..857a2e3a2 100644
--- a/python/pyfory/struct.py
+++ b/python/pyfory/struct.py
@@ -57,11 +57,14 @@ from pyfory.types import (
 )
 from pyfory.type_util import (
     TypeVisitor,
+    _get_args,
+    _get_origin,
     infer_field,
     get_homogeneous_tuple_elem_type,
     is_subclass,
     get_type_hints,
     unwrap_optional,
+    unwrap_ref,
 )
 from pyfory.serialization import Buffer
 from pyfory.serialization import ENABLE_FORY_CYTHON_SERIALIZATION
@@ -825,7 +828,7 @@ def compute_struct_fingerprint(type_resolver, field_names, serializers, nullable
     Computes the fingerprint string for a struct type used in schema versioning.
 
     Fingerprint Format:
-        Each field contributes: <field_id_or_name>,<type_id>,<ref>,<nullable>;
+        Each field contributes: <field_id_or_name>,<type_id>,<ref>,<nullable>[<child...>];
         Fields are sorted by tag ID (if >=0) or field name (if id=-1).
 
     Field Components:
@@ -844,7 +847,7 @@ def compute_struct_fingerprint(type_resolver, field_names, serializers, nullable
     if nullable_map is None:
         nullable_map = {}
 
-    # Build field info list for fingerprint: (sort_key, field_id_or_name, type_id, ref_flag, nullable_flag)
+    # Build field info list for fingerprint: (sort_key, field_id_or_name, type_fingerprint)
     fp_fields = []
 
     # Build a lookup for field_infos by name if available
@@ -858,29 +861,16 @@ def compute_struct_fingerprint(type_resolver, field_names, serializers, nullable
         # Get field metadata if available
         fi = field_info_map.get(field_name)
         tag_id = fi.tag_id if fi else -1
-        ref_flag = "1" if (fi and fi.ref) else "0"
-
-        if serializer is None:
-            type_id = TypeId.UNKNOWN
-            # For unknown serializers, use nullable from map (defaults to False for xlang)
-            nullable_flag = "1" if nullable_map.get(field_name, False) else "0"
-        else:
-            type_id = type_resolver.get_type_info(serializer.type_).type_id
-            if is_union_type(type_id):
-                # customized types can't be detected at compile time for some languages
-                type_id = TypeId.UNKNOWN
-            is_nullable = nullable_map.get(field_name, False)
-
-            # For polymorphic or enum types, set type_id to UNKNOWN but preserve nullable from map
-            if is_polymorphic_type(type_id) or type_id in {
-                TypeId.ENUM,
-                TypeId.NAMED_ENUM,
-            }:
-                type_id = TypeId.UNKNOWN
-
-            # Use nullable from map - for xlang, this is already computed correctly
-            # (False by default except for Optional[T] or explicit annotation)
-            nullable_flag = "1" if is_nullable else "0"
+        nullable = fi.nullable if fi else nullable_map.get(field_name, False)
+        ref = fi.ref if fi else False
+        type_hint = fi.type_hint if fi else (serializer.type_ if serializer is not None else typing.Any)
+        type_fingerprint = _build_schema_fingerprint_type(
+            type_resolver,
+            type_hint,
+            nullable=nullable,
+            track_ref=ref,
+            include_ref=True,
+        )
 
         # Determine field identifier for fingerprint
         if tag_id >= 0:
@@ -892,19 +882,132 @@ def compute_struct_fingerprint(type_resolver, field_names, serializers, nullable
             # Sort by field name (lexicographic) for name-based fields
             sort_key = (1, field_name, "")  # 1 = name fields come after
 
-        fp_fields.append((sort_key, field_id_or_name, type_id, ref_flag, nullable_flag))
+        fp_fields.append((sort_key, field_id_or_name, type_fingerprint))
 
     # Sort fields: tag ID fields first (by ID), then name fields (lexicographically)
     fp_fields.sort(key=lambda x: x[0])
 
     # Build fingerprint string
     hash_parts = []
-    for _, field_id_or_name, type_id, ref_flag, nullable_flag in fp_fields:
-        hash_parts.append(f"{field_id_or_name},{type_id},{ref_flag},{nullable_flag};")
+    for _, field_id_or_name, type_fingerprint in fp_fields:
+        hash_parts.append(f"{field_id_or_name},{type_fingerprint};")
 
     return "".join(hash_parts)
 
 
+def _normalize_schema_fingerprint_type_id(type_id):
+    if type_id in {
+        TypeId.UNKNOWN,
+        TypeId.ENUM,
+        TypeId.NAMED_ENUM,
+        TypeId.STRUCT,
+        TypeId.COMPATIBLE_STRUCT,
+        TypeId.NAMED_STRUCT,
+        TypeId.NAMED_COMPATIBLE_STRUCT,
+        TypeId.EXT,
+        TypeId.NAMED_EXT,
+        TypeId.UNION,
+        TypeId.TYPED_UNION,
+        TypeId.NAMED_UNION,
+    }:
+        return TypeId.UNKNOWN
+    return type_id
+
+
+def _leaf_schema_fingerprint_type_id(type_resolver, type_hint):
+    if type_hint is typing.Any or type_hint is object:
+        return TypeId.UNKNOWN
+    if is_primitive_array_type(type_hint):
+        return type_resolver.get_type_info(type_hint).type_id
+    if is_subclass(type_hint, enum.Enum):
+        return _normalize_schema_fingerprint_type_id(type_resolver.get_type_info(type_hint).type_id)
+    if type_hint in basic_types:
+        return _normalize_schema_fingerprint_type_id(type_resolver.get_type_info(type_hint).type_id)
+    typeinfo = type_resolver.get_type_info(type_hint, create=False)
+    if typeinfo is None:
+        return TypeId.UNKNOWN
+    return _normalize_schema_fingerprint_type_id(typeinfo.type_id)
+
+
+def _build_schema_fingerprint_type(type_resolver, type_hint, nullable, track_ref, include_ref, include_nullable=True):
+    type_hint, ref_override = unwrap_ref(type_hint)
+    if include_ref and ref_override is not None:
+        track_ref = ref_override
+    if not include_ref:
+        track_ref = False
+
+    unwrapped_type, is_optional = unwrap_optional(type_hint)
+    nullable = nullable or is_optional
+    origin = _get_origin(unwrapped_type) or getattr(unwrapped_type, "__origin__", unwrapped_type)
+    args = _get_args(unwrapped_type)
+
+    ref_flag = "1" if track_ref else "0"
+    nullable_flag = "1" if include_nullable and nullable else "0"
+
+    if args:
+        if origin is list or origin == typing.List:
+            elem_type = args[0]
+            child = _build_schema_fingerprint_type(
+                type_resolver,
+                elem_type,
+                nullable=True,
+                track_ref=False,
+                include_ref=False,
+                include_nullable=False,
+            )
+            return f"{TypeId.LIST},{ref_flag},{nullable_flag}[{child}]"
+        if origin is set or origin == typing.Set:
+            elem_type = args[0]
+            child = _build_schema_fingerprint_type(
+                type_resolver,
+                elem_type,
+                nullable=True,
+                track_ref=False,
+                include_ref=False,
+                include_nullable=False,
+            )
+            return f"{TypeId.SET},{ref_flag},{nullable_flag}[{child}]"
+        if origin is tuple or origin == typing.Tuple:
+            elem_type = get_homogeneous_tuple_elem_type(args)
+            if elem_type is None:
+                child = f"{TypeId.UNKNOWN},0,0"
+            else:
+                child = _build_schema_fingerprint_type(
+                    type_resolver,
+                    elem_type,
+                    nullable=True,
+                    track_ref=False,
+                    include_ref=False,
+                    include_nullable=False,
+                )
+            return f"{TypeId.LIST},{ref_flag},{nullable_flag}[{child}]"
+        if origin is dict or origin == typing.Dict:
+            key_type, value_type = args
+            key = _build_schema_fingerprint_type(
+                type_resolver,
+                key_type,
+                nullable=True,
+                track_ref=False,
+                include_ref=False,
+                include_nullable=False,
+            )
+            value = _build_schema_fingerprint_type(
+                type_resolver,
+                value_type,
+                nullable=True,
+                track_ref=False,
+                include_ref=False,
+                include_nullable=False,
+            )
+            return f"{TypeId.MAP},{ref_flag},{nullable_flag}[{key}|{value}]"
+        if origin is typing.Union:
+            type_id = TypeId.UNKNOWN
+            return f"{type_id},{ref_flag},{nullable_flag}"
+
+    type_id = _leaf_schema_fingerprint_type_id(type_resolver, unwrapped_type)
+    return f"{type_id},{ref_flag},{nullable_flag}"
+
+
 def compute_struct_meta(type_resolver, field_names, serializers, nullable_map=None, field_infos_list=None):
     """
     Computes struct metadata including version hash, sorted field names, and serializers.
diff --git a/python/pyfory/tests/test_cross_language.py b/python/pyfory/tests/test_cross_language.py
index e608b80eb..e70fad630 100644
--- a/python/pyfory/tests/test_cross_language.py
+++ b/python/pyfory/tests/test_cross_language.py
@@ -519,6 +519,7 @@ def test_struct_hash(data_file_path):
         serializer._field_names,
         serializer._serializers,
         serializer._nullable_fields,
+        serializer._field_infos,
     )[0]
     assert read_hash == v, (read_hash, v)
 
diff --git a/rust/fory-derive/src/object/util.rs b/rust/fory-derive/src/object/util.rs
index 7cdabb2e4..1539c7602 100644
--- a/rust/fory-derive/src/object/util.rs
+++ b/rust/fory-derive/src/object/util.rs
@@ -585,16 +585,10 @@ pub(super) fn get_sort_fields_ts(fields: &[&Field]) -> TokenStream {
 
 /// Field metadata for fingerprint computation.
 struct FieldFingerprintInfo {
-    /// Field name (snake_case) or field ID as string
+    /// Field name (snake_case) or field ID as string.
     name_or_id: String,
-    /// Whether the field has explicit nullable=true/false set via #[fory(nullable)]
-    explicit_nullable: Option<bool>,
-    /// Whether reference tracking is enabled
-    track_ref: bool,
-    /// The type ID (UNKNOWN for user-defined types including enums/unions)
-    type_id: u32,
-    /// Whether the field type is `Option<T>`
-    is_option_type: bool,
+    /// Recursive field type fingerprint.
+    type_fingerprint: String,
 }
 
 /// Adjusts integer type IDs based on semantic `encoding` attributes.
@@ -616,12 +610,152 @@ fn adjust_type_id_for_encoding(base_type_id: u32, meta: &super::field_meta::Fory
     }
 }
 
+fn fingerprint_type_id(type_id: u32) -> u32 {
+    if type_id == TypeId::UNKNOWN as u32
+        || type_id == TypeId::UNION as u32
+        || type_id == TypeId::TYPED_UNION as u32
+        || type_id == TypeId::NAMED_UNION as u32
+    {
+        TypeId::UNKNOWN as u32
+    } else {
+        type_id
+    }
+}
+
+fn type_name_and_args(
+    ty: &Type,
+) -> Option<(
+    String,
+    Option<&syn::punctuated::Punctuated<GenericArgument, syn::token::Comma>>,
+)> {
+    let Type::Path(type_path) = ty else {
+        return None;
+    };
+    let seg = type_path.path.segments.last()?;
+    let PathArguments::AngleBracketed(args) = &seg.arguments else {
+        return Some((seg.ident.to_string(), None));
+    };
+    Some((seg.ident.to_string(), Some(&args.args)))
+}
+
+fn single_type_arg(
+    args: &syn::punctuated::Punctuated<GenericArgument, syn::token::Comma>,
+) -> Option<&Type> {
+    args.iter().find_map(|arg| match arg {
+        GenericArgument::Type(ty) => Some(ty),
+        _ => None,
+    })
+}
+
+fn two_type_args(
+    args: &syn::punctuated::Punctuated<GenericArgument, syn::token::Comma>,
+) -> Option<(&Type, &Type)> {
+    let mut iter = args.iter().filter_map(|arg| match arg {
+        GenericArgument::Type(ty) => Some(ty),
+        _ => None,
+    });
+    Some((iter.next()?, iter.next()?))
+}
+
+fn build_type_fingerprint(
+    ty: &Type,
+    meta: &super::field_meta::ForyFieldMeta,
+    include_ref: bool,
+    include_nullable: bool,
+) -> String {
+    use super::field_meta::{classify_field_type, extract_option_inner_type, is_option_type};
+
+    let type_class = classify_field_type(ty);
+    let nullable = meta.effective_nullable(type_class) || is_option_type(ty);
+    let track_ref = include_ref && meta.effective_ref(type_class);
+    let type_id = fingerprint_type_id(adjust_type_id_for_encoding(
+        get_type_id_by_type_ast(ty),
+        meta,
+    ));
+
+    let mut fingerprint = format!(
+        "{},{},{}",
+        type_id,
+        if track_ref { 1 } else { 0 },
+        if include_nullable && nullable { 1 } else { 0 }
+    );
+
+    let container_ty = extract_option_inner_type(ty).unwrap_or_else(|| ty.clone());
+
+    if let Type::Array(array) = &container_ty {
+        if type_id == TypeId::LIST as u32 {
+            fingerprint.push('[');
+            fingerprint.push_str(&build_type_fingerprint(
+                array.elem.as_ref(),
+                &meta.element_meta(),
+                false,
+                false,
+            ));
+            fingerprint.push(']');
+        }
+        return fingerprint;
+    }
+
+    let Some((name, args)) = type_name_and_args(&container_ty) else {
+        return fingerprint;
+    };
+
+    match name.as_str() {
+        "Vec" | "VecDeque" | "LinkedList" if type_id == TypeId::LIST as u32 => {
+            if let Some(elem_ty) = args.and_then(single_type_arg) {
+                fingerprint.push('[');
+                fingerprint.push_str(&build_type_fingerprint(
+                    elem_ty,
+                    &meta.element_meta(),
+                    false,
+                    false,
+                ));
+                fingerprint.push(']');
+            }
+        }
+        "HashSet" | "BTreeSet" | "BinaryHeap" if type_id == TypeId::SET as u32 => {
+            if let Some(elem_ty) = args.and_then(single_type_arg) {
+                fingerprint.push('[');
+                fingerprint.push_str(&build_type_fingerprint(
+                    elem_ty,
+                    &meta.element_meta(),
+                    false,
+                    false,
+                ));
+                fingerprint.push(']');
+            }
+        }
+        "HashMap" | "BTreeMap" if type_id == TypeId::MAP as u32 => {
+            if let Some((key_ty, value_ty)) = args.and_then(two_type_args) {
+                fingerprint.push('[');
+                fingerprint.push_str(&build_type_fingerprint(
+                    key_ty,
+                    &meta.map_key_meta(),
+                    false,
+                    false,
+                ));
+                fingerprint.push('|');
+                fingerprint.push_str(&build_type_fingerprint(
+                    value_ty,
+                    &meta.map_value_meta(),
+                    false,
+                    false,
+                ));
+                fingerprint.push(']');
+            }
+        }
+        _ => {}
+    }
+
+    fingerprint
+}
+
 /// Computes struct fingerprint string at compile time (during proc-macro execution).
 ///
-/// **Fingerprint Format:** `<field_name_or_id>,<type_id>,<ref>,<nullable>;`
+/// **Fingerprint Format:** `<field_name_or_id>,<type_id>,<ref>,<nullable>[<child...>];`
 /// Fields are sorted by name lexicographically.
 fn compute_struct_fingerprint(fields: &[&Field]) -> String {
-    use super::field_meta::{classify_field_type, parse_field_meta};
+    use super::field_meta::parse_field_meta;
 
     let mut field_infos: Vec<FieldFingerprintInfo> = Vec::with_capacity(fields.len());
 
@@ -639,30 +773,9 @@ fn compute_struct_fingerprint(fields: &[&Field]) -> String {
             to_snake_case(&name)
         };
 
-        let type_class = classify_field_type(&field.ty);
-        let track_ref = meta.effective_ref(type_class);
-        let explicit_nullable = meta.nullable;
-
-        // Get compile-time TypeId, considering encoding attributes for u32/u64 fields
-        let base_type_id = get_type_id_by_type_ast(&field.ty);
-        let type_id = adjust_type_id_for_encoding(base_type_id, &meta);
-
-        // Check if field type is Option<T>
-        let ty_str: String = field
-            .ty
-            .to_token_stream()
-            .to_string()
-            .chars()
-            .filter(|c| !c.is_whitespace())
-            .collect();
-        let is_option_type = ty_str.starts_with("Option<");
-
         field_infos.push(FieldFingerprintInfo {
             name_or_id,
-            explicit_nullable,
-            track_ref,
-            type_id,
-            is_option_type,
+            type_fingerprint: build_type_fingerprint(&field.ty, &meta, true, true),
         });
     }
 
@@ -672,32 +785,9 @@ fn compute_struct_fingerprint(fields: &[&Field]) -> String {
     // Build fingerprint string
     let mut fingerprint = String::new();
     for info in &field_infos {
-        let ref_flag = if info.track_ref { "1" } else { "0" };
-        let nullable = match info.explicit_nullable {
-            Some(true) => true,
-            Some(false) => false,
-            None => info.is_option_type,
-        };
-        let nullable_flag = if nullable { "1" } else { "0" };
-
-        // User-defined types (UNKNOWN) and unions use 0 in fingerprint, matching Java behavior
-        let effective_type_id = if info.type_id == TypeId::UNKNOWN as u32
-            || info.type_id == TypeId::UNION as u32
-            || info.type_id == TypeId::TYPED_UNION as u32
-            || info.type_id == TypeId::NAMED_UNION as u32
-        {
-            0
-        } else {
-            info.type_id
-        };
-
         fingerprint.push_str(&info.name_or_id);
         fingerprint.push(',');
-        fingerprint.push_str(&effective_type_id.to_string());
-        fingerprint.push(',');
-        fingerprint.push_str(ref_flag);
-        fingerprint.push(',');
-        fingerprint.push_str(nullable_flag);
+        fingerprint.push_str(&info.type_fingerprint);
         fingerprint.push(';');
     }
 
diff --git a/swift/Sources/Fory/MacroDeclarations.swift b/swift/Sources/Fory/MacroDeclarations.swift
index 60995726e..e1c34b9bd 100644
--- a/swift/Sources/Fory/MacroDeclarations.swift
+++ b/swift/Sources/Fory/MacroDeclarations.swift
@@ -20,6 +20,7 @@
     names: named(staticTypeId),
     named(foryEvolving),
     named(isRefType),
+    named(__foryNormalizeSchemaFingerprintTypeID),
     named(__forySchemaHash),
     named(__forySchemaHashTrackRefDisabled),
     named(__forySchemaHashTrackRefEnabled),
@@ -43,6 +44,7 @@ public macro ForyStruct(evolving: Bool = true) = #externalMacro(module: "ForyMac
     names: named(staticTypeId),
     named(foryEvolving),
     named(isRefType),
+    named(__foryNormalizeSchemaFingerprintTypeID),
     named(__forySchemaHash),
     named(__forySchemaHashTrackRefDisabled),
     named(__forySchemaHashTrackRefEnabled),
@@ -66,6 +68,7 @@ public macro ForyEnum() = #externalMacro(module: "ForyMacro", type: "ForyEnumMac
     names: named(staticTypeId),
     named(foryEvolving),
     named(isRefType),
+    named(__foryNormalizeSchemaFingerprintTypeID),
     named(__forySchemaHash),
     named(__forySchemaHashTrackRefDisabled),
     named(__forySchemaHashTrackRefEnabled),
diff --git a/swift/Sources/ForyMacro/ForyObjectMacro.swift b/swift/Sources/ForyMacro/ForyObjectMacro.swift
index b07ae61a8..1e68ae3b8 100644
--- a/swift/Sources/ForyMacro/ForyObjectMacro.swift
+++ b/swift/Sources/ForyMacro/ForyObjectMacro.swift
@@ -64,7 +64,7 @@ public struct ForyStructMacro: MemberMacro, ExtensionMacro {
         \(raw: accessPrefix)static var isRefType: Bool { true }
         """ : nil
 
-        let schemaHashDecl: DeclSyntax = DeclSyntax(stringLiteral: buildSchemaHashDecl(fields: parsed.fields))
+        let schemaHashDecl: DeclSyntax = DeclSyntax(stringLiteral: try buildSchemaHashDecl(fields: parsed.fields))
         let compatibleTypeMetaDecl: DeclSyntax = DeclSyntax(
             stringLiteral: buildCompatibleTypeMetaFieldsDecl(sortedFields: sortedFields, accessPrefix: accessPrefix)
         )
@@ -273,6 +273,7 @@ enum DynamicAnyCodecKind {
 struct ParsedField {
     let name: String
     let typeText: String
+    fileprivate let typeHint: FieldTypeHint?
     let originalIndex: Int
 
     let isOptional: Bool
@@ -735,6 +736,7 @@ private func parseFields(_ declaration: some DeclGroupSyntax) throws -> ParsedDe
                 ParsedField(
                     name: name,
                     typeText: rawType,
+                    typeHint: fieldTypeHint,
                     originalIndex: originalIndex,
                     isOptional: isOptional,
                     isCollection: classification.isCollection || classification.isMap,
@@ -1912,15 +1914,24 @@ private func sortFields(_ fields: [ParsedField]) -> [ParsedField] {
     }
 }
 
-private func buildSchemaHashDecl(fields: [ParsedField]) -> String {
-    let fingerprintTrackRefDisabled = buildSchemaFingerprint(fields: fields, trackRefExpression: "false")
-    let fingerprintTrackRefEnabled = buildSchemaFingerprint(fields: fields, trackRefExpression: "true")
+private func buildSchemaHashDecl(fields: [ParsedField]) throws -> String {
+    let fingerprintTrackRefDisabled = try buildSchemaFingerprint(fields: fields, trackRefExpression: "false")
+    let fingerprintTrackRefEnabled = try buildSchemaFingerprint(fields: fields, trackRefExpression: "true")
     return """
+    private static func __foryNormalizeSchemaFingerprintTypeID(_ typeID: UInt32) -> UInt32 {
+        switch typeID {
+        case 0, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35:
+            return 0
+        default:
+            return typeID
+        }
+    }
+
     private static let __forySchemaHashTrackRefDisabled: UInt32 = SchemaHash.structHash32(\(fingerprintTrackRefDisabled))
     private static let __forySchemaHashTrackRefEnabled: UInt32 = SchemaHash.structHash32(\(fingerprintTrackRefEnabled))
 
     private static func __forySchemaHash(_ trackRef: Bool) -> UInt32 {
-        trackRef ? Self.__forySchemaHashTrackRefEnabled : Self.__forySchemaHashTrackRefDisabled
+        trackRef ? __forySchemaHashTrackRefEnabled : __forySchemaHashTrackRefDisabled
     }
     """
 }
@@ -1933,7 +1944,7 @@ private func buildCompatibleTypeMetaFieldsDecl(sortedFields: [ParsedField], acce
     private static let __foryFieldsInfoTrackRefEnabled: [TypeMeta.FieldInfo] = \(enabledExpr)
 
     \(accessPrefix)static func foryFieldsInfo(trackRef: Bool) -> [TypeMeta.FieldInfo] {
-        trackRef ? Self.__foryFieldsInfoTrackRefEnabled : Self.__foryFieldsInfoTrackRefDisabled
+        trackRef ? __foryFieldsInfoTrackRefEnabled : __foryFieldsInfoTrackRefDisabled
     }
     """
 }
@@ -1959,48 +1970,193 @@ private func compatibleFieldIDExpr(_ field: ParsedField) -> String {
     return "nil"
 }
 
-private func buildSchemaFingerprint(fields: [ParsedField], trackRefExpression: String) -> String {
-    let entries = fields
+private func buildSchemaFingerprint(fields: [ParsedField], trackRefExpression: String) throws -> String {
+    let sortedFields = fields
         .sorted { lhs, rhs in
             if lhs.schemaIdentifier != rhs.schemaIdentifier {
                 return lhs.schemaIdentifier < rhs.schemaIdentifier
             }
             return lhs.originalIndex < rhs.originalIndex
         }
-        .map { field -> String in
-            let typeID = fingerprintTypeID(for: field)
-            let nullable = field.isOptional ? "1" : "0"
-            let trackRefExpr: String
-            if let dynamicAnyCodec = field.dynamicAnyCodec {
-                switch dynamicAnyCodec {
-                case .anyValue:
-                    trackRefExpr = "(\(trackRefExpression)) ? 1 : 0"
-                case .anyHashableValue, .anyList, .stringAnyMap, .int32AnyMap, .anyHashableAnyMap:
-                    trackRefExpr = "0"
-                }
-            } else if let customCodecType = field.customCodecType {
-                trackRefExpr = "((\(trackRefExpression)) && \(customCodecType).isRefType) ? 1 : 0"
-            } else {
-                trackRefExpr = "((\(trackRefExpression)) && \(field.typeText).isRefType) ? 1 : 0"
-            }
-            return "\"\(field.schemaIdentifier),\(typeID),\\(\(trackRefExpr)),\(nullable);\""
-        }
+    let entries = try sortedFields.map { field -> String in
+        let typeFingerprint = try buildSchemaFieldTypeFingerprint(field, trackRefExpression: trackRefExpression)
+        return "\"\(field.schemaIdentifier),\" + \(typeFingerprint) + \";\""
+    }
     if entries.isEmpty {
         return "\"\""
     }
     return entries.joined(separator: " + ")
 }
 
-private func fingerprintTypeID(for field: ParsedField) -> UInt32 {
-    if field.customCodecType != nil {
-        return field.typeID
+private func buildSchemaFieldTypeFingerprint(
+    _ field: ParsedField,
+    trackRefExpression: String
+) throws -> String {
+    let fieldTrackRefExpression: String
+    if let dynamicAnyCodec = field.dynamicAnyCodec {
+        fieldTrackRefExpression = dynamicAnyUsesContextTrackRef(dynamicAnyCodec) ? trackRefExpression : "false"
+    } else if let customCodecType = field.customCodecType {
+        fieldTrackRefExpression = "\(trackRefExpression) && \(customCodecType).isRefType"
+    } else {
+        fieldTrackRefExpression = "\(trackRefExpression) && \(field.typeText).isRefType"
     }
-    let optional = unwrapOptional(field.typeText)
-    let classification = classifyType(optional.type)
-    if classification.isPrimitive || classification.isBuiltIn {
-        return field.typeID
+
+    return try buildSchemaTypeFingerprint(
+        typeText: field.typeText,
+        hint: field.typeHint,
+        nullableExpression: field.isOptional ? "true" : "false",
+        trackRefExpression: fieldTrackRefExpression,
+        explicitTypeIDExpression: field.customCodecType != nil ? "\(field.typeID)" : nil
+    )
+}
+
+private func buildSchemaTypeFingerprint(
+    typeText: String,
+    hint: FieldTypeHint?,
+    nullableExpression: String,
+    trackRefExpression: String,
+    includeNullable: Bool = true,
+    explicitTypeIDExpression: String? = nil
+) throws -> String {
+    let normalized = trimType(typeText)
+    let optional = unwrapOptional(normalized)
+    let concreteType = optional.type
+    let outerClassification = classifyType(concreteType)
+    let nullableLiteral = includeNullable && nullableExpression == "true" ? "1" : "0"
+    let trackFlagExpr = trackRefExpression == "false" ? "0" : "((\(trackRefExpression)) ? 1 : 0)"
+
+    if let hint {
+        switch hint {
+        case .list(let elementHint):
+            let listClassification = try classification(for: concreteType, hint: hint)
+            if listClassification.typeID != 22 {
+                let typeIDExpr = explicitTypeIDExpression ?? "\(listClassification.typeID)"
+                return "\"\\(__foryNormalizeSchemaFingerprintTypeID(\(typeIDExpr))),\\(\(trackFlagExpr)),\(nullableLiteral)\""
+            }
+            let elementType = parseArrayElement(concreteType) ?? hintedValueTypeName(elementHint)
+            guard let elementType else {
+                throw MacroExpansionErrorMessage(
+                    "list field hint requires an Array/List Swift type or a full element type hint"
+                )
+            }
+            let elementNullable = compatibleGenericNullableExpression(elementType)
+            let elementExpr = try buildSchemaTypeFingerprint(
+                typeText: elementType,
+                hint: elementHint,
+                nullableExpression: elementNullable,
+                trackRefExpression: "false",
+                includeNullable: false
+            )
+            let prefix = "\"\\(__foryNormalizeSchemaFingerprintTypeID(UInt32(TypeId.list.rawValue))),\\(\(trackFlagExpr)),\(nullableLiteral)\""
+            return "\(prefix) + \"[\" + \(elementExpr) + \"]\""
+        case .set(let elementHint):
+            let elementType = parseSetElement(concreteType) ?? hintedValueTypeName(elementHint)
+            guard let elementType else {
+                throw MacroExpansionErrorMessage(
+                    "set field hint requires a Set Swift type or a full element type hint"
+                )
+            }
+            let elementNullable = compatibleGenericNullableExpression(elementType)
+            let elementExpr = try buildSchemaTypeFingerprint(
+                typeText: elementType,
+                hint: elementHint,
+                nullableExpression: elementNullable,
+                trackRefExpression: "false",
+                includeNullable: false
+            )
+            let prefix = "\"\\(__foryNormalizeSchemaFingerprintTypeID(UInt32(TypeId.set.rawValue))),\\(\(trackFlagExpr)),\(nullableLiteral)\""
+            return "\(prefix) + \"[\" + \(elementExpr) + \"]\""
+        case .map(let keyHint, let valueHint):
+            let parsedMap = parseDictionary(concreteType)
+            let keyType = parsedMap?.0 ?? keyHint.flatMap(hintedValueTypeName)
+            let valueType = parsedMap?.1 ?? valueHint.flatMap(hintedValueTypeName)
+            guard let keyType, let valueType else {
+                throw MacroExpansionErrorMessage(
+                    "map field hint requires a Dictionary/Map Swift type or full key/value hints"
+                )
+            }
+            let keyNullable = compatibleGenericNullableExpression(keyType)
+            let valueNullable = compatibleGenericNullableExpression(valueType)
+            let keyExpr = try buildSchemaTypeFingerprint(
+                typeText: keyType,
+                hint: keyHint,
+                nullableExpression: keyNullable,
+                trackRefExpression: "false",
+                includeNullable: false
+            )
+            let valueExpr = try buildSchemaTypeFingerprint(
+                typeText: valueType,
+                hint: valueHint,
+                nullableExpression: valueNullable,
+                trackRefExpression: "false",
+                includeNullable: false
+            )
+            let prefix = "\"\\(__foryNormalizeSchemaFingerprintTypeID(UInt32(TypeId.map.rawValue))),\\(\(trackFlagExpr)),\(nullableLiteral)\""
+            return "\(prefix) + \"[\" + \(keyExpr) + \"|\" + \(valueExpr) + \"]\""
+        case .inferredEncoding, .scalar:
+            break
+        }
+    }
+
+    if outerClassification.typeID == 22, let elementType = parseArrayElement(concreteType) {
+        let elementNullable = compatibleGenericNullableExpression(elementType)
+        let elementExpr = try buildSchemaTypeFingerprint(
+            typeText: elementType,
+            hint: nil,
+            nullableExpression: elementNullable,
+            trackRefExpression: "false",
+            includeNullable: false
+        )
+        let prefix = "\"\\(__foryNormalizeSchemaFingerprintTypeID(UInt32(TypeId.list.rawValue))),\\(\(trackFlagExpr)),\(nullableLiteral)\""
+        return "\(prefix) + \"[\" + \(elementExpr) + \"]\""
+    }
+
+    if outerClassification.typeID == 23, let elementType = parseSetElement(concreteType) {
+        let elementNullable = compatibleGenericNullableExpression(elementType)
+        let elementExpr = try buildSchemaTypeFingerprint(
+            typeText: elementType,
+            hint: nil,
+            nullableExpression: elementNullable,
+            trackRefExpression: "false",
+            includeNullable: false
+        )
+        let prefix = "\"\\(__foryNormalizeSchemaFingerprintTypeID(UInt32(TypeId.set.rawValue))),\\(\(trackFlagExpr)),\(nullableLiteral)\""
+        return "\(prefix) + \"[\" + \(elementExpr) + \"]\""
     }
-    return 0
+
+    if outerClassification.typeID == 24, let (keyType, valueType) = parseDictionary(concreteType) {
+        let keyNullable = compatibleGenericNullableExpression(keyType)
+        let valueNullable = compatibleGenericNullableExpression(valueType)
+        let keyExpr = try buildSchemaTypeFingerprint(
+            typeText: keyType,
+            hint: nil,
+            nullableExpression: keyNullable,
+            trackRefExpression: "false",
+            includeNullable: false
+        )
+        let valueExpr = try buildSchemaTypeFingerprint(
+            typeText: valueType,
+            hint: nil,
+            nullableExpression: valueNullable,
+            trackRefExpression: "false",
+            includeNullable: false
+        )
+        let prefix = "\"\\(__foryNormalizeSchemaFingerprintTypeID(UInt32(TypeId.map.rawValue))),\\(\(trackFlagExpr)),\(nullableLiteral)\""
+        return "\(prefix) + \"[\" + \(keyExpr) + \"|\" + \(valueExpr) + \"]\""
+    }
+
+    let typeIDExpr: String
+    if let explicitTypeIDExpression {
+        typeIDExpr = explicitTypeIDExpression
+    } else if let hint {
+        typeIDExpr = "\(try classification(for: concreteType, hint: hint).typeID)"
+    } else if isDynamicAnyConcreteType(concreteType) {
+        typeIDExpr = "UInt32(TypeId.unknown.rawValue)"
+    } else {
+        typeIDExpr = compatibleFieldTypeIDExpression(concreteType)
+    }
+
+    return "\"\\(__foryNormalizeSchemaFingerprintTypeID(\(typeIDExpr))),\\(\(trackFlagExpr)),\(nullableLiteral)\""
 }
 
 private func buildDefaultDecl(isClass: Bool, fields: [ParsedField], accessPrefix: String) -> String {
diff --git a/swift/Tests/ForyTests/CompatibilityTests.swift b/swift/Tests/ForyTests/CompatibilityTests.swift
index 91a11d98e..6633e0bf7 100644
--- a/swift/Tests/ForyTests/CompatibilityTests.swift
+++ b/swift/Tests/ForyTests/CompatibilityTests.swift
@@ -101,6 +101,12 @@ private struct RemoteNestedFixedMapV1: Equatable {
     var ids: Set<Int32?> = []
 }
 
+@ForyStruct
+private struct SchemaHashNestedAnnotatedContainer: Equatable {
+    @MapField(key: .uint32(encoding: .fixed), value: .list(element: .uint64(encoding: .tagged)))
+    var values: [UInt32?: [UInt64?]?] = [:]
+}
+
 @ForyStruct
 private struct LocalNestedVarintMapV2: Equatable {
     @ForyField(id: 1)
@@ -243,6 +249,31 @@ func schemaHashMatchesJavaFingerprintForTaggedUnsignedFields() {
     )
 }
 
+@Test
+func schemaHashUsesNestedAnnotatedContainerTypeIDs() throws {
+    let fory = Fory(config: .init(xlang: true, trackRef: false, compatible: false, checkClassVersion: true))
+    fory.register(SchemaHashNestedAnnotatedContainer.self, id: 9912)
+
+    let bytes = try fory.serialize(
+        SchemaHashNestedAnnotatedContainer(
+            values: [
+                UInt32(7): [UInt64(11), nil]
+            ]
+        )
+    )
+    let buffer = ByteBuffer(data: bytes)
+    _ = try fory.readHead(buffer: buffer)
+    _ = try buffer.readInt8()
+    _ = try buffer.readVarUInt32()
+    _ = try buffer.readVarUInt32()
+    let schemaHash = UInt32(bitPattern: try buffer.readInt32())
+
+    #expect(
+        schemaHash ==
+            SchemaHash.structHash32("values,24,0,0[11,0,0|22,0,0[15,0,0]];")
+    )
+}
+
 @Test
 func compatibleNestedArrayEvolves() throws {
     let writerV1 = Fory(config: .init(xlang: true, trackRef: false, compatible: true))


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org