android-support.md
July 16, 2026 ยท View on GitHub
Android Runtime
Fory Java supports Android 8.0+ (API level 26+) through the regular fory-core artifact. No separate
Android artifact is required for core object serialization.
Use core object serialization on Android:
Fory#serialize(Object)andFory#deserialize(byte[]).BaseFory#deserialize(ByteBuffer)for heap, direct, and read-onlyByteBufferinputs.- Stream, channel, and out-of-band buffer APIs through byte-array, heap-buffer, or
ByteBuffercopy paths. - Java collections/maps and xlang collections/maps.
java/fory-format row-format APIs are JVM-only and are not supported on Android.
Runtime Codegen
Runtime serializer code generation is disabled on Android. If withCodegen(true) is set, Fory keeps
Android serialization on the non-codegen path and logs a warning.
Android apps that need generated serializers should use build-time static generated serializers instead.
Fory JSON
Fory JSON supports ordinary classes on Android API level 26 and later through the regular
fory-json artifact. Runtime JSON code generation and asynchronous compilation are disabled
automatically, so ForyJson.builder().build() uses the interpreted object mapper.
Add Fory JSON to the application:
dependencies {
implementation("org.apache.fory:fory-json:${foryVersion}")
}
@JsonCodec has the same declaration behavior on Android and the JVM. It supports complete values,
direct collection and array elements, Optional and AtomicReference contents, Map keys and
values, ordinary getters, setter value parameters, and JsonCreator parameters:
import java.util.List;
import org.apache.fory.json.annotation.JsonCodec;
public final class Invoice {
@JsonCodec(elementCodec = MoneyCodec.class)
public List<Money> items;
private Money primary;
public void setPrimary(@JsonCodec(MoneyCodec.class) Money primary) {
this.primary = primary;
}
public Invoice() {}
}
Child codecs act on one direct level only. For example, elementCodec on Money[][] handles each
Money[], and elementCodec on AtomicReferenceArray<Money> handles each Money. Use a complete
value codec when deeper custom behavior is required.
Add the annotation processor and mark application object models with JsonType to generate direct
field, getter, setter, Record constructor, and JsonCreator operations together with exact R8
rules:
dependencies {
annotationProcessor("org.apache.fory:fory-annotation-processor:${foryVersion}")
}
import org.apache.fory.json.annotation.JsonType;
@JsonType
public final class Invoice {
// ...
}
The same processor supports Fory JSON Mixins. A Mixin declares one exact target and is registered on the runtime that should use it:
import org.apache.fory.json.ForyJson;
import org.apache.fory.json.annotation.JsonBase64;
import org.apache.fory.json.annotation.JsonMixin;
@JsonMixin(target = ThirdPartyInvoice.class)
public abstract class ThirdPartyInvoiceMixin {
@JsonBase64 byte[] signature;
}
ForyJson json =
ForyJson.builder().registerMixin(ThirdPartyInvoiceMixin.class).build();
Compile every non-empty Mixin source with fory-annotation-processor. The processor emits exact
R8 rules and any pair-specific target operations that the runtime can use. Registered codecs,
effective type codecs, and built-in mappings keep their normal runtime precedence. An empty Mixin
produces no generated output.
The target does not need JsonType merely because it has a Mixin. JsonMixin is itself the
processor entry point for the pair. If a target also uses JsonType, the runtime selects the
pair-specific companion for a non-empty registered Mixin instead of combining the overlay with the
target's direct companion.
Only one source is enabled for an exact target in one built runtime. A later registration for that
target replaces an earlier registration on the builder, and build() snapshots the selected
mapping. The processor may generate artifacts for multiple source alternatives; the runtime uses
only the last registered source.
Use the processor-generated R8 rules for non-empty Mixins instead of broad package keep rules.
Ordinary non-Record classes that omit JsonType can supply equivalent exact rules themselves.
Retain every model
constructor, field, method, generic signature, declaration annotation, and parameter annotation used
by Fory JSON, plus the public no-argument constructor of every annotation-selected codec. For the
previous Invoice example:
-keepattributes Signature,RuntimeVisibleAnnotations,RuntimeVisibleParameterAnnotations
-keepattributes AnnotationDefault,MethodParameters,InnerClasses,EnclosingMethod
-keep,allowoptimization class com.example.Invoice {
public <init>();
public java.util.List items;
public void setPrimary(com.example.Money);
}
-keep,allowoptimization,allowobfuscation class com.example.MoneyCodec {
public <init>();
}
The same exact-rule approach supports every JsonCodec member; it is not limited to complete-value
codecs. JsonType is not required for codec selection on an ordinary class.
For @JsonType models, the generated R8 rules also retain JsonValue fields and effective methods,
fixed JsonRawValue and JsonBase64 fields and getters, their runtime annotations, and the Base64
codec constructor. Without @JsonType, these annotations still work through reflection, but a
release-minified application must keep the exact annotated members, annotation attributes, and
codec constructor itself. A JsonValue method may use a non-JavaBean name, so its manual rule must
name that method explicitly.
Android Fory JSON requires a retained no-argument constructor for an ordinary mutable class; it may
be non-public when Android reflection can make it accessible. JsonCreator constructor-backed
classes follow the normal creator rules instead. Retain every field and method used for reflection,
or use an application codec when a model cannot satisfy those requirements. JsonUnwrapped
supports mutable classes, creator-backed classes, and Records through their normal property and
construction paths. When the containing model and its unwrapped children use JsonType, their
generated companions supply those operations.
Android-desugared Records require processor-generated operations from either a direct @JsonType
declaration or a compiled exact @JsonMixin pair. Manual R8 rules alone cannot reconstruct Record
component order because Android does not provide the Java Record reflection APIs. This also applies
to a Record whose complete representation is a JsonValue String: the generated companion
identifies the propagated component accessor and calls an annotated one-String canonical
constructor directly. Generated child codecs act on one level exactly as they do on the JVM. Every
Record in a JsonUnwrapped path needs its own direct JsonType declaration or compiled exact
JsonMixin pair. Use a complete value codec for deeper nested behavior.
Static Generated Serializers
Use @ForyStruct static generated serializers for Android application classes. They are generated by
javac during the app build and work without runtime bytecode generation.
Install The Annotation Processor
Add fory-annotation-processor to the annotation processor path of the module that compiles your
Android model classes:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.apache.fory</groupId>
<artifactId>fory-annotation-processor</artifactId>
<version>${fory.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
Then annotate Android model classes with @ForyStruct.
Static generated serializers are required on Android when a serialized class uses Fory type-use annotations, for example:
import java.util.List;
import org.apache.fory.annotation.ForyStruct;
import org.apache.fory.annotation.UInt8Type;
@ForyStruct
public class ImageBlock {
public List<@UInt8Type Integer> pixels;
}
Without the generated static descriptors, Android reflection may not expose the nested type-use
metadata needed for annotations such as @Ref, @Int8Type, @UInt8Type, @Float16Type, or
@BFloat16Type. Serialization for those classes will not have the schema information Fory needs.
See Static Generated Serializers for setup instructions.
Object Model Requirements
Android serializers use public Android APIs. For application classes, prefer:
- accessible no-argument constructors, or records with supported constructors.
- public, protected, or package-private serialized fields.
- non-private getters and setters for private serialized fields.
@ForyStructstatic generated serializers for Android model classes.
Final fields in ordinary classes are not suitable for generated read/copy methods. Use records for constructor-based immutable values.
Unsupported Features
The following JVM features are not supported on Android:
- Runtime serializer code generation and async compilation.
- Lambda and
SerializedLambdaserialization. - Native-address serialization APIs and native-address
MemoryBufferwrapping. - Raw unsafe memory copy APIs.
java/fory-formatrow-format APIs.
ByteBuffer
BaseFory#deserialize(ByteBuffer) supports heap, direct, and read-only buffers on Android by copying
the remaining bytes into a Fory-owned heap buffer. The caller buffer position and limit are not
changed.
Raw direct-buffer address wrapping is a JVM-only fast path and is not used on Android.
Collections, Maps, And Proxies
Common JDK collection and map implementations are supported on Android. In xlang mode, collection and map serialization uses the xlang protocol and does not encode Java wrapper/view internals.
java.lang.reflect.Proxy serialization is supported for normal proxy usage. Do not invoke, log, or
use a proxy as a map/set key while it is still being deserialized; the invocation handler may not be
ready yet.