Java Remote Method Invocation: 10 - RMI Wire Protocol


10.1 Overview

The RMI protocol makes use of two other protocols for its on-the-wire format: Java Object Serialization and HTTP. The Object Serialization protocol is used to marshal call and return data. The HTTP protocol is used to "POST" a remote method invocation and obtain return data when circumstances warrant. Each protocol is documented as a separate grammar. Nonterminal symbols in production rules may refer to rules governed by another protocol (either Object Serialization or HTTP). When a protocol boundary is crossed, subsequent productions use that embedded protocol.

Notes about Grammar Notation

10.2 RMI Transport Protocol

The wire format for RMI is represented by a Stream. The terminology adopted here reflects a client perspective. Out refers to output messages and In refers to input messages. The contents of the transport header are not formatted using object serialization.

Stream:
    Out
    In

The input and output streams used by RMI are paired. Each Out stream has a corresponding In stream. An Out stream in the grammar maps to the output stream of a socket (from the client's perspective). An In stream (in the grammar) is paired with the corresponding socket's input stream. Since output and input streams are paired, the only header information needed on an input stream is an acknowledgment as to whether the protocol is understood; other header information (such as the magic number and version number) can be implied by the context of stream pairing.

10.2.1 Format of an Output Stream

An output stream in RMI consists of transport Header information followed by a sequence of Messages. Alternatively, an output stream can contain an invocation embedded in the HTTP protocol.

Out:
    Header Messages
    HttpMessage

Header:
    0x4a 0x52 0x4d 0x49 Version Protocol

Version:
    0x00 0x01

Protocol:
    StreamProtocol
    SingleOpProtocol

StreamProtocol:
    0x4b

SingleOpProtocol:
    0x4c

Messages:
    Message
    Messages Message

The Messages are wrapped within a particular protocol as specified by Protocol. For the SingleOpProtocol, there may only be one Message after the Header, and there is no additional data that the Message is wrapped in. The SingleOpProtocol is used for invocation embedded in HTTP requests, where interaction beyond a single request and response is not possible.

For the StreamProtocol the server must respond with a byte 0x4e acknowledging support for the protocol, and an EndpointIdentifier that contains the host name and port number that the server can see is being used by the client. The client can use this information to determine its host name if it is otherwise unable to do that for security reasons. The client must then respond with another EndpointIdentifier that contains the client's default endpoint for accepting connections.

For the StreamProtocol, after this endpoint negotiation, the Messages are sent over the output stream without any additional wrapping of the data.

There are three types of output messages: Call, Ping and DgcAck. A Call encodes a method invocation. A Ping is a transport-level message for testing liveness of a remote virtual machine. A DgcAck is an acknowledgment directed to a server's distributed garbage collector that indicates that remote objects in a return value from a server have been received by the client.

Message:
    Call
    Ping
    DgcAck

Call:
    0x50 CallData

Ping:
    0x52

DgcAck:
    0x54 UniqueIdentifier

Note: The obsolete Protocol MultiplexProtocol (0x4d) has been removed as of Java SE 9.

10.2.2 Format of an Input Stream

There are currently three types of input messages: ReturnData, HttpReturn and PingAck. ReturnData is the result of a "normal" RMI call. An HttpReturn is a return result from an invocation embedded in the HTTP protocol. A PingAck is the acknowledgment for a Ping message.

In:
    ProtocolAck Returns
    ProtocolNotSupported
    HttpReturn

ProtocolAck:
    0x4e

ProtocolNotSupported:
    0x4f

Returns:
    Return
    Returns Return

Return:
    ReturnData
    PingAck

ReturnData:
    0x51 ReturnValue

PingAck:
    0x53

10.3 RMI's Use of Object Serialization

Call and return data in RMI calls are formatted using the Java Object Serialization protocol. Each method invocation's CallData is written to a Java object output stream that contains the ObjectIdentifier (the target of the call), an Operation (a number representing the method to be invoked), a Hash (a number that verifies that client stub and remote object skeleton use the same stub protocol), followed by a list of zero or more Arguments for the call.

In the JDK1.1 stub protocol the Operation represents the method number as assigned by rmic, and the Hash was the stub/skeleton hash which is the stub's interface hash. As of the Java 2 stub protocol (Java 2 stubs are generated using the -v1.2 option with rmic), Operation has the value -1 and the Hash is a hash representing the method to call. The hash is described in the section "The RemoteRef Interface".

CallData:
    ObjectIdentifier Operation Hash Arguments[opt]
    ObjectIdentifier:
    ObjectNumber UniqueIdentifier

UniqueIdentifier:
    Number Time Count

Arguments:
    Value
    Arguments Value

Value:
    Object
    Primitive

A ReturnValue of an RMI call consists of a return code to indicate either a normal or exceptional return, a UniqueIdentifier to tag the return value (used to send a DGCAck if necessary) followed by the return result: either the Value returned or the Exception thrown.

ReturnValue:
    0x01 UniqueIdentifier Value[opt]
    0x02 UniqueIdentifier Exception

Note: ObjectIdentifier, UniqueIdentifier, and EndpointIdentifier are not written out using default serialization, but each uses its own special write method (this is not the writeObject method used by object serialization); the write method for each type of identifier adds its component data consecutively to the output stream.

10.3.1 Class Annotation and Class Loading

RMI overrides the annotateClass and resolveClass methods of ObjectOutputStream and ObjectInputStream respectively. Each class is annotated with the codebase URL (the location from which the class can be loaded). In the annotateClass method, the classloader that loaded the class is queried for its codebase URL. If the classloader is non-null and the classloader has a non-null codebase, then the codebase is written to the stream using the ObjectOutputStream.writeObject method; otherwise a null is written to the stream using the writeObject method. Note: as an optimization, classes in the "java" package are not annotated, since they are always available to the receiver.

The class annotation is resolved during deserialization using the ObjectInputStream.resolveClass method. The resolveClass method first reads the annotation via the ObjectInputStream.readObject method. If the annotation, a codebase URL, is non-null, then it obtains the classloader for that URL and attempts to load the class. The class is loaded by using a java.net.URLConnection to fetch the class bytes (the same mechanism used by a web browser's applet classloader).

10.4 RMI's Use of HTTP POST Protocol

The implementation of RMI calls through firewalls via proxies has been removed as of JDK 9.

10.5 Application-Specific Values for RMI

This table lists the nonterminal symbols that represent application-specific values used by RMI. The table maps each symbol to its respective type. Each is formatted using the protocol in which it is embedded.

Symbol type
Count short
Exception java.lang.Exception
Hash long
Hostname UTF
Number int
Object java.lang.Object
ObjectNumber long
Operation int
PortNumber int
Primitive byte, int, short, long ...
Time long