Skip to content

Java-only project referencing Scala 3 enums hangs compile forever: zinc JavaAnalyze dies with NoClassDefFoundError: scala/reflect/Enum and the BSP request is never answered #4361

Description

@riccardomerolla

Version(s)

  • Scala CLI 1.14.0, default Scala 3.8.3
  • Bloop v2.0.19 (Zinc v1.12.0), daemon on GraalVM CE JDK 24
  • Project pins //> using jvm 21
  • macOS 26.5.1 (arm64)

Summary

Compiling a Java-only scala-cli project (no .scala sources, no //> using scala)
whose dependency classes are Scala 3 libraries can hang scala-cli compile forever
(we observed a CI job sit 3h37m before being killed; locally it never returns).

  • javac itself succeeds — running plain javac with the same classpath compiles the
    file in ~0.5s.
  • The hang happens after javac, inside Zinc's post-compile Java analysis in the Bloop
    server: JavaAnalyze/ClassToAPI reflectively loads classes referenced by the produced
    classfiles, and dies with NoClassDefFoundError: scala/reflect/Enum — the analysis
    classloader can't see scala3-library even though it is on the project's compile
    classpath (it's a transitive dependency of the library being used).
  • The failed compile task then never answers the BSP request: the scala-cli client
    blocks in the bloop-rifle connection with 0% CPU, printing nothing after
    Compiling project (Java). There is no error, no timeout — just an eternal hang.
    The failure is only visible in the Bloop daemon log
    (~/Library/Caches/ScalaCli/bloop/daemon/output).

So there appear to be two stacked defects:

  1. Zinc/Bloop: the Java-analysis classloader used by AnalyzingJavaCompiler/
    JavaAnalyze is missing the Scala library for Java-only projects, so analyzing any
    classfile that references a Scala 3 enum (they implement scala.reflect.Enum)
    throws.
  2. Bloop (the severe one): an exception in the compile pipeline results in a
    never-completed BSP request instead of a failed compile — turning an analysis bug
    into an indefinite client hang with no diagnostics.

Reproduction (verified against public Maven Central artifacts)

Repro.java:

//> using dep "io.github.riccardomerolla:llm4zio-java:3.12.0"
//> using jvm 21
import llm4zio.javaapi.*;
import llm4zio.flow.Plan;

public class Repro {
  public static void main(String[] args) {
    Llm4zioJava.flow(args, "owner/repo#number", flow -> {
      var maybeRef = Refs.issue(flow.userPrompt());
      if (maybeRef.isEmpty()) {
        flow.fail("usage");
        return;
      }
      var ref = maybeRef.get();
      var issue = flow.stage("Read issue " + ref.shortRef(), () -> flow.gh().readIssue(ref));
      var payload = "Issue: " + issue.title();
      var planPath = flow.workDir().resolve(".llm4zio/issue-" + ref.number() + ".md");
      Plan plan;
      var existing = flow.loadPlan(planPath);
      if (existing.isPresent()) {
        plan = existing.get();
      } else {
        var assessment = flow.assessThenPlan(payload);
        if (assessment instanceof JavaAssessment.Blocked blocked) {
          flow.stage("Post assessment on the issue", () -> flow.gh().writeIssueComment(ref, blocked.reason()));
          return;
        }
        plan = ((JavaAssessment.Proceed) assessment).plan();
        flow.git().checkoutOrCreate(plan.epicId());
        flow.savePlan(planPath, plan);
      }
      System.out.println(plan.epicId());
    });
  }
}
scala-cli compile Repro.java     # prints "Compiling project (Java)" and never returns

(llm4zio-java is an ordinary Scala 3 library published for Java consumers;
JavaAssessment is a Scala 3 enum with payload cases, Plan a case class, etc.)

Expected

Either a successful compile (javac succeeds; analysis should too — scala3-library_3
is on the compile classpath), or at minimum a failed compile with the
NoClassDefFoundError surfaced to the client
. Never a silent, indefinite hang.

Actual

Client output stops after:

Compiling project (Java)

and the process waits forever (0% CPU, blocked in the bloop-rifle client; jstack of
the Bloop server shows no compile thread running). The Bloop daemon log contains:

java.lang.NoClassDefFoundError: scala/reflect/Enum
	at java.base/java.lang.ClassLoader.defineClass1(Native Method)
	at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:962)
	at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:144)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
	at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
	at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3010)
	at java.base/java.lang.Class.getDeclaredMethods(Class.java:2329)
	at sbt.internal.inc.ClassToAPI$.structure(ClassToAPI.scala:196)
	...
	at sbt.internal.inc.classfile.JavaAnalyze$.apply(JavaAnalyze.scala:108)
	at sbt.internal.inc.javac.AnalyzingJavaCompiler.$anonfun$compile$22(AnalyzingJavaCompiler.scala:216)
	at sbt.internal.inc.javac.AnalyzingJavaCompiler.compile(AnalyzingJavaCompiler.scala:208)
	at sbt.internal.inc.bloop.internal.BloopHighLevelCompiler.$anonfun$compile$11(BloopHighLevelCompiler.scala:197)
	...
Caused by: java.lang.ClassNotFoundException: scala.reflect.Enum

Control experiments

Variant Result
Same file + classpath, plain javac compiles in ~0.5s
Same file + //> using scala "3.8.3" (mixed project) compiles fine
Same file + --server=false (no Bloop) compiles fine
Trivial repro attempt: one-enum Scala 3 lib (enum Status { case Ok; case Bad(reason: String) }) + a Java file using valueOf/instanceof on it does not reproduce — compiles fine

The last row is the minimization frontier we reached: a single tiny Scala 3 dependency
doesn't trigger it, while the (larger) real dependency graph above does, deterministically
— same file hangs across fresh project dirs and Bloop daemon restarts. Whatever
ClassToAPI ends up loading in the failing case pulls in a type whose loader is missing
scala3-library.

Workarounds (for anyone else hitting this)

  • Add //> using scala "<your scala version>" to the .java file — the project becomes
    mixed and analysis has the Scala library. (This is what we ship in our examples now.)
  • Or run with --power --server=false.

Impact

Any Java-first consumer of a Scala 3 library via scala-cli (single-file Java "scripts"
against a Scala library) can hit this, and the failure mode is the worst kind: an
indefinite silent hang, locally and in CI.


Filing here since scala-cli compile is where users hit it, but the root cause looks split between Bloop and Zinc — happy for this to be moved/split as appropriate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    blocked upstreamIssues and changes blocked by something in an external project.javaIssues tied to compiling & running of Java code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions