Distributed objects, and what a stub is
◈ 5 cardsHow a method call crosses a JVM boundary: the stub as a marshalling proxy, why modern RMI needs no rmic, and the four ways a remote call can fail that a local one cannot.
A method call that leaves the JVM
Everything you have built so far moves bytes. You open a socket, agree a wire format, write lines terminated by CRLF, parse the reply. Java RMI (Remote Method Invocation) proposes the opposite trade: you keep writing ordinary Java method calls, and the network becomes an implementation detail of the call. The client holds a reference typed as an interface, calls largestPrimeInRange(1000, 2000) on it, and a long comes back. Another JVM — possibly on another machine — actually ran the method body.
Nothing here is magic, and the machinery is small enough to hold in your head at once. It is worth learning precisely because it hides the network: the parts it hides are the parts that break, and you cannot debug what you cannot picture.
The stub is a local proxy
The object the client holds is not the service. It is a stub: a local object that implements the same remote interface and does five things on every call.
- Marshal the arguments — serialise them into a byte stream.
- Open (or reuse) a TCP connection to the port the server exported the object on.
- Write an identifier for the method being called, followed by the marshalled arguments.
- Block the calling thread until a reply arrives.
- Unmarshal the return value — or rethrow the exception the server's method threw.
On the far side the RMI runtime reads the call off the wire, finds the exported object it names, invokes the method reflectively, and marshals whatever comes back. There is no skeleton class for you to write or generate: skeletons were eliminated in Java 2, and the dispatch side has been generic ever since.
Nobody runs rmic any more
Old RMI required a build step: run the rmic compiler over your implementation class to produce PrimeServiceImpl_Stub.class, then ship that file to every client. Since Java 5 the stub is a dynamic proxy (java.lang.reflect.Proxy) constructed at export time from the remote interfaces the object implements. There is no generated file and no extra build step. rmic was deprecated in Java 8 and removed from the JDK in Java 15. Any tutorial that tells you to run it is describing a JDK that no longer exists — and the assignment explicitly asks for the modern form.
Four failures a local call cannot have
- It never arrives. Nothing is listening at that address and port, and the call fails before the method exists.
- It arrives, then the server dies. The exception tells you the call failed. It does not tell you whether the method ran. Any remote operation with side effects has to be designed for that ambiguity — which is one more reason to prefer pure queries like "largest prime in this range".
- The bytes will not decode. An argument was not serialisable, or the receiving JVM has no class file for the type it was handed.
- It takes arbitrarily long. A local call returns in nanoseconds; the calling thread here is blocked on a network round trip and there is no upper bound on it.
All four surface as java.rmi.RemoteException, which is checked — which is why, in the next lesson, every method of a remote interface must declare it.
Worked example — one call, traced
The client executes one statement:
long p = primes.largestPrimeInRange(1000L, 2000L);
Two longs go out inside a call frame; one long comes back. The server scans downwards from 2000 and answers 1999. On loopback the whole exchange costs tens of microseconds; across a LAN, hundreds of microseconds to a millisecond. A local call to the same method costs single-digit nanoseconds. That is four to six orders of magnitude, and it is why the shape of the interface matters more than the speed of the code behind it.
Now picture the same job written the other way — for (long n = high; n >= low; n--) if (primes.isPrime(n)) { … }. Identical result, one thousand round trips, and a client that is now slower than doing the whole search locally. Same algorithm, same server, three orders of magnitude apart, decided entirely by where you drew the interface.