Worked build: the prime service, and its failure modes
◈ 6 cardsAssemble A3 question 2 end to end — interface, implementation, server bootstrap, command-line client — then read the five exceptions that actually happen and know what each one means.
Stage 1 — the contract
One file, shared by both programs: PrimeService extends Remote, with long largestPrimeInRange(long low, long high) throws RemoteException, and a public static final String NAME = "PrimeService" so neither side can invent its own spelling. Compile it once and put the same .class on both classpaths.
Stage 2 — the implementation
PrimeServiceImpl implements PrimeService, and nothing else. Search downwards from high so the first prime found is the largest, stop at max(low, 2), and test each candidate by trial division to its square root. Validate first: low > high throws IllegalArgumentException. Document the empty case — no prime in range returns -1. Nothing in this class mentions the network, so you can test it in a main before RMI is involved at all, and you should.
Stage 3 — the server main
Four statements, in this order, and one field.
impl = new PrimeServiceImpl();into a static field — a strong reference that outlivesmain.PrimeService stub = (PrimeService) UnicastRemoteObject.exportObject(impl, 0);Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);registry.rebind(PrimeService.NAME, stub);then print a line saying so.
main may now return: RMI's listener threads are non-daemon, so the JVM stays up. Stop the server with Ctrl-C.
Stage 4 — the client main
Read the range from the command line — java PrimeClient 1000 2000 [host] — and default the host to localhost. Then getRegistry, lookup, cast to PrimeService, one call, print the answer. Handle -1 as "no prime in that range" rather than printing a bare -1. Everything the client does is three lines of RMI wrapped in argument parsing; if it is longer than that, the design has drifted chatty.
Stage 5 — the five failures you will actually meet
ConnectExceptionatlookup— nothing is listening on that host and port. The server is not running, or you are pointing at the wrong host. Remember thatgetRegistryis not the line that fails.NotBoundException— the registry is there, the name is not. The two sides spell it differently, which is whyNAMEis a shared constant.UnmarshalExceptioncaused byClassNotFoundException— the client's classpath has noPrimeService. The stub cannot be reconstructed without the interface it implements.ExportException: Port already in use—createRegistry(1099)while an old JVM (or anrmiregistryyou forgot about) still holds 1099. Find it and kill it; do not just move to another port and hide the problem.ConnectExceptionfrom a remote client only — the classic.InetAddress.getLocalHost()resolved to127.0.0.1because of an/etc/hostsline, so the stub carries127.0.0.1, and a client on another machine dutifully connects to itself. The fix is-Djava.rmi.server.hostname=<the address other machines can reach>on the server command line. Localhost testing never reveals it.
Worked example — the acceptance run
Start the server in one terminal; it prints its bound name and stays there. In another, run the client for 1000 2000 and expect 1999. Then run the tests that matter more than the happy path: a range with no prime (8 10 → the documented message), a reversed range (2000 1000 → the IllegalArgumentException arriving at the client as the cause of a ServerException), and the client with the server stopped (ConnectException). Four runs, each one exercising a row of the table below — that is your test plan, and A3 asks for one in writing.