blob: 974018743c5b067cfecf0f157494cb4157cee073 [file] [view]
---
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
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.
---
This page covers in-memory Java object graph copying with `Fory#copy(Object)`.
`Fory.copy` creates a deep copy of a Java object graph in memory. It does not produce serialized
output.
## When to Use Object Copy
Use object copy when you want a detached in-memory clone of an existing Java object graph.
Typical use cases:
- Clone request or response models before mutation
- Duplicate cached state for optimistic updates
- Copy graphs that contain collections, maps, arrays, or nested beans
- Preserve shared references and circular references during cloning
Use serialization instead when you need bytes for transport, storage, or cross-process exchange.
| Operation | `Fory.copy` | `serialize` / `deserialize` |
| ---------------------- | ------------------- | ---------------------------------------- |
| Result | Java object graph | Binary data or a reconstructed object |
| Main use | In-memory deep copy | Transport, persistence, interoperability |
| Reference option | `withRefCopy(...)` | `withRefTracking(...)` |
| Cross-language support | No | Yes, in xlang mode |
| Uses serialized data | No | Yes |
## Quick Start
For general-purpose object graphs, enable `withRefCopy(true)` so shared references and cycles are
handled correctly:
```java
import org.apache.fory.Fory;
public class Example {
public static void main(String[] args) {
Fory fory = Fory.builder()
.withXlang(false)
.withRefCopy(true)
.build();
Order original = new Order();
Order copied = fory.copy(original);
}
}
```
`copy(null)` returns `null`.
## Reference Semantics
The most important copy option is `ForyBuilder#withRefCopy(boolean)`.
### `withRefCopy(true)`
This is the recommended setting for general object graphs. Shared references remain shared in the
copied graph, and circular references can be copied correctly.
```java
import org.apache.fory.Fory;
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()
.withXlang(false)
.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.
```java
import org.apache.fory.Fory;
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()
.withXlang(false)
.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 `withRefTracking`
These two options control different operations:
- `withRefCopy(true)` affects `Fory.copy(...)`
- `withRefTracking(true)` affects serialization and deserialization
Enabling one does not automatically enable the other. If your application both serializes and
copies graphs with shared or circular references, configure both options explicitly.
```java
Fory fory = Fory.builder().withXlang(false)
.withRefTracking(true)
.withRefCopy(true)
.build();
```
## Immutable vs Mutable Values
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-is
- Primitive arrays, string arrays, collections, maps, beans, dates, and other mutable structures
are copied into distinct objects
Do not use object identity alone to decide whether copy succeeded. Use the mutability contract of
the value you are copying.
## Class Registration
When class registration is enabled, register application classes before copying their object
graphs.
```java
import org.apache.fory.Fory;
public class Example {
public static void main(String[] args) {
Fory fory = Fory.builder().withXlang(false)
.requireClassRegistration(true)
.withRefCopy(true)
.build();
fory.register(Order.class);
Order copied = fory.copy(new Order());
}
}
```
## Thread-Safe Copy
`ThreadSafeFory` also supports `copy(...)`.
For general multi-threaded usage:
```java
import org.apache.fory.Fory;
import org.apache.fory.ThreadSafeFory;
public class Example {
public static void main(String[] args) {
ThreadSafeFory fory = Fory.builder()
.withXlang(false)
.withRefCopy(true)
.buildThreadSafeFory();
Order copied = fory.copy(new Order());
}
}
```
The same API also works for `buildThreadLocalFory()` and `buildThreadSafeForyPool(poolSize)`.
## Built-In Coverage
Fory already provides copy support for many common Java platform types, including:
- Primitive values and boxed primitives
- Strings and primitive arrays
- Common JDK collections and maps
- Java time and date/time values
- Beans, records, and nested object graphs
Custom serializers for mutable types must implement `Serializer.copy(...)` to support object copy.
## Custom Copy with `ForyCopyable`
If 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:
```java
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:
- Call `copyContext.reference(origin, copy)` immediately after creating a composite mutable object
if the type can participate in cycles or shared-reference graphs
- Use `copyContext.copyObject(...)` for nested values instead of manually duplicating nested copy
logic
- Keep copy logic consistent with the normal Java semantics of the type
## Custom Copy in a Serializer
When a type already uses a custom serializer, override `Serializer.copy(...)` for mutable values.
```java
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.
## Best Practices
- Reuse `Fory` or `ThreadSafeFory` instances instead of rebuilding them for each copy
- Enable `withRefCopy(true)` unless you are certain the graph is acyclic and does not rely on
shared references
- Treat `withRefCopy(false)` as a performance optimization for tree-like data, not as a default
- Test custom copy implementations with both shared-reference and cyclic graphs
- Implement `Serializer.copy(...)` for every mutable type that uses a custom serializer
## Troubleshooting
### Stack Overflow or Copy Failure on Cyclic Graphs
If copy fails on a cyclic object graph, enable `withRefCopy(true)`:
```java
Fory fory = Fory.builder().withXlang(false)
.withRefCopy(true)
.build();
```
Disabling copy ref tracking is only safe for acyclic graphs.
### Shared References Are Not Preserved
If the same source object is copied into multiple distinct target objects, `withRefCopy` is
disabled. Turn it on:
```java
Fory fory = Fory.builder().withXlang(false)
.withRefCopy(true)
.build();
```
`withRefTracking(true)` alone does not change `Fory.copy(...)` behavior.
### `Copy for ... is not supported`
This means object copy is not implemented for that type.
Fix it by either:
- Implementing `ForyCopyable<T>` on the class, or
- Overriding `Serializer.copy(CopyContext, T)` in the registered serializer
## Related Topics
- [Basic Serialization](basic-serialization.md) - Fory instance creation and core APIs
- [Configuration](configuration.md) - Builder options including `withRefCopy`
- [Custom Serializers](custom-serializers.md) - Serializer design and registration
- [Virtual Threads](virtual-threads.md) - Thread-safe Fory guidance