0

I received a Scala jar and need to write a java program to use a method. The imported Scala method definitely works because it has been in production for years.

here is the program:

import com.company.VerifyApp // The imported Scala jar

public class Validation {
    public static void main(String[] args) {
        VerifyApp app = new VerifyApp();
        app.main(args);
    }
}

Decompiled Scala code:

package com.company;

public final class VerifyApp {
    public static void main(String[] var0) {
        VerifyApp$.MODULE$.main(var0);
    }
}

Here are observations:

  • Error message: cannot find symbol constructor VerifyApp()
    • location: class com.company.VerifyApp
    • Just a note that this location is correct
  • VerifyApp, from the decompiled code, is a final class with no constructor (thus should have a default 0-arg constructor)
  • I'm using IntelliJ and activated the "Include dependencies with "Provided" scope" already
  • This is not a maven project, I just load the jar as external library
  • When editing, IntelliJ shows no error or warning
  • I checked tons of Stackoverflow posts with the same topic but couldn't find anything useful

I'm wondering what could be the issue?

Edit: IntelliJ warned me I'm instantiating a utility class and this mostly likely is an error. Since I'm a Java newbie, I Googled a bit and found out that maybe I could directly call VerifyApp.main(args) without instantiating the object. This DOES pass the build, but I got a runtime error:

NoClassDefFoundError, caused by ClassNotFoundException

Now I'm out of trick.

Nicholas Humphrey
  • 1,220
  • 1
  • 16
  • 33
  • See [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). There's no Scala code here, and with only an `import` to go on, it could be "the file doesn't exist", "your classpath is wrong", or any number of basic configuration details. What does the Scala code in question look like? – Silvio Mayolo Jan 17 '22 at 03:18
  • @SilvioMayolo it's an imported jar. I can show the decompiled code, just a moment. – Nicholas Humphrey Jan 17 '22 at 03:21
  • @SilvioMayolo done, added decompiled code – Nicholas Humphrey Jan 17 '22 at 03:24
  • 1
    It must be a local classpath issue with the way you're invoking the JAR. If I create those two `.java` files verbatim with the correct directory structure (and the missing semicolon on the `import`) then everything works. So whatever is happening is local to your dev environment. – Silvio Mayolo Jan 17 '22 at 03:30
  • @SilvioMayolo Just a note I got a warning "Instantiation of utility class VerifyApp()". I Googled a bit and found that utility class is usually created with a private consturctor, I guess that could also be the reason (but not sure why it doesn't show up in the decompiled code) – Nicholas Humphrey Jan 17 '22 at 03:33
  • 1
    Have you tried: `VerifyApp$.MODULE$.main(args);` or something along these lines? https://lampwww.epfl.ch/~michelou/scala/using-scala-from-java.html. If it's originally defined in a object App.main then you can call it directly: `App.main(args)`. – yǝsʞǝla Jan 17 '22 at 03:33
  • @yǝsʞǝla yeah I tried, seems to be private so I can't call those. – Nicholas Humphrey Jan 17 '22 at 03:35
  • main must be public to be an entry point – yǝsʞǝla Jan 17 '22 at 03:37
  • 1
    Check when you run it, does it include `scala-library.jar` on classpath. – yǝsʞǝla Jan 17 '22 at 03:39
  • 1
    BTW this is a valid question, so I don't know why people try to downvote or close it. Let's keep it alive. It's a valid problem of Java/Scala interop. – yǝsʞǝla Jan 17 '22 at 03:41
  • @yǝsʞǝla I cannot run it at the moment, it doesn't pass Build. BTW IntelliJ indicates VerifyApp is a utility class, does this ring a bell? I Googled a bit and looks like I should be able to directly call main() using: `VerifyApp().main(args)`, sadly it doesn't work either. It does pass Build but shows another error: NoClassDefFoundError. BTW it does have the jar on classpath. – Nicholas Humphrey Jan 17 '22 at 03:45
  • I would suggest to create a toy example without that JAR. You can build your own JAR so you would be sure about the `main` method and how to call it. – yǝsʞǝla Jan 17 '22 at 03:47
  • 1
    I would just add that JAR and Scala JAR to the libraries in IntelliJ and try to compile/run. I'm not sure what is a utility class. Usually all libraries come as JARs and that's all we need. – yǝsʞǝla Jan 17 '22 at 03:49
  • @yǝsʞǝla I created a toy example and has no issue calling the main(). I don't know Scala so probably I didn't understand method calling correctly. Thanks for the help, I'll speak to the jar author tomorrow. – Nicholas Humphrey Jan 17 '22 at 03:52
  • 1
    To summarize use `VerifyApp().main(args)`, add Scala and your App JAR to libraries, this will put them on classpath. It should be able to compile and run. – yǝsʞǝla Jan 17 '22 at 03:52
  • 1
    This is how `main` is defined: https://docs.scala-lang.org/overviews/scala-book/hello-world-1.html or alternatively: https://docs.scala-lang.org/overviews/scala-book/hello-world-2.html – yǝsʞǝla Jan 17 '22 at 03:54
  • @yǝsʞǝla thanks! Actually, I'll try to learn Scala basics quickly and maybe just create a Scala project. Doesn't seem to be a huge effort just for a few lines... – Nicholas Humphrey Jan 17 '22 at 04:05
  • 1
    Yes if you want to just call Scala code, doing it from Scala will be a little more straightforward. You can even make a small `build.sbt` and build your project with SBT and import into IntelliJ as SBT project (requires Scala plugin). In that case you can just copy JAR into `lib` dir. See my answer here: https://stackoverflow.com/questions/25129227/how-to-include-an-external-jar-file-into-the-jar-with-package. – yǝsʞǝla Jan 17 '22 at 04:07
  • @yǝsʞǝla thanks. I managed to build a minimum Scala project and pinpointed the issue. The "NoClassDefFOundError" is caused by org/apache/hadoop/fs/FSDataInputStream. It's probably a dev environment issue and I'm missing some libs. I'll contact the author tomorrow. – Nicholas Humphrey Jan 17 '22 at 04:44

1 Answers1

2

Answering the original question, the reason for the error "cannot find symbol constructor VerifyApp()" has to do with a difference between Java and Scala.

It is true that the decompiled Java code, if re-compiled, would have a default, public, zero-argument constructor. This is a feature of the Java compiler: it inserts this default constructor into the compiled bytecode if no constructor is declared. Note that the bytecode interpreter itself does not insert a default constructor at runtime if it isn't present in the compiled bytecode.

In the original Scala source code, this was defined as a singleton object. It would have looked something like this:

object VerifyApp {
  def main(args: Array[String]): Unit =
    ???
}

I don't know what the actual method body looks like, because it would have been compiled into a class called VerifyApp$, which you can see referenced in the decompiled source code in the question.

In general, the Scala compiler compiles object definitions by:

  • Creating a class with the name of the object followed by $, containing instance methods for methods defined on the object, a private constructor, and a static, final MODULE$ field holding an instance of the class.
  • Creating a class with the plain name of the object containing static forwarder methods that invoke the matching method on that MODULE$ instance, and no constructor.

You can see this by using the javap program, for example:

javap -p -c com/company/VerifyApp.class com/company/VerifyApp$.class

You'll get output similar to this:

Compiled from "VerifyApp.scala"
public final class com.company.VerifyApp {
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #17                 // Field com/company/VerifyApp$.MODULE$:Lcom/company/VerifyApp$;
       3: aload_0
       4: invokevirtual #19                 // Method com/company/VerifyApp$.main:([Ljava/lang/String;)V
       7: return
}
Compiled from "VerifyApp.scala"
public final class com.company.VerifyApp$ {
  public static final com.company.VerifyApp$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class com/company/VerifyApp$
       3: dup
       4: invokespecial #12                 // Method "<init>":()V
       7: putstatic     #14                 // Field MODULE$:Lcom/company/VerifyApp$;
      10: return

  public void main(java.lang.String[]);
    Code:
       0: getstatic     #22                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: invokevirtual #26                 // Method scala/Predef$.$qmark$qmark$qmark:()Lscala/runtime/Nothing$;
       6: athrow

  private com.company.VerifyApp$();
    Code:
       0: aload_0
       1: invokespecial #29                 // Method java/lang/Object."<init>":()V
       4: return
}

The usual way to access these objects from Java is using the static forwarder methods without trying to instantiate the class. In this case, VerifyApp.main(args) (as you discovered).

In the rare case that you do need to access the instance directly (for example, when it implements an interface that you need an instance of), you need to use the unsightly VerifyApp$.MODULE$ reference. It can also be assigned to another static final field of a Java class or interface to provide a more readable name.

Tim Moore
  • 8,958
  • 2
  • 23
  • 34