While pure RMI stubs and skeletons can only communicate with other Java applications, it should be noted that ordinary CORBA applications can communicate with an RMI application which was compiled with the -iiop flag. The necessary IDL can be generated from the remote interfaces using the idlj program (in Java 2), and the rmic compiler is capable of generating CORBA stubs and ties (RMI/IIOP protocol) once the IDL exists.
In an RMI/IIOP application, interfaces are defined just as they are with RMI (by extending the Remote interface and throwing RemoteExceptions). The server doesn't extend UnicastRemoteObject or Activatable, but instead extends PortableRemoteObject. Stubs and skeletons are generated just as they would be in RMI, however the rmic compiler is given the -iiop flag, which tells rmic to use the IIOP communication protocol. Finally, all code that uses a naming service must use the CORBA naming service (typically JNDI) instead of the RMI registry. As mentioned before, the IDL can be generated from the initial remote interfaces. The IDL enables non-Java programs to send and receive messages as if the RMI/IIOP application was just any old CORBA application.
The point of this shpiel is that while RMI is clearly of a more narrow focus than CORBA, it is misleading to say that an RMI application is incapable of communicating with non-Java applications. Contrary to previous writeups on this node, RMI does not require more design considerations than any other distributed architecture (most notably CORBA). The design of classes used in a distributed application will always require some forethought, but the goal of RMI was to make a lightweight, quick, and easy-to-learn protocol for distributed application development. The sheer volume of CORBA's infrastructure guarantees that application development will require a greater design investment than with RMI.
Don't get me wrong - I'm not an RMI zealot. Significant drawbacks exist when using RMI/IIOP, and it is not a substitute for the amazing inter-operability of a CORBA application. Constant definitions in remote interfaces must be primitive types or instances of String. Names of classes or exceptions can't differ only in their case, since CORBA is not case-sensitive. RMI/IIOP doesn't handle object identity particularly well, so that if you send the same object twice, you may end up with two different objects after demarshalling. And most subtle of the caveats is the RMI/IIOP dependence on a new portion of the CORBA specification called Objects-By-Value (OBV). The OBV component of CORBA is only defined for Java and C++ (limiting your language choices) and very few vendors support OBV. If the vendor claims to be CORBA 2.3 compliant, you are probably OK on that one.
On the other hand, using RMI to build complicated systems is much easier. Because a pure RMI application is Java specific, the amount of code that needs to be written for a complex RMI application will be significantly smaller than for an equivalent CORBA application. And since RMI uses the Java serialization, it is much easier to maintain. Custom socket factories only exist in RMI (enabling the use of other wire protocols than TCP/IP). And finally, CORBA has nothing like RMI's distributed garbage collection mechanisms.
Don't be so quick to consign RMI to the bargain basement of distributed computing protocols. It isn't the universal panacea, but it certainly has its strong points.