blob: 6a6d735ef5eee17fb8b02ba5cc579b19164e2d30 [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` 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.
## 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 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 |
## Quick Start
For general-purpose object graphs, enable `withRefCopy(true)` so shared references and cycles are
handled correctly:
```java
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`.
## Reference Semantics
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.
```java
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.
```java
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 `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()
.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
If class registration is required, register copied classes before calling `copy`.
```java
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.
## Thread-Safe Copy
`ThreadSafeFory` also supports `copy(...)`.
For general multi-threaded usage:
```java
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)`.
## Built-In Coverage
Fory already provides copy support for many common Java runtime 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
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.
## 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 runtime 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
- Keep mutable custom serializer copy paths explicit and do not rely on fallback behavior
## 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()
.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()
.withRefCopy(true)
.build();
```
`withRefTracking(true)` alone does not change `Fory.copy(...)` behavior.
### `Copy for ... is not supported`
This means the mutable serializer for that type does not implement `copy(...)`.
Fix it by either:
- Implementing `ForyCopyable<T>` on the class, or
- Overriding `Serializer.copy(CopyContext, T)` in the registered serializer
### Registration Errors
If your runtime uses `requireClassRegistration(true)`, make sure the copied runtime types are
registered before calling `copy(...)`.
## Related Topics
- [Basic Serialization](basic-serialization.md) - Runtime 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 runtime guidance