Java RMI and ClassNotFoundException
Asked Answered
B

6

12

I am just starting to learn how to use RMI, and I have a question. I have the following directory structure:

compute.jar
client
     |
     org\examples\rmi\client
                           |--> ComputePi     // client main
                           |--> Pi            // implements Task
     org\examples\rmi\compute
                           |--> Compute       // interface
                           |--> Task          // interface

server
     |
     org\examples\rmi\engine
                           |--> ComputeEngine // server main, implements Compute
     org\examples\rmi\compute
                           |--> Compute       // interface
                           |--> Task          // interface

Here's the main method in the ComputePi class:

if (System.getSecurityManager() == null) {
  System.setSecurityManager(new SecurityManager());
}
try {
  String name = "Compute";
  // args[0] = 127.0.0.1, args[1] is irrelevant
  Registry registry = LocateRegistry.getRegistry(args[0], 0);
  Compute comp = (Compute) registry.lookup(name);
  Pi task = new Pi(Integer.parseInt(args[1]));
  BigDecimal pi = comp.executeTask(task);
  System.out.println(pi);
}
catch (Exception e) {
  System.err.println("ComputePi exception:");
  e.printStackTrace();
}

Here's the main method in the ComputeEngine class:

if (System.getSecurityManager() == null) {
  System.setSecurityManager(new SecurityManager());
}
try {
  String name = "Compute";
  Compute engine = new ComputeEngine();
  Compute stub = (Compute) UnicastRemoteObject.exportObject(engine, 0);
  Registry registry = LocateRegistry.getRegistry();
  registry.rebind(name, stub);
  System.out.println("ComputeEngine bound.");
}
catch (Exception e) {
  System.err.println("ComputeEngine exception: ");
  e.printStackTrace();
}

Here's the executeTask method, also in the ComputeEngine class:

  public <T> T executeTask(Task<T> task) throws RemoteException {
    if (task == null) {
      throw new IllegalArgumentException("task is null");
    }
    return task.execute();
  }

The RMI registry and server start up just fine. Here are the params for the server:

C:\Users\Public\RMI\server>set CLASSPATH=
C:\Users\Public\RMI\server>start rmiregistry
C:\Users\Public\RMI\server>java -Djava.rmi.server.codebase="file:/C:/Users/Public/RMI/compute.jar" -Djava.rmi.server.hostname=127.0.0.1 -Djava.security.policy=server.policy org.examples.rmi.engine.ComputeEngine

Here are the params for the client:

C:\Users\Public\RMI\client>java -Djava.rmi.server.codebase="file:/C:/Users/Public/RMI/compute.jar" -Djava.security.policy=client.policy org.examples.rmi.client.ComputePi 127.0.0.1 45

However, I get the following exception when I try to run the client:

java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
        java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
        java.lang.ClassNotFoundException: org.examples.rmi.client.Pi
        at sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
        at sun.rmi.transport.Transport$1.run(Unknown Source)
        at sun.rmi.transport.Transport$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Unknown Source)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)
        at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(Unknown Source)
        at sun.rmi.transport.StreamRemoteCall.executeCall(Unknown Source)
        at sun.rmi.server.UnicastRef.invoke(Unknown Source)
        at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(Unknown Source)
        at java.rmi.server.RemoteObjectInvocationHandler.invoke(Unknown Source)
        at $Proxy0.executeTask(Unknown Source)
        at org.examples.rmi.client.ComputePi.main(ComputePi.java:38)
Caused by: java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
        java.lang.ClassNotFoundException: org.examples.rmi.client.Pi
        at sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
        at sun.rmi.transport.Transport$1.run(Unknown Source)
        at sun.rmi.transport.Transport$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Unknown Source)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: org.examples.rmi.client.Pi
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Unknown Source)
        at sun.rmi.server.LoaderHandler.loadClass(Unknown Source)
        at sun.rmi.server.LoaderHandler.loadClass(Unknown Source)
        at java.rmi.server.RMIClassLoader$2.loadClass(Unknown Source)
        at java.rmi.server.RMIClassLoader.loadClass(Unknown Source)
        at sun.rmi.server.MarshalInputStream.resolveClass(Unknown Source)
        at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
        at java.io.ObjectInputStream.readClassDesc(Unknown Source)
        at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
        at java.io.ObjectInputStream.readObject0(Unknown Source)
        at java.io.ObjectInputStream.readObject(Unknown Source)
        at sun.rmi.server.UnicastRef.unmarshalValue(Unknown Source)
        ... 11 more

But if I add the Pi.class file to the server directory:

server
     |
     org\examples\rmi\engine
                           |--> ComputeEngine // server main, implements Compute
     org\examples\rmi\compute
                           |--> Compute       // interface
                           |--> Task          // interface
     org\examples\rmi\client
                           |--> Pi            // same as Pi for client

The program works. My question is, does Pi.class really need to be on the server for my program to work? My understanding is (and please correct me if I'm wrong) that I send an instance of that class to the server, and the server would know what to do with it, i.e. it doesn't care about the implementation. Can someone explain how RMI is working in my case? I really appreciate it. Thanks!

Bootee answered 30/6, 2012 at 10:26 Comment(0)
T
8

You are trying to send a serialized object of a class that is unknown to the server.

When you execute:

  Pi task = new Pi(Integer.parseInt(args[1]));
  BigDecimal pi = comp.executeTask(task);

The server doesn't really know what is Pi. And since the Pi class is a part of your API, it should be loaded on server, too.

When I have an application that needs to execute something remotely, using for example RMI, Spring Remoting or similar, I divide my project in 3 projects: API, Server and Client. The API project will have all interfaces and model classes relevant to the functionality (this project will result in a jar, and is more or less like your computer JAR). The server will import the API JAR, will implement the interfaces and make the service available through an Remote layer (like you did with your server), and the client as you did with your client.

When you work with serialization, the class itself must be known by both sides. What is then transferred is the state of the objects in order to rebuild it on the other side.

Serialization is the mechanism used by RMI to pass objects between JVMs, either as arguments in a method invocation from a client to a server or as return values from a method invocation.

A bit of Serialization on RMI By William Grosso (October 2001). And here a bit more info.

Terzetto answered 30/6, 2012 at 10:31 Comment(5)
Note that Grosso's statement that Java arrays aren't Serializable is incorrect.Torment
Thanks so much for your response, Spaeth!Bootee
It took me a while to figure out the main implication of this answer. The idea is not to put Pi.class on the server, but to put it where the server can get it. The question I have is, why is the provided file: URL for the client codebase not good enough? Does the server attempt to retrieve the implementation class definitions on its own or does it ask the client to serve them?Celio
I'm not sure if I got your question but the thing here is to have the interface (a kind of mask) on both (client and server) and the client will basically communicate with the server when some method is invoked in a way that the server can invoke the method on the target object. The interface definition needs to be present on both (client to create the proxy, or stub, and server to have the implementation of it, that will at the end be used to instantiate the skeleton).Terzetto
@FranciscoSpaeth The codebase is used to dynamically load the class definitions themselves not the objects only. So Pi.class definition is not needed to be known on the server side in advance rather it is dynamically downloaded from the codebase defined by the client. Is not this true?Anthrax
C
13

I tried this example with two PC in same network. One with Java 1.7.0_40 working as server, another with Java 1.7.0_45 as client. Both PCs are Windows based. I met the same problem raised by denshaotoko.

The solution is:

Server side:

C:\>start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false

The -J-Djava.rmi.server.useCodebaseOnly option is needed for Java 7 since the default value is true, which means the RMI Registry will not look for other code base except the directory it is started from. Then the next step starting the server will fail. Details see here: http://docs.oracle.com/javase/7/docs/technotes/guides/rmi/enhancements-7.html

C:\>java -cp c:\rmi;c:\rmi\compute.jar -Djava.rmi.server.useCodebaseOnly=false -Djava.rmi.server.codebase=file:/c:/rmi/compute.jar -Djava.rmi.server.hostname=192.168.1.124 -Djava.security.policy=c:\rmi\server.policy engine.ComputeEngine

Again the java.rmi.server.useCodebaseOnly should be set to false. Otherwise the server won't use the codebase provided by the client. Then client side will get the class not found exception. The hostname of 192.168.1.124 is the server's IP address

You should get "ComputeEngine bound"

Client side:

C:\>java -cp c:\rmi;c:\rmi\compute.jar -Djava.rmi.server.codebase=http://54.200.126.244/rmi/ -Djava.security.policy=c:\rmi\client.policy client.ComputePi 192.168.1.124 45 

I trid the file:/ URL but not successful. I think the reason is simple. There're so many security limits that make the server not possible to access a file on the client PC. So I put the Pi.class file on my web server which is at http://54.200.126.244 under rmi directory. My web server use Apache. Any PC can access http://54.200.126.244/rmi/ so the problem is solved cleanly.

Finally, you should be able to start the rmiregistry, the server and the client from any directory using the same commands. Otherwise, some settings may still not be correct even if you can succeed. For example, if you start rmiregistry from the directory contain the "compute" directory (in my case is C:\rmi), the rmiregistry will directly load Compute.class and Task.class from it's starting directory, so the setting of -Djava.rmi.server.codebase=file:/c:/rmi/compute.jar become useless.

Compeer answered 29/11, 2013 at 17:17 Comment(1)
Wow, this just saved my day. I just ran into the same issue and specifying java.rmi.server.useCodebaseOnly=false did the trick.Cantu
T
8

You are trying to send a serialized object of a class that is unknown to the server.

When you execute:

  Pi task = new Pi(Integer.parseInt(args[1]));
  BigDecimal pi = comp.executeTask(task);

The server doesn't really know what is Pi. And since the Pi class is a part of your API, it should be loaded on server, too.

When I have an application that needs to execute something remotely, using for example RMI, Spring Remoting or similar, I divide my project in 3 projects: API, Server and Client. The API project will have all interfaces and model classes relevant to the functionality (this project will result in a jar, and is more or less like your computer JAR). The server will import the API JAR, will implement the interfaces and make the service available through an Remote layer (like you did with your server), and the client as you did with your client.

When you work with serialization, the class itself must be known by both sides. What is then transferred is the state of the objects in order to rebuild it on the other side.

Serialization is the mechanism used by RMI to pass objects between JVMs, either as arguments in a method invocation from a client to a server or as return values from a method invocation.

A bit of Serialization on RMI By William Grosso (October 2001). And here a bit more info.

Terzetto answered 30/6, 2012 at 10:31 Comment(5)
Note that Grosso's statement that Java arrays aren't Serializable is incorrect.Torment
Thanks so much for your response, Spaeth!Bootee
It took me a while to figure out the main implication of this answer. The idea is not to put Pi.class on the server, but to put it where the server can get it. The question I have is, why is the provided file: URL for the client codebase not good enough? Does the server attempt to retrieve the implementation class definitions on its own or does it ask the client to serve them?Celio
I'm not sure if I got your question but the thing here is to have the interface (a kind of mask) on both (client and server) and the client will basically communicate with the server when some method is invoked in a way that the server can invoke the method on the target object. The interface definition needs to be present on both (client to create the proxy, or stub, and server to have the implementation of it, that will at the end be used to instantiate the skeleton).Terzetto
@FranciscoSpaeth The codebase is used to dynamically load the class definitions themselves not the objects only. So Pi.class definition is not needed to be known on the server side in advance rather it is dynamically downloaded from the codebase defined by the client. Is not this true?Anthrax
S
5

My question is, does Pi.class really need to be on the server for my program to work? My understanding is (and please correct me if I'm wrong) that I send an instance of that class to the server, and the server would know what to do with it, i.e. it doesn't care about the implementation.

You understood correctly. Pi.class doesn't need to be on server when you compile it, but the server does need to download it at runtime! (Pi must be serializable)

The question is: How does a server know where to download the Pi.class when does he need it?

And the answer is: by the value of java.rmi.server.codebase setting provided by the client. The client must set the java.rmi.server.codebase option. You have to say where the Pi.class is. It is a common habit to put a copy of Pi.class in a public directory for deployment. Therefore the complete solution is:

  1. The Structure:

    compute.jar
    client\
    |-org\
    |   |-examples\
    |       |-rmi\
    |           |client\
    |               |--> ComputePi     // client main
    |               |--> Pi            // implements Task
    |-deploy\            
    |   |-org\
    |       |-examples\
    |           |-rmi\
    |               |-client\ // directory that will contain the deployment copy of Pi.class
    |--> client.policy  
    
    server\
    |-org\
    |   |-examples\
    |       |-rmi\
    |           |-engine\
    |               |--> ComputeEngine // server main, implements Compute
    |--> server.policy
    

    where compute.jar is a jar file previously created

    cd C:\Users\Public\RMI\
    javac compute\Compute.java compute\Task.java
    jar cvf compute.jar compute\*.class
    

    Did you set correctly the package and import commands in your java files? Because you modified the original structure of the tutorial...

  2. Compile the Server:

    C:\Users\Public\RMI\> cd server
    C:\Users\Public\RMI\server> javac -cp ..\compute.jar org\examples\rmi\engine\ComputeEngine.java
    
  3. Compile the Client:

    C:\Users\Public\RMI\> cd client
    C:\Users\Public\RMI\client> javac -cp ..\compute.jar org\examples\rmi\client\ComputePi.java org\examples\rmi\client\Pi.java
    
  4. Move the Pi.class into the deploy directory

    C:\Users\Public\RMI\> cp client\org\examples\rmi\client\Pi.class client\deploy
    
  5. Run the rmi registry. If you are using java 7 set the option -J-Djava.rmi.server.useCodebaseOnly=false, as suggested by muyong

    C:\Users\Public\RMI\> start rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false
    
  6. Run the server. If you are using java 7 set the option -J-Djava.rmi.server.useCodebaseOnly=false, as suggested by muyong

    C:\Users\Public\RMI\> cd server
    C:\Users\Public\RMI\server> java -cp .;..\compute.jar 
        -Djava.rmi.server.useCodebaseOnly=false 
        -Djava.rmi.server.codebase=file:/c:/Users/Public/RMI/compute.jar
        -Djava.rmi.server.hostname=127.0.0.1
        -Djava.security.policy=server.policy
        org.examples.rmi.engine.ComputeEngine
    
  7. Run the Client. NOTE: watch out the java.rmi.server.codebase setting (REMEMBER the conclusive /)

    C:\Users\Public\RMI\> cd client
    C:\Users\Public\RMI\client> java -cp .;..\compute.jar
        -Djava.rmi.server.codebase=file:/c:/Users/Public/RMI/client/deploy/
        -Djava.security.policy=client.policy
        org.examples.rmi.client.Compute.Pi 127.0.0.1 45
    

Let me know if it works!

P.s. I don't use Windows OS but Linux, I could have made confusion between '\' and '/'

Serpentiform answered 5/1, 2014 at 19:49 Comment(0)
A
3

from jdk 1.7 the default value of useCodebaseOnly is true, that mean it wont look for other codebase unless it is in the same directory.

Set this vm argument -Djava.rmi.server.useCodebaseOnly=false for running both server and client and also provide the path to codebase and hostname as well. See below below examples.

Below is my implementation and it's according to my directory structure. For running in windows replace : (colon) with ; (semi-colon).

java -cp classes:classes/compute.jar -Djava.rmi.server.useCodebaseOnly=false -Djava.rmi.server.codebase=url:http://localhost:4800/ -Djava.rmi.server.hostname=localhost -Djava.security.policy=client.policy client.ComputePi localhost 45

Apt answered 6/4, 2015 at 15:49 Comment(1)
This should be marked as the correct answer. It's also worth mentioning that Oracle's RMI tutorial omits this point, which makes it very difficult to get things working. That's why so many people are having this same problem.Perdurable
L
-1

I think the codebase that you specify for the client is not correct:

-Djava.rmi.server.codebase="file:/C:/Users/Public/RMI/compute.jar"

This will not help the server to find client.Pi .

The "Java Tutorial RMI" specifies

-Djava.rmi.server.codebase=file:/c:/home/jones/public_html/classes/

and that's the dir under which there is client/Pi.class (i.e. if one follows the tutorial, where "jones" wrote the client).

Unfortunately, even when following my own advice, meaning in my situation specifying

-Djava.rmi.server.codebase=file:/h:/rmi-example/jones/src/

when I start the client, I get the same exception as you do.

Haven't solved it yet. I'm hoping java option -verbose:class will shed some light on the problem.

Lodging answered 3/8, 2013 at 20:6 Comment(2)
I found out what caused the ClassNotFoundException for client.Pi : I started the server using java 7 , and the client using java 6 . If I start registry, client, and server, all using java 6 than it works okay.Lodging
A codebase item can be either a directiry or a JAR file.Torment
R
-2

Press Win + R shortcut keys together on your keyboard. This will open the Run dialog. ... Type the following command in the Run box: rundll32.exe sysdm.cpl,EditEnvironmentVariables

then give you current directory

Roos answered 11/3, 2020 at 12:54 Comment(1)
I don't understand this answer. If you can elaborate why we are typing the command and briefly what does the command do, it will help the community.Apt

© 2022 - 2024 — McMap. All rights reserved.