title: Object Copy sidebar_position: 9 id: object_copy 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 covers in-memory Java object graph copying with Fory#copy(Object).
Fory.copy is a deep-copy operation for Java object graphs. It does not serialize to bytes first. Instead, it uses the same runtime type system and serializers to create a copied object graph in memory.
Use object copy when you want a detached in-memory clone of an existing Java object graph.
Typical use cases:
Use serialization instead when you need bytes for transport, storage, or cross-process exchange.
| Operation | Fory.copy | serialize / deserialize |
|---|---|---|
| Result | Java object graph | Binary payload plus reconstructed objects |
| Main use | In-memory deep copy | Transport, persistence, interoperability |
| Copy ref option | withRefCopy(...) | withRefTracking(...) |
| Cross-language payload | No | Yes, in xlang mode |
| Intermediate byte buffer | No | Yes |
For general-purpose object graphs, enable withRefCopy(true) so shared references and cycles are handled correctly:
import org.apache.fory.Fory; import org.apache.fory.config.Language; public class Example { public static void main(String[] args) { Fory fory = Fory.builder() .withLanguage(Language.JAVA) .withRefCopy(true) .build(); Order original = new Order(); Order copied = fory.copy(original); } }
copy(null) returns null.
The most important copy option is ForyBuilder#withRefCopy(boolean).
withRefCopy(true)This is the safe default for general object graphs. Shared references remain shared in the copied graph, and circular references can be copied correctly.
import org.apache.fory.Fory; import org.apache.fory.config.Language; public class Example { static final class Address { String city; } static final class Pair { Address left; Address right; } public static void main(String[] args) { Fory fory = Fory.builder() .withLanguage(Language.JAVA) .withRefCopy(true) .build(); Address address = new Address(); address.city = "Shanghai"; Pair pair = new Pair(); pair.left = address; pair.right = address; Pair copied = fory.copy(pair); System.out.println(copied.left == copied.right); // true } }
withRefCopy(false)Disable copy ref tracking only when you know the graph is tree-like and does not rely on shared or cyclic references. This can be faster, but repeated references are copied into different objects.
import org.apache.fory.Fory; import org.apache.fory.config.Language; public class Example { static final class Address { String city; } static final class Pair { Address left; Address right; } public static void main(String[] args) { Fory fory = Fory.builder() .withLanguage(Language.JAVA) .withRefCopy(false) .build(); Address address = new Address(); Pair pair = new Pair(); pair.left = address; pair.right = address; Pair copied = fory.copy(pair); System.out.println(copied.left == copied.right); // false } }
If you disable withRefCopy and the graph contains a cycle, copy can fail with stack overflow.
withRefCopy vs withRefTrackingThese two options control different operations:
withRefCopy(true) affects Fory.copy(...)withRefTracking(true) affects serialization and deserializationEnabling one does not automatically enable the other. If your application both serializes and copies graphs with shared or circular references, configure both options explicitly.
Fory fory = Fory.builder() .withRefTracking(true) .withRefCopy(true) .build();
Fory may reuse the original instance for immutable values. For mutable values, it creates a new object graph.
In practice, this means:
String, boxed primitives, enums, and many immutable JDK value types may be returned as-isDo not use object identity alone to decide whether copy succeeded. Use the mutability contract of the value you are copying.
If class registration is required, register copied classes before calling copy.
import org.apache.fory.Fory; public class Example { public static void main(String[] args) { Fory fory = Fory.builder() .requireClassRegistration(true) .withRefCopy(true) .build(); fory.register(Order.class); Order copied = fory.copy(new Order()); } }
This follows the same registration rules as the rest of the runtime: if the runtime requires class registration, copied runtime types must be registered first.
ThreadSafeFory also supports copy(...).
For general multi-threaded usage:
import org.apache.fory.Fory; import org.apache.fory.ThreadSafeFory; import org.apache.fory.config.Language; public class Example { public static void main(String[] args) { ThreadSafeFory fory = Fory.builder() .withLanguage(Language.JAVA) .withRefCopy(true) .buildThreadSafeFory(); Order copied = fory.copy(new Order()); } }
The same API also works for buildThreadLocalFory() and buildThreadSafeForyPool(poolSize).
Fory already provides copy support for many common Java runtime types, including:
If the runtime already knows how to serialize a mutable type, it may still need an explicit copy implementation in that serializer. For mutable serializers, the default Serializer.copy(...) throws UnsupportedOperationException unless the serializer overrides it.
ForyCopyableIf a type needs custom copy logic, implement ForyCopyable<T>.
This is the simplest approach when the class itself should control how nested fields are copied:
import java.util.ArrayList; import java.util.List; import org.apache.fory.ForyCopyable; import org.apache.fory.context.CopyContext; public final class Node implements ForyCopyable<Node> { private String name; private final List<Node> neighbors = new ArrayList<>(); @Override public Node copy(CopyContext copyContext) { Node copied = new Node(); copyContext.reference(this, copied); copied.name = name; for (Node neighbor : neighbors) { copied.neighbors.add(copyContext.copyObject(neighbor)); } return copied; } }
Guidelines:
copyContext.reference(origin, copy) immediately after creating a composite mutable object if the type can participate in cycles or shared-reference graphscopyContext.copyObject(...) for nested values instead of manually duplicating nested copy logicWhen a type already uses a custom serializer, override Serializer.copy(...) for mutable values.
import org.apache.fory.config.Config; import org.apache.fory.context.CopyContext; import org.apache.fory.context.ReadContext; import org.apache.fory.context.WriteContext; import org.apache.fory.serializer.Serializer; public final class EnvelopeSerializer extends Serializer<Envelope> { public EnvelopeSerializer(Config config) { super(config, Envelope.class); } @Override public Envelope copy(CopyContext copyContext, Envelope value) { Envelope copied = new Envelope(); copyContext.reference(value, copied); copied.header = copyContext.copyObject(value.header); copied.payload = copyContext.copyObject(value.payload); return copied; } @Override public void write(WriteContext writeContext, Envelope value) { throw new UnsupportedOperationException("omitted"); } @Override public Envelope read(ReadContext readContext) { throw new UnsupportedOperationException("omitted"); } }
Use this approach when copy behavior belongs with a serializer rather than the domain class.
Fory or ThreadSafeFory instances instead of rebuilding them for each copywithRefCopy(true) unless you are certain the graph is acyclic and does not rely on shared referenceswithRefCopy(false) as a performance optimization for tree-like data, not as a defaultIf copy fails on a cyclic object graph, enable withRefCopy(true):
Fory fory = Fory.builder() .withRefCopy(true) .build();
Disabling copy ref tracking is only safe for acyclic graphs.
If the same source object is copied into multiple distinct target objects, withRefCopy is disabled. Turn it on:
Fory fory = Fory.builder() .withRefCopy(true) .build();
withRefTracking(true) alone does not change Fory.copy(...) behavior.
Copy for ... is not supportedThis means the mutable serializer for that type does not implement copy(...).
Fix it by either:
ForyCopyable<T> on the class, orSerializer.copy(CopyContext, T) in the registered serializerIf your runtime uses requireClassRegistration(true), make sure the copied runtime types are registered before calling copy(...).
withRefCopy