title: Field Configuration sidebar_position: 5 id: field_configuration license: | 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
This page explains how to configure field-level metadata for serialization in Java.
Apache Fory™ provides field-level configuration through annotations:
@ForyField: Configure field metadata (id, nullable, ref, dynamic)@Ignore: Exclude fields from serializationThis enables:
Use annotations on fields:
import org.apache.fory.annotation.ForyField; public class Person { @ForyField(id = 0) private String name; @ForyField(id = 1) private int age; @ForyField(id = 2, nullable = true) private String nickname; }
@ForyField AnnotationUse @ForyField to configure field-level metadata:
public class User { @ForyField(id = 0) private long id; @ForyField(id = 1) private String name; @ForyField(id = 2, nullable = true) private String email; @ForyField(id = 3, ref = true) private List<User> friends; @ForyField(id = 4, dynamic = ForyField.Dynamic.TRUE) private Object data; }
| Parameter | Type | Default | Description |
|---|---|---|---|
id | int | -1 | Field tag ID (-1 = use field name) |
nullable | boolean | false | Whether the field can be null |
ref | boolean | false | Enable reference tracking |
dynamic | Dynamic | AUTO | Control polymorphism for struct fields |
id)Assigns a numeric ID to a field to minimize struct field meta size overhead for compatible mode:
public class User { @ForyField(id = 0) private long id; @ForyField(id = 1) private String name; @ForyField(id = 2) private int age; }
Benefits:
Recommendation: It is recommended to configure field IDs for compatible mode since it reduces serialization cost.
Notes:
Without field IDs (field names used in metadata):
public class User { private long id; private String name; }
nullable)Use nullable = true for fields that can be null:
public class Record { // Nullable string field @ForyField(id = 0, nullable = true) private String optionalName; // Nullable Integer field (boxed type) @ForyField(id = 1, nullable = true) private Integer optionalCount; // Non-nullable field (default) @ForyField(id = 2) private String requiredName; }
Notes:
nullable = false (non-nullable)nullable = false, Fory skips writing the null flag (saves 1 byte)Integer, Long, etc.) that can be null should use nullable = trueref)Enable reference tracking for fields that may be shared or circular:
public class RefOuter { // Both fields may point to the same inner object @ForyField(id = 0, ref = true, nullable = true) private RefInner inner1; @ForyField(id = 1, ref = true, nullable = true) private RefInner inner2; } public class CircularRef { @ForyField(id = 0) private String name; // Self-referencing field for circular references @ForyField(id = 1, ref = true, nullable = true) private CircularRef selfRef; }
Use Cases:
Notes:
ref = false (no reference tracking)ref = false, avoids IdentityMap overhead and skips ref tracking flagControls polymorphism behavior for struct fields in cross-language serialization:
public class Container { // AUTO: Interface/abstract types are dynamic, concrete types are not @ForyField(id = 0, dynamic = ForyField.Dynamic.AUTO) private Animal animal; // Interface - type info written // FALSE: No type info written, uses declared type's serializer @ForyField(id = 1, dynamic = ForyField.Dynamic.FALSE) private Dog dog; // Concrete - no type info // TRUE: Type info written to support runtime subtypes @ForyField(id = 2, dynamic = ForyField.Dynamic.TRUE) private Object data; // Force polymorphic }
Options:
| Value | Description |
|---|---|
AUTO | Auto-detect: interface/abstract are dynamic, concrete types are not |
FALSE | No type info written, uses declared type's serializer directly |
TRUE | Type info written to support subtypes at runtime |
@IgnoreExclude fields from serialization:
import org.apache.fory.annotation.Ignore; public class User { @ForyField(id = 0) private long id; @ForyField(id = 1) private String name; @Ignore private String password; // Not serialized @Ignore private Object internalState; // Not serialized }
transientJava's transient keyword also excludes fields:
public class User { @ForyField(id = 0) private long id; private transient String password; // Not serialized private transient Object cache; // Not serialized }
Fory provides annotations to control integer encoding for cross-language compatibility.
@Int32Type)import org.apache.fory.annotation.Int32Type; public class MyStruct { // Variable-length encoding (default) - compact for small values @Int32Type(compress = true) private int compactId; // Fixed 4-byte encoding - consistent size @Int32Type(compress = false) private int fixedId; }
@Int64Type)import org.apache.fory.annotation.Int64Type; import org.apache.fory.config.LongEncoding; public class MyStruct { // Variable-length encoding (default) @Int64Type(encoding = LongEncoding.VARINT) private long compactId; // Fixed 8-byte encoding @Int64Type(encoding = LongEncoding.FIXED) private long fixedTimestamp; // Tagged encoding (4 bytes for small values, 9 bytes otherwise) @Int64Type(encoding = LongEncoding.TAGGED) private long taggedValue; }
import org.apache.fory.annotation.Uint8Type; import org.apache.fory.annotation.Uint16Type; import org.apache.fory.annotation.Uint32Type; import org.apache.fory.annotation.Uint64Type; import org.apache.fory.config.LongEncoding; public class UnsignedStruct { // Unsigned 8-bit [0, 255] @Uint8Type private short flags; // Unsigned 16-bit [0, 65535] @Uint16Type private int port; // Unsigned 32-bit with varint encoding (default) @Uint32Type(compress = true) private long compactCount; // Unsigned 32-bit with fixed encoding @Uint32Type(compress = false) private long fixedCount; // Unsigned 64-bit with various encodings @Uint64Type(encoding = LongEncoding.VARINT) private long varintU64; @Uint64Type(encoding = LongEncoding.FIXED) private long fixedU64; @Uint64Type(encoding = LongEncoding.TAGGED) private long taggedU64; }
| Annotation | Type ID | Encoding | Size |
|---|---|---|---|
@Int32Type(compress = true) | 5 | varint | 1-5 bytes |
@Int32Type(compress = false) | 4 | fixed | 4 bytes |
@Int64Type(encoding = VARINT) | 7 | varint | 1-10 bytes |
@Int64Type(encoding = FIXED) | 6 | fixed | 8 bytes |
@Int64Type(encoding = TAGGED) | 8 | tagged | 4 or 9 bytes |
@Uint8Type | 9 | fixed | 1 byte |
@Uint16Type | 10 | fixed | 2 bytes |
@Uint32Type(compress = true) | 12 | varint | 1-5 bytes |
@Uint32Type(compress = false) | 11 | fixed | 4 bytes |
@Uint64Type(encoding = VARINT) | 14 | varint | 1-10 bytes |
@Uint64Type(encoding = FIXED) | 13 | fixed | 8 bytes |
@Uint64Type(encoding = TAGGED) | 15 | tagged | 4 or 9 bytes |
When to Use:
varint: Best for values that are often small (default)fixed: Best for values that use full range (e.g., timestamps, hashes)tagged: Good balance between size and performanceimport org.apache.fory.Fory; import org.apache.fory.annotation.ForyField; import org.apache.fory.annotation.Ignore; import org.apache.fory.annotation.Int64Type; import org.apache.fory.annotation.Uint64Type; import org.apache.fory.config.LongEncoding; import java.util.List; import java.util.Map; import java.util.Set; public class Document { // Fields with tag IDs (recommended for compatible mode) @ForyField(id = 0) private String title; @ForyField(id = 1) private int version; // Nullable field @ForyField(id = 2, nullable = true) private String description; // Collection fields @ForyField(id = 3) private List<String> tags; @ForyField(id = 4) private Map<String, String> metadata; @ForyField(id = 5) private Set<String> categories; // Integer with different encodings @ForyField(id = 6) @Uint64Type(encoding = LongEncoding.VARINT) private long viewCount; // varint encoding @ForyField(id = 7) @Uint64Type(encoding = LongEncoding.FIXED) private long fileSize; // fixed encoding @ForyField(id = 8) @Uint64Type(encoding = LongEncoding.TAGGED) private long checksum; // tagged encoding // Reference-tracked field for shared/circular references @ForyField(id = 9, ref = true, nullable = true) private Document parent; // Ignored field (not serialized) private transient Object cache; // Getters and setters... } // Usage public class Main { public static void main(String[] args) { Fory fory = Fory.builder() .withXlang(true) .withCompatible(true) .withRefTracking(true) .build(); fory.register(Document.class, 100); Document doc = new Document(); doc.setTitle("My Document"); doc.setVersion(1); doc.setDescription("A sample document"); // Serialize byte[] data = fory.serialize(doc); // Deserialize Document decoded = (Document) fory.deserialize(data); } }
When serializing data to be read by other languages (Python, Rust, C++, Go), use field IDs and matching type annotations:
public class CrossLangData { // Use field IDs for cross-language compatibility @ForyField(id = 0) @Int32Type(compress = true) private int intVar; @ForyField(id = 1) @Uint64Type(encoding = LongEncoding.FIXED) private long longFixed; @ForyField(id = 2) @Uint64Type(encoding = LongEncoding.TAGGED) private long longTagged; @ForyField(id = 3, nullable = true) private String optionalValue; }
Compatible mode supports schema evolution. It is recommended to configure field IDs to reduce serialization cost:
// Version 1 public class DataV1 { @ForyField(id = 0) private long id; @ForyField(id = 1) private String name; } // Version 2: Added new field public class DataV2 { @ForyField(id = 0) private long id; @ForyField(id = 1) private String name; @ForyField(id = 2, nullable = true) private String email; // New field }
Data serialized with V1 can be deserialized with V2 (new field will be null).
Alternatively, field IDs can be omitted (field names will be used in metadata with larger overhead):
public class Data { private long id; private String name; }
Field configuration behaves differently depending on the serialization mode:
Native mode has relaxed default values for maximum compatibility:
String, boxed types, and time types)In native mode, you typically don't need to configure field annotations unless you want to:
// Native mode: works without any annotations public class User { private long id; private String name; private List<String> tags; // Nullable and ref-tracked by default }
Xlang mode has stricter default values due to type system differences between languages:
nullable = false)ref = false)In xlang mode, you need to configure fields when:
nullable = true)ref = true)// Xlang mode: explicit configuration required for nullable/ref fields public class User { @ForyField(id = 0) private long id; @ForyField(id = 1) private String name; @ForyField(id = 2, nullable = true) // Must declare nullable private String email; @ForyField(id = 3, ref = true, nullable = true) // Must declare ref for shared objects private User friend; }
| Option | Native Mode Default | Xlang Mode Default |
|---|---|---|
nullable | true (reference types) | false |
ref | true | false |
dynamic | true (non-final) | AUTO (concrete types are final) |
nullable = true for nullable fields: Required for fields that can be nullref = true when objects are shared or circular@Ignore or transient for sensitive data: Passwords, tokens, internal statevarint for small values, fixed for full-range values| Annotation | Description |
|---|---|
@ForyField(id = N) | Field tag ID to reduce metadata size |
@ForyField(nullable = true) | Mark field as nullable |
@ForyField(ref = true) | Enable reference tracking |
@ForyField(dynamic = ...) | Control polymorphism for struct fields |
@Ignore | Exclude field from serialization |
@Int32Type(compress = ...) | 32-bit signed integer encoding |
@Int64Type(encoding = ...) | 64-bit signed integer encoding |
@Uint8Type | Unsigned 8-bit integer |
@Uint16Type | Unsigned 16-bit integer |
@Uint32Type(compress = ...) | Unsigned 32-bit integer encoding |
@Uint64Type(encoding = ...) | Unsigned 64-bit integer encoding |