In the recently released Solon v4.0.5, a minor but strategically significant update is highlighted in the release notes:
- Added
solon-serialization-foryplugin (the originalsolon-serialization-furyis now deprecated).
For developers familiar with the Java serialization ecosystem, this is not just a simple name refactoring. It marks Solon's active alignment with the renaming milestone of the top-tier Apache serialization framework—Apache Fury, which has officially transitioned to Apache Fory.
This article dives into the technical details of how Solon integrates the new Apache Fory engine under the hood, how it maintains backward compatibility, and how it handles deserialization security with its built-in blacklist checker.
The Backdrop: Why Apache Fury Became Apache Fory
In June 2025, the Apache Fury team announced that due to trademark regulations and brand compliance requested by the Apache Software Foundation (ASF), the project was renamed to Apache Fory, effective immediately.
Along with the name change, the project migrated:
-
Maven GroupId & ArtifactId: from
org.apache.furytoorg.apache.fory(e.g.,fory-core). -
Java Package Namespace: from
org.apache.fury.*toorg.apache.fory.*. -
Class Prefixes: from
XXXFury/FuryXXXtoXXXFory/ForyXXX.
Fory preserves all the blazingly fast features of Fury—including JIT compilation, zero-copy, cross-language serialization, and reference tracking—while starting its new lifecycle under the v0.11.0+ release series (currently at 1.5.0 in the stable branch).
Solon's Migration Path: solon-serialization-fory
In Solon v4.0.5, the new solon-serialization-fory plugin was introduced, bringing in the org.apache.fory:fory-core:1.5.0 dependency.
Let's dissect the core components of this integration to see how it operates.
1. The Serialization Plugin
The SerializationForyPlugin acts as the entrypoint for Solon's IoC container to boot up the Fory engine:
public class SerializationForyPlugin implements Plugin {
@Override
public void start(AppContext context) {
// 1. Retrieve the default serializer instance
ForyBytesSerializer serializer = ForyBytesSerializer.getDefault();
context.wrapAndPut(ForyBytesSerializer.class, serializer);
context.wrapAndPut(EntityBytesSerializer.class, serializer);
// 2. Register Fory using the legacy "@fury" mapping name
context.app().serializers().register(SerializerNames.AT_FURY, serializer);
// 3. Register the entity converter for HTTP body conversion
ForyEntityConverter entityConverter = new ForyEntityConverter(serializer);
context.wrapAndPut(ForyEntityConverter.class, entityConverter);
context.app().chains().addEntityConverter(entityConverter);
}
}
2. The Serializer Engine (ForyBytesSerializer)
The ForyBytesSerializer wraps the Fory engine. It constructs a ThreadLocalFory instance (which implements ThreadSafeFory) to ensure thread-safety when reusing serializer configurations across threads:
fory = new ThreadLocalFory(classLoader -> {
Fory tmp = Fory.builder()
.withAsyncCompilation(true) // Enable asynchronous JIT compilation
.withLanguage(Language.JAVA) // Set JVM-native language mode
.withRefTracking(true) // Enable circular and shared reference tracking
.requireClassRegistration(false) // Do not force manual class registration
.build();
// Populate and apply the deserialization blacklist
for (String key : blackList) {
blackListChecker.disallowClass(key + "*");
}
tmp.getTypeResolver().setTypeChecker(blackListChecker);
return tmp;
});
3. Backward Compatibility: Preserving @fury
One major concern during package renaming is breaking downstream systems. For example, if you are using Solon's Nami RPC to communicate between microservices, how does the caller negotiate serialization formats with the receiver?
Solon elegant solves this by mapping the new fory serializer to the legacy constant SerializerNames.AT_FURY (which translates to the string "@fury").
When a Solon microservice receives an HTTP request containing:
Serialization-Type: @fury
Content-Type: application/fory
The router maps the request payload to the new ForyBytesSerializer seamlessly. Existing client-side Nami configurations utilizing Fury do not need to rewrite their protocol negotiator tags.
Hardening Deserialization Security with AllowListChecker
Fast binary serialization engines (like Fury/Fory, Kryo, or Hessian) often suffer from gadgets injection vulnerabilities if arbitrary untrusted classes are allowed to be deserialized.
To counter this, Solon's Fory plugin implements a built-in safety net. During the initialization of ForyBytesSerializer, it loads META-INF/solon/furyBlackList.txt (which contains a comprehensive blacklist of dangerous classes, such as JNDI connections, logback DB appenders, or scripting interpreters):
public class BlackListUtil {
private static final String BLACKLIST_TXT_PATH = "META-INF/solon/furyBlackList.txt";
private static Collection<String> blackList;
public static Collection<String> getBlackList() {
if (blackList == null) {
try (InputStream is = ResourceUtil.getResourceAsStream(BLACKLIST_TXT_PATH)) {
if (is != null) {
blackList = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))
.lines()
.collect(Collectors.toSet());
} else {
throw new IllegalStateException("Read blacklist failed");
}
} catch (IOException e) {
throw new IllegalStateException("Read blacklist failed", e);
}
}
return blackList;
}
}
These rules are then registered with Fory's AllowListChecker under CheckLevel.WARN. If an attacker attempts to transmit a payload containing a blacklisted class, the engine blocks the instantiation and throws a warning, keeping your application safe.
How to Use the New Fory Serialization in Solon 4.0.5
If you want to use the blazingly fast Fory serialization for REST controllers or Nami RPC in your Solon 4.0.5 project, follow these simple steps:
Step 1: Add the Dependency
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-serialization-fory</artifactId>
</dependency>
Step 2: Annotate your Controller or RPC Service
When returning objects or taking request bodies, Solon's AOP layer will automatically convert the format when specifying @fury content types:
@Controller
public class OrderController {
// Accepts and returns application/fory binary format
@Produces("application/fory")
@Mapping("/order/process")
public OrderDo processOrder(@Body OrderDo order) {
order.setStatus("PROCESSED");
return order;
}
}
Or programmatically access it:
ForyBytesSerializer serializer = ForyBytesSerializer.getDefault();
byte[] data = serializer.serialize(new OrderDo(1001, "iPad"));
OrderDo order = (OrderDo) serializer.deserialize(data, OrderDo.class);
Summary
Solon's migration from solon-serialization-fury to solon-serialization-fory is a textbook example of maintaining modern ecosystem alignment without breaking backward compatibility. By upgrading to Fory 1.5.0, Solon projects benefit from the latest multi-language JIT-powered serialization improvements, whilst maintaining seamless integration with legacy microservices via the @fury protocol tag.
If you are running high-throughput microservices on Solon, swapping out standard JSON for the solon-serialization-fory plugin is a quick way to save CPU cycles and bandwidth.
Top comments (0)