Memra

Parameters, returns, and what actually crosses the wire

◈ 4 cards

Arguments and returns are serialized copies, remote objects travel as stubs, and therefore mutating an argument on the server is invisible to the client — plus serialVersionUID and coarse-grained design.

Everything crosses by copy

In a local call, an object argument is a reference: the callee and the caller are looking at the same object, and a mutation by one is visible to the other. In a remote call there is no shared heap, so RMI does the only thing it can — it serialises the argument, sends the bytes, and deserialises them into a brand-new object in the server's heap. Every non-primitive argument and every return value must therefore implement java.io.Serializable, and what the server receives is a deep copy: the whole object graph reachable from the argument travels with it, minus anything marked transient.

This is the single most important semantic difference between a local interface and a remote one, and it is invisible at the call site. The code compiles identically. It just means something else.

The one exception: remote objects

If an argument's class implements a Remote interface and the object has been exported, RMI does not copy it — it passes a stub. This is the mechanism behind callbacks: the client exports a listener object, passes it to the server, and the server now holds a reference it can call back across the network. It is worth knowing the rule exists even in an assignment that does not need callbacks, because it explains the whole design: Remote marks the types whose identity matters, Serializable marks the types whose value is all that matters.

Therefore mutation is invisible

An argument the server mutates is the server's private copy. Nothing propagates back. If the client needs to see a change, it must be in the return value — that is the only channel there is.

serialVersionUID, and mismatched builds

Every Serializable class has a version stamp. If you do not declare one, the JVM computes it from the class's structure — fields, methods, modifiers — so any structural edit changes it. Recompile one side only and deserialisation dies with InvalidClassException: local class incompatible. Declaring private static final long serialVersionUID = 1L; pins it, and then compatible changes (adding a field) deserialise cleanly with the missing field left at its default.

The remote interface has an equivalent trap with a much stranger message. RMI identifies a method by a hash of its signature, so if you change largestPrimeInRange(long, long) to take ints and rebuild only the server, the client's call fails with UnmarshalException: unrecognized method hash. Both traps have the same one-line fix: rebuild both sides from the same source and ship the same interface class file to each.

Design coarse-grained calls

Because every call is a round trip that can fail, the unit of remote design is the question, not the step. largestPrimeInRange(low, high) asks one question and pays one round trip. isPrime(n) in a loop asks the same question in a thousand pieces and pays a thousand. When you find yourself wanting several calls in a row, that is the signal to add one method that answers the whole thing — or to pass a small Serializable request object and get a small response object back.

Worked example — what the server actually sees

Suppose the interface took a request object instead of two bounds:

PrimeRequest req = new PrimeRequest(1000, 2000);
long p = primes.largestPrime(req);      // server sets req.note = "scanned 1001 numbers"
System.out.println(req.getNote());      // null

The server really did set the note — on its copy, which was discarded when the call returned. The client's req was never touched, so getNote() is still null. Nothing failed, nothing was logged, and the value is simply wrong. The fix is not a trick: return the note as part of the result. Whatever the client must see, return it.

argument kindwhat crossesserver holdsclient sees afterprimitivethe valuethe valueunchangedSerializable objecta deep copya new objectno changeexported Remotea stuba proxy backshared objectanything elsenothingthe call failsMarshalExceptionOnly the return value can carry a change back.
The last column is the one that surprises people: for a copied object the answer is always "no change", however much the server mutated it. The request object in the worked example is row two.
NORMAL ~/memra/learn/comp-348/rmi-parameters-by-copy utf-8 LF