7 Securing Serialization and Remote Execution
A distributed operation carries more than data. Entry processors, aggregators, filters, extractors, comparators, invocables, and tasks can cause code to run on another process. Coherence applies checks during decoding and at executable installation or invocation. User authorization is a separate policy applied to the authenticated caller and requested resource.
-
Define the application types that may cross each service boundary and package their security configuration.
-
Classify remote executable classes and functional-interface targets; classify nested state as well as the outer object.
-
Configure serializer and protocol-specific policies, including POF registration, JSON aliases, and gRPC serializer names.
-
Exercise the actual client/server workflows under hardened policy before enabling it across the deployment.
Start with Generating Application Security Configuration, then use Classifying Remote Executable Objects and Choosing a Lambda Serialization Policy. Diagnose failures with Security Errors and Diagnostic Signals.
Use Developing Classified Application Types for complete record and lite/POF processor examples with explicit type/executable configuration.
Layered Input Validation
Layered validation allows Coherence to fail closed for common deserialization entry points. It adds bytecode deny-list checks, JEP-290 filter enforcement, strict POF type handling, dynamic class-definition validation, generated application allowlists, security-mode-aware defaults, and per-boundary telemetry.
The compatibility model has two independent axes:
-
coherence.moderemains the released runtime/license mode switch. It is independent of the security profile. -
coherence.security.modecontrols the compatibility-sensitive serialization and remote-execution gates. Starting with Coherence 26.1.0.0, an unset property selectshardened. Set-Dcoherence.security.mode=compatibilityexplicitly when a migration needs the older compatibility-sensitive behavior and shadow telemetry.
Why not JEP-290 alone?
java.io.ObjectInputFilter (JEP-290) is a transport-level filter wired to ObjectInputStream. Coherence's dominant wire formats (POF, ExternalizableLite, PortableObject) bypass ObjectInputStream entirely, so a JEP-290 filter alone would silently miss the majority of deserialization traffic. serialization hardening also needs the same allowlist to drive three distinct gates -- class allowlist on deserialization, lambda-bytecode acceptance, and remote-execution policy -- and to compose across many modules at build time with annotation-driven generation. JEP-290's single allow/reject bit and flat filter string cannot express that. Coherence therefore uses security-config.xml as the source of truth, has every wire-format reader consult it, and registers a JEP-290 filter that delegates back to the same allowlist for the paths that do run through ObjectInputStream. JEP-290 is a hook, not a substitute, and customer-supplied jdk.serialFilter settings still compose normally for non-Coherence streams.
Compatibility and Existing Payloads
coherence.security.mode controls how serialization hardening gates are applied while applications migrate to generated security-config.xml and explicit remote-executable classification.
| Security mode | Allowlist gate: isAllowlistEnforced()
|
DYNAMIC remote default: isDynamicRemoteDefaultDeny()
|
Remote executable gate: isRemoteExecutableEnforced()
|
Use |
|---|---|---|---|---|
Explicit compatibility
|
false
|
false
|
false
|
Explicit migration profile. Set coherence.security.mode=compatibility before startup when the upgrade plan needs older compatibility-sensitive behavior. Selected gates emit result=would_reject shadow telemetry in either runtime mode; omission of the property does not select this profile starting with Coherence 26.1.0.0.
|
hardened or unset (starting with 26.1.0.0)
|
true
|
false |
true
|
Enforced security profile. Generated allowlists and named remote-executable classification are enforced. The current integration default permits unauthenticated dynamic remote code; set coherence.remote.dynamic.unauthenticated=deny explicitly to refuse those paths.
|
Trust Application Code Deliberately
An allowlisted class is trusted application code. Its constructors, read methods, resolution hooks, and invoked methods can use resources available to the member process. Review dependencies and code changes with the same care as member-side code. Class allowlisting is not an isolation sandbox, and it does not replace filesystem, process, or network restrictions.
Use exact application classes where practical. Broad package entries make every matching future class eligible. Built-in deny rules are evaluated independently and cannot be bypassed merely by adding an allow entry. Preserve those boundaries when migrating an existing payload.
- Developing Classified Application Types
Use modern data and processor examples with explicit serialization, POF registration, and executable classification. - Generating Application Security Configuration
Generate and package security configuration for every application artifact that contributes serialized payload types or remote executables. - Classifying Remote Executable Objects
Declare trusted remote behavior explicitly and verify the operation-specific installation policy on the receiving service. - Choosing a Lambda Serialization Policy
Choose how remote lambdas are represented and account for captured state, executable targets, and mixed-version deployment.
Developing Classified Application Types
An immutable application value
This Java 21+ record uses ordinary Java serialization and an explicit application allowlist annotation. Compile the value module and run security-config generation for that module. The annotation classifies the type; it does not select a serializer or define a POF type ID.
package com.example.security;
import com.tangosol.util.function.Remote;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Objects;
@Remote.Allowed
public record Invoice(String id, BigDecimal amount) implements Serializable {
public Invoice {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(amount, "amount");
}
}
Select a compatible serializer on the actual receiving path. If the application uses POF, provide its supported record/model mapping and registration instead of assuming Serializable alone supplies a POF codec. For gRPC, the separately approved client serializer format still applies.
A purpose-built remote processor
The following complete processor supports the Coherence lite and POF codecs explicitly and has a public no-argument constructor for realization. It operates on non-null integer values, creates a missing value from zero, and rejects arithmetic overflow through Math.addExact. It requires Java 21+ and the application security-generation step.
package com.example.security;
import com.tangosol.io.ExternalizableLite;
import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;
import com.tangosol.util.InvocableMap;
import com.tangosol.util.function.Remote;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
@Remote.Executable
public final class IncrementBalance
implements InvocableMap.EntryProcessor<String, Integer, Integer>,
ExternalizableLite, PortableObject {
private int delta;
public IncrementBalance() {
}
public IncrementBalance(int delta) {
this.delta = delta;
}
@Override
public Integer process(InvocableMap.Entry<String, Integer> entry) {
int current = entry.isPresent() ? entry.getValue() : 0;
int updated = Math.addExact(current, delta);
entry.setValue(updated);
return updated;
}
@Override
public void readExternal(DataInput in) throws IOException {
delta = in.readInt();
}
@Override
public void writeExternal(DataOutput out) throws IOException {
out.writeInt(delta);
}
@Override
public void readExternal(PofReader in) throws IOException {
delta = in.readInt(0);
}
@Override
public void writeExternal(PofWriter out) throws IOException {
out.writeInt(0, delta);
}
}
For a POF deployment, save the following as an application file such as billing-pof-config.xml, use a type ID allocated uniquely within the application's registry, and select this configuration on every participating process. The example ID is illustrative; it is not an Oracle-assigned application ID.
<pof-config xmlns="http://xmlns.oracle.com/coherence/coherence-pof-config">
<user-type-list>
<include>coherence-pof-config.xml</include>
<user-type>
<type-id>10201</type-id>
<class-name>com.example.security.IncrementBalance</class-name>
</user-type>
</user-type-list>
</pof-config>
Deploy the processor class and generated META-INF/coherence/security-config.xml on the receiver. Its generated entry has executable="true"; POF registration does not replace that classification. With a typed map obtained from the intended authenticated session, an invocation is:
NamedMap<String, Integer> balances = session.getMap("balances");
int updated = balances.invoke("account-1", new IncrementBalance(5));
Use an authorization policy and cache mapping appropriate to balances; the accounts-only policy elsewhere in this guide deliberately denies other cache names. Verify allowed execution and a denied caller, then verify that an unclassified test processor is rejected in an isolated hardened deployment. No global dynamic/reflection allowance is needed merely to run this purpose-built processor.
Parent topic: Securing Serialization and Remote Execution
Generating Application Security Configuration
The build-time generator scans compiled classes in the current artifact. It does not scan dependency jars. Each jar that contributes wire payload types should generate and package its own META-INF/coherence/security-config.xml.
Maven
The security-config-maven-plugin goal is named generate and is bound to process-classes by default.
<plugin>
<groupId>com.oracle.coherence</groupId>
<artifactId>security-config-maven-plugin</artifactId>
<version>${coherence.version}</version>
<executions>
<execution>
<id>generate-security-config</id>
<phase>process-classes</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
Set -Dcoherence.security-config.skip=true to skip generation for a build.
Gradle
The Coherence Gradle plugin registers the securityConfig task when the Java plugin is applied.
plugins {
id 'java'
id 'com.oracle.coherence.gradle.plugin' version coherenceVersion
}
securityConfig {
processTestClasses = false
}
Set processTestClasses = true to also generate securityConfigTest output for test classes.
Annotations
Use com.tangosol.util.function.Remote.Allowed for application classes that may legitimately cross a wire-deserialization boundary as polymorphic payloads.
At type level, @Remote.Allowed includes the annotated class and, by default, its declared nested types. Set recursive = false to include only the annotated class:
// Generates allowlist entries for MyType AND MyType.Nested.
@Remote.Allowed
public class MyType
{
static class Nested {}
}
// Generates an allowlist entry for MyType only; Nested is excluded.
@Remote.Allowed(recursive = false)
public class MyType
{
static class Nested {}
}
At package level, add @Remote.Allowed to package-info.java. The default includes classes in that package and subpackages; recursive = false limits generation to the package itself.
com.tangosol.io.pof.schema.annotation.PortableType also generates allowlist entries. A class does not need both annotations unless your source code uses both concepts for clarity.
The generated artifact is META-INF/coherence/security-config.xml. At member start, Coherence loads every such resource visible to the thread context class loader, validates each resource against coherence-security-config.xsd, and merges the allowed class names into a single runtime allowlist. A malformed or invalid resource fails member startup and names the offending URL. The schema is shipped as coherence-security-config.xsd in the Coherence jar.
A generated file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<security-config xmlns="http://xmlns.oracle.com/coherence/coherence-security-config" version="1.0">
<allowed-classes>
<class name="com.acme.billing.InvoiceFilter" source="@Remote.Allowed"/>
<class name="com.acme.billing.InvoiceProcessor" source="@Remote.Allowed"/>
<class name="com.acme.billing.model.Invoice" source="@PortableType"/>
<class name="com.acme.billing.util.MoneyExtractor" source="@Remote.Allowed(package)"/>
</allowed-classes>
</security-config>Each jar on the runtime classpath contributes its own META-INF/coherence/security-config.xml; the runtime unions all of them into a single allowlist. This means multi-module applications do not need a single curated file - each module that sends wire payload classes ships its own generated resource. A class that is not listed in any generated file and is not added via coherence.serialization.allowed is rejected at the gate.
"Wire payload classes" means more than the obvious entry processors, filters, aggregators, and POF data types. Any class whose state crosses a Coherence wire boundary is subject to the allowlist gate, including classes captured by the closure of a serialized lambda. If your code writes cache.invokeAll(filter, entry -> doSomething(entry, helper)) and helper is an instance of com.acme.PricingHelper, then PricingHelper is part of the lambda's serialized state and must be allowlisted on the receiver. The typical fix is to annotate the helper class with @Remote.Allowed or apply a package-level annotation. After upgrade, the most common security-config-missing rejections come from such capturer classes rather than from obvious payload types.
Verify the Packaged Artifact
jar tf billing-model.jar
jar tf billing-processors.jar
Each contributing JAR must contain META-INF/coherence/security-config.xml. Inspect the resource itself and confirm the expected fully qualified class names and executable or lambda-target attributes. Check the packaged JAR used by the receiving process, rather than only the build output directory.
Generate configuration as part of a normal repeatable build. Apply generation separately to each dependency module that owns payload classes; the application generator does not scan dependency JARs. A manually authored security resource must be merged into the artifact deliberately so resource copying does not overwrite generated entries.
Build and deploy the producing and receiving artifacts, restart the relevant processes, and exercise a legitimate operation. In an isolated test deployment, send an unclassified application type and confirm rejection before trusting the configured boundary. Existing compatibility traffic alone does not prove that hardened decoding will succeed.
Parent topic: Securing Serialization and Remote Execution
Classifying Remote Executable Objects
Remote executable objects are application classes whose methods Coherence may invoke on behalf of a remote request.
What Counts As An Executable
-
Processors —
InvocableMap.EntryProcessorimplementations that run against cache entries. -
Aggregators —
InvocableMap.EntryAggregatorimplementations that compute server-side aggregate results. -
Invocables —
Invocableimplementations submitted to theInvocationService. -
Filters —
Filterimplementations evaluated against cache, query, topic, REST, or gRPC data. -
Value extractors —
ValueExtractorimplementations that read values during server-side filtering, sorting, indexing, or projection. -
Comparators —
Comparatorimplementations used for server-side ordering. -
Triggers —
MapTriggerimplementations installed to run during cache mutation. -
Server-side event interceptors —
EventInterceptorimplementations that run in the cluster when matching events fire. -
coherence-concurrenttasks / callables / runnables — executable work submitted through the Coherence concurrent APIs.
Client-installed MapListeners are routed, not invoked, on the server; they do not require @Remote.Executable. A listener that additionally implements MapTrigger is governed by the MapTrigger rule.
The Annotation
@Remote.Executable
public class PriceAdjustmentProcessor
implements InvocableMap.EntryProcessor<String, Order, Void> { ... }
@Remote.Executable has CLASS retention and is class-level only. It implies @Remote.Allowed: a class that is executable is also a class the receiver may deserialize. The build-time generator emits a single entry in security-config.xml with source="@Remote.Executable" and executable="true". At runtime, the security-config reader adds that class to executableFqns() as well as allowedFqns().
XML Alternative
You can also author the same classification by hand in META-INF/coherence/security-config.xml:
<class name="com.example.PriceAdjustmentProcessor"
source="@Remote.Executable"
executable="true"/>
Runtime merge semantics are the same as generated allowlist entries: Coherence unions every visible classpath source into one view. See Generated security-config.xml for the full merge story.
InvocationService
When security hardening disables the Coherence*Extend InvocationService proxy by default, operators who intentionally expose remote invocation can set coherence.invocation.enabled=true or configure <proxy-config><invocation-service-proxy><enabled>true</enabled></invocation-service-proxy></proxy-config>. The system property overrides operational configuration when both are present.
When the proxy is enabled, every remote Invocable submitted through Extend is checked by RemoteExecutablePolicy before InvocationService.query(...) executes it and before any delegated PriorityTask scheduling, timeout, or cancellation callback can run. The invocable class must carry @Remote.Executable or appear with executable="true" in merged security-config.xml. Cluster-internal Invocable dispatch is not gated by the Extend proxy flag.
If the proxy is disabled, the server log records InvocationService proxy is DISABLED in <mode> mode by policy once at proxy startup, while the client receives the same generic service-unavailable shape used for an unbound Extend receiver. The wire response intentionally does not name the policy decision. See the Coherence security guide section "Securing Extend Client Connections" for Extend authentication setup.
MethodInvocationProcessor / ScriptProcessor
MethodInvocationProcessor and ScriptProcessor are built-in EntryProcessor implementations that execute behavior described by their serialized state. Both classes are final, annotated with @Remote.Executable, and checked by RemoteExecutablePolicy before their dynamic-remote gates run.
The shared coherence.remote.dynamic.unauthenticated policy controls unauthenticated DYNAMIC lambda bytecode, reflective method invocation through MethodInvocationProcessor, and script evaluation through ScriptProcessor. The current integration default is allow in both profiles. Set deny explicitly when those paths are not approved. The hardened profile alone does not refuse them.
MethodInvocationProcessor also applies the lambda bytecode deny-list before reflection. The supplier class, the runtime target class, and the targetClass#methodName pair are each checked against META-INF/coherence/lambda-bytecode-denylist.txt and extension resources such as META-INF/coherence/lambda-bytecode-denylist.d/*.txt. Entries such as java.lang.Runtime and java.lang.System#exit are hard floors in every security mode. When a processor targets an absent cache entry, the supplier class is checked first, but the supplier is not executed and the entry is not populated until the dynamic-remote mode gate allows MIP.
ScriptProcessor runs scripts in the singleton Graal Context with explicit host access. allowAllAccess(true) is not enabled. Host class lookup rejects deny-listed classes, and loadable deny-listed classes are also blocked through Graal HostAccess.denyAccess(...). These HostAccess checks are hard floors in every mode.
MapTrigger Remote Install and Removal
Remote MapTrigger installation and removal through Coherence*Extend are checked before the listener is registered with or removed from the storage tier. Named trigger classes must carry @Remote.Executable or appear with executable="true" in merged META-INF/coherence/security-config.xml. Missing classification rejects named classes in hardened mode and records compatibility shadow telemetry.
Dynamic trigger classes use the shared dynamic-install gate. The current implementation allows a dynamic class through that gate even when the preceding executable-class check lacks a named-class entry. An explicit coherence.remote.dynamic.unauthenticated=deny rejects the install in either profile. With the current allow default, missing executable classification alone is not a refusal for this dynamic route; serialization, lambda-target and hard bytecode checks still apply.
Classes declared in cache configuration are trusted server configuration. Coherence logs a WARN for each unannotated declared MapTrigger or EventInterceptor class, deduplicated by class name and kind, so operators can classify those classes before allowing equivalent remote installs. Event-interceptor coverage described here is a startup advisory; it does not imply a remote interceptor installation API.
Topic Subscriber Filter / Extractor Install
Remote topic subscriber group and subscriber creation through Coherence*Extend and gRPC are checked before metadata is installed on the storage tier. Subscriber filters use OperationReason.EVALUATE_FILTER; extractors and converters use OperationReason.EXTRACT; both publish role=TOPICS in telemetry. Named filter and extractor classes require @Remote.Executable or executable="true"; missing classification rejects in hardened mode and records compatibility shadow telemetry.
Coherence also walks shipped topic subscriber containers that can hide nested executable state before install and again during persisted replay. This includes filter wrappers such as ArrayFilter, ExtractorFilter, LimitFilter, and FilterWrapper; extractor wrappers such as ConditionalExtractor, CollectionExtractor, KeyExtractor, fragment extractors, and transaction wrappers; comparator wrappers such as ExtractorComparator, ChainedComparator, and SafeComparator; and script-backed subscriber filters and extractors. Each nested class is checked with topic subscriber telemetry and replay-drift handling. Customer-defined composites with opaque fields are not inspected reflectively; annotate or allowlist the composite class and any nested classes exposed through the built-in containers.
Dynamic or lambda-shaped subscriber filter and extractor classes use the same dynamic-install exception and shared policy as remote dynamic triggers. The current integration default permits them in either profile; set coherence.remote.dynamic.unauthenticated=deny explicitly to refuse their remote installation. Persisted replay has a separate class-policy drift check and does not inherit this install-time exception.
Persisted subscriber metadata is rechecked when topic subscription metadata is activated from persistence. The coherence.topics.persisted.policy-drift property controls how Coherence handles metadata whose class was valid at install time but is no longer executable at replay time:
-
rejectrefuses the replayed subscription and emitssub_reason=replay_drift. -
warn-allowemits a deduped WARN for the drifted class and allows replay.
The default is reject in hardened security mode and warn-allow in compatibility security mode. An explicit reject value fails closed in both security modes. Operators should use warn-allow only as a migration bridge while annotating the affected filter/extractor classes, regenerating security-config.xml, and redeploying the owning artifacts.
Cache-config-declared topic subscribers do not emit the same startup advisory. Verify their declared filters and extractors directly; remote installs and persisted replay rechecks still apply as described above.
Cache Data-Plane Processor / Aggregator / Filter / Extractor / Comparator Install
Remote NamedCache data-plane operations through Coherence*Extend, REST, and gRPC are checked after the wire payload or registry alias resolves to a concrete executable object and before dispatch to the storage tier. Entry processors use OperationReason.PROCESS_ENTRY; aggregators use AGGREGATE; filters use EVALUATE_FILTER; value extractors use EXTRACT; comparators use COMPARE. Telemetry publishes role=EXTEND_PROXY, role=REST, or role=GRPC depending on the transport.
Named processor, aggregator, filter, extractor, and comparator classes require @Remote.Executable or executable="true" in merged security configuration. Missing classification rejects named classes in hardened mode and records compatibility shadow telemetry. Dynamic and lambda-shaped installs use the dynamic-install exception described above. Script-backed executables also use the shared dynamic gate; deny explicitly refuses these paths in either profile. The script gate covers ScriptProcessor, ScriptFilter, ScriptValueExtractor, and ScriptAggregator, at remote install where exposed and directly before execution or delegate creation.
Coherence also walks the built-in composite containers that can hide nested executables. This includes processor wrappers such as CompositeProcessor, ConditionalProcessor, asynchronous / priority processor wrappers, ExtractorProcessor, PropertyProcessor, and UpdaterProcessor; aggregator wrappers such as CompositeAggregator, GroupAggregator, ReducerAggregator, asynchronous / priority aggregators, and TopNAggregator; filter wrappers such as ArrayFilter, ExtractorFilter, LimitFilter, MapEventTransformerFilter, WrapperQueryRecorderFilter, and transaction FilterWrapper; extractor wrappers such as ConditionalExtractor, CollectionExtractor, DeserializationAccelerator, transaction ExtractorWrapper, and the fragment extractors; and comparator wrappers such as ExtractorComparator, ChainedComparator, and SafeComparator. Comparator payloads that are also ValueExtractor instances, such as KeyExtractor, ChainedExtractor, ConditionalExtractor, CollectionExtractor, and other AbstractExtractor subclasses, are routed through the extractor cascade as well as the comparator gate; this also applies when they are wrapped by SafeComparator, InverseComparator, EntryComparator, or transaction ComparatorWrapper. ValueManipulator and ValueUpdater instances nested inside property and updater processors are treated as executable mutation state under the processor install check. Each cascaded class records its own policy telemetry event. Customer-defined composites with opaque fields are not inspected reflectively; annotate or allowlist the composite class and any nested classes that are exposed through the built-in containers above.
REST default processor and aggregator factories preflight the configured class before invoking its constructor. REST resources still check the produced processor or aggregator instance before dispatch so nested cascade checks are not lost. Custom REST ProcessorFactory and AggregatorFactory implementations remain a trusted compatibility boundary: Coherence cannot preflight arbitrary factory side effects before the custom factory chooses to construct or return an executable object.
To migrate an application, identify each rejected or would-reject class, annotate the supported remote executable type with @Remote.Executable or add an executable="true" entry to generated security config, redeploy the owning jar, and rerun the workload with -Dcoherence.security.mode=hardened. For REST registry entries, classify the registry factory output. For CohQL queries and gRPC bytes-based requests, classify the concrete filter, comparator, extractor, aggregator, or processor class that is sent by the client.
Coherence Concurrent Executor
Coherence Concurrent executor submissions are checked before task execution, scheduling, executor selection, result collection, completion callbacks, and subscriber callbacks. Named Task, Callable, Runnable, ExecutionStrategy, Task.Collector, completion predicate, completion runnable, and Task.Subscriber classes must carry @Remote.Executable or appear with executable="true" in merged META-INF/coherence/security-config.xml. Dynamic concurrent task and callback classes use the shared dynamic-install exception and policy described above. Enforcement telemetry uses OperationReason.CONCURRENT_TASK and role=CONCURRENT.
The concurrent gate also walks shipped executor wrappers that can carry nested callback state. CallableTask, RunnableTask, RunnableWithResultTask, scheduled task wrappers, and CronTask gate their nested callable, runnable, or task state after the task-manager subject is available. CronTask gates both the original and current task. StandardExecutionStrategy gates its candidate predicate, and ConditionalCollector gates its predicate and nested collector. Concurrent cache-processor wrappers such as LocalOnlyProcessor and ClusteredTaskManager.ChainedProcessor gate their nested processors before they can invoke them. Built-in predicate wrappers such as Predicates.not(...) and typed composites such as Predicates.and(...) are unwrapped and their nested predicates are checked before use. Value-style predicate wrappers gate nested payloads that could otherwise dispatch application code through equals(...) or toString(): unsafe Predicates.equalTo(...) values, non-built-in Predicates.has(...) registration options, and non-JDK Predicates.onException(...) throwables are checked before the predicate can run. Common scalar values, built-in registration options, exact product Member registration metadata, and bootstrap java.* throwables remain data-only. Crafted or non-product nested members inside Predicates.has(Member.of(...)) are checked before comparison. ValueTask is gated as a task, but its value is result data and is not treated as an executable callback merely because it implements a callback interface. Likewise, RunnableWithResultTask gates its nested runnable, but its configured result is returned as data and is not executed by remote execution policy. Deserialization safety for either result value belongs to the serialization policy. Task properties, task options, execution plans, retained results, and tracing context are data-only for this gate.
During a rolling upgrade or partition transfer, a member running with coherence.security.mode=hardened can reject a replicated concurrent task entry that was installed before that member joined if its executable classes would not pass the member's current policy. Before enabling hardened security mode on members that may own concurrent task partitions, deploy consistent classification for tasks and every nested strategy, collector, predicate, and completion callback across the cluster. Keep security mode in compatibility posture during that preparation. The coherence.mode runtime/license setting does not select this gate.
Local member submissions capture the current caller Subject as advisory gate context and serialize it with ClusteredTaskManager at POF index 20 and as a final appended ExternalizableLite field. Remote-client submissions through remote cache services, including RemoteExecutor client paths, serialize a null subject; client JVM subjects are not authorization-bearing. The subject is not used with Subject.doAs(...) and does not grant per-subject execution permission.
The ConcurrentProxy Extend service uses the existing coherence.concurrent.extend.enabled property with a security-mode-aware default: enabled in compatibility security mode and disabled in hardened security mode. A disabled proxy logs the resolved runtime mode, security mode, property/config source, and enabled state at startup, plus a startup WARN in hardened mode naming the override property. Clients still see a generic service-unavailable shape rather than policy-specific wire text. Cluster-internal executor use is independent of this proxy switch.
Functional-Interface Lambda Targets
A lambda target is the functional interface named by a serialized lambda. For example, a lambda sent as a Remote.Function declares com.tangosol.util.function.Remote$Function as its functional-interface target; the generated lambda class is a separate implementation detail.
Coherence pre-checks that functional interface before it runs the lambda bytecode scanner. The target check answers "may this receiver materialize a lambda for this API surface?" with a constant-time lookup in merged security-config.xml. The bytecode scanner still answers the separate question "does this wire-supplied bytecode contain denied references or unsupported shape?" Both checks apply; a target allowlist entry does not disable bytecode scanning.
To allow a custom serializable functional interface as a remote lambda target, annotate the interface with @Remote.Executable and ensure it also carries @FunctionalInterface:
@FunctionalInterface
@Remote.Executable
public interface PriceFunction
extends java.io.Serializable
{
BigDecimal apply(Order order);
}
The build-time scanner emits:
<class name="com.example.PriceFunction"
source="@Remote.Executable"
lambda-target="true"/>
You can also author that XML entry by hand. In that case the entry must name the functional interface, not the generated lambda implementation class.
Unannotated functional interfaces are denied in hardened security mode before lambda bytecode is materialized. In compatibility security mode, allowlist and lambda-target enforcement are disabled; use -Dcoherence.security.mode=hardened in pre-production to validate lambda-target readiness before migration. The supported migration is to annotate the functional interface or add a manual lambda-target="true" entry to the artifact that owns it.
When the target pre-check rejects a lambda, the reason is REASON_LAMBDA_TARGET_NOT_ALLOWED and the operator-visible reason string is lambda-target-not-allowed. Pre-GA adopters who previously relied on custom serializable functional interfaces should rebuild those artifacts with @Remote.Executable, verify the generated security-config.xml, and redeploy receivers before sending lambdas that target the new interface.
Migration
@Remote.Executable covers two distinct classifications:
-
Functional interfaces annotated with
@Remote.Executableare enforced today when security mode is hardened. Lambdas that target an unannotated functional interface are rejected withlambda-target-not-allowedbefore bytecode is materialized. Migrate by annotating every custom serializable functional interface used as a remote lambda target and rebuilding the artifact that owns it. See Functional-Interface Lambda Targets. -
Concrete executable classes are classified by the build. Runtime enforcement covers the remote sinks described above, with the dynamic-install exception and startup-only interceptor advisory already noted. Regenerate
security-config.xml, review executable shadow telemetry, and test the actual named and dynamic paths under the intended profile and explicit dynamic policy.
In both cases the build pipeline is the same. Rebuild every artifact that owns an executable class or functional interface; the security-config-maven-plugin and the Gradle securityConfig task pick up the annotations automatically.
Why `@Remote.Executable` And Not Just `@Remote.Allowed`?
@Remote.Allowed answers "can this class be deserialised on the server?"; @Remote.Executable answers "may the server invoke its methods on behalf of a remote request?" The latter is a strict subset and deserves an explicit opt-in so that adding a class to the deserialisation allowlist does not silently grant it execution privileges.
Classification and Caller Authorization
Executable classification applies to the code being installed or invoked. A storage authorizer applies the caller policy to the resource and operation. Configure both when the application needs both guarantees. A successful class-policy check does not assign a caller a role.
Continuous Aggregation registration, removal, and full-result queries use aggregate authorization and the corresponding remote-install policy. Partition projections use the entry-processor path. Where that feature is available, classify custom filters, extractors, aggregators, and projection functions just as for their on-demand equivalents.
Parent topic: Securing Serialization and Remote Execution
Choosing a Lambda Serialization Policy
Coherence supports dynamic and static lambda serialization. Dynamic serialization is the shipped default and carries generated bytecode to the receiver, avoiding a requirement to deploy the same compiled lambda implementation everywhere first. Static serialization resolves a compiled lambda representation already available to the receiver. Both forms remain subject to class, executable-target, and captured-state validation.
| Policy | Benefit | Deployment requirement |
|---|---|---|
| Dynamic, compatibility profile | Retains the shipped dynamic representation and compatible defaults. | Observe available diagnostics and test under hardened policy before rollout. |
| Dynamic, hardened profile | Retains dynamic deployment flexibility. The current integration default is allow; use an explicit deny when dynamic remote code is outside the deployment policy.
|
The default unauthenticated dynamic policy is deny; an explicit allow is a deliberate expansion of executable input. |
| Static | Avoids delivering a generated lambda class definition over the wire. | Make matching compiled application lambda classes available to all senders and receivers before their traffic crosses versions. |
Dynamic Remote Policy
The current implementation defaults coherence.remote.dynamic.unauthenticated to allow in both profiles during hardened-default integration. Set deny explicitly to refuse unauthenticated dynamic lambdas, reflective MethodInvocationProcessor, and script execution through the shared dynamic gate. An explicit allow permits those paths together; it does not bypass their independent validation. Do not infer dynamic default-deny from the hardened profile.
-Dcoherence.security.mode=hardened
-Dcoherence.remote.dynamic.unauthenticated=allow
The setting does not disable bytecode structure checks, forbidden class/method references, functional-interface target validation, script host-access restrictions, or storage authorization. It does not add a subject-aware permission system to dynamic code. Limit who can connect and submit requests before allowing this behavior.
Static Lambda Deployment
-Dcoherence.lambdas=static
Deploy compatible compiled application classes to every participating sender and receiver. Test rolling upgrades with the exact application versions and captured types involved. Changing representation is a deployment change; keep existing persisted subscriber metadata and older clients in the test matrix.
Captured State and Functional Targets
A captured helper, value object, or nested executable is part of the serialized object graph. Register the relevant application classes, and classify custom functional-interface targets with lambda-target="true" where required by the generated/manual security configuration. Avoid capturing an application service, credential source, or large object graph simply for convenience.
A client-side listener that is delivered events does not become a remote server executable merely because it is a listener. Server-side filters, extractors, triggers, and task callbacks have their own installation rules. Use the route descriptions in Classifying Remote Executable Objects to identify the boundary actually being exercised.
Parent topic: Securing Serialization and Remote Execution