Jonathan Schneider
Jonathan Schneider

Reputation: 27727

PlayDependencyClassLoader does not find application code

When a third party dependency attempts to load a class defined in a Play application using

Class.forName(className, true, Thread.currentThread().getContextClassLoader());

Play will throw a ClassNotFoundException because the context class loader is of type PlayDependencyClassLoader which apparently only contains classes defined in jar dependencies.

Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: eventstore.Commit
    at org.mapdb.SerializerPojo.classForName(SerializerPojo.java:96)
    at org.mapdb.SerializerPojo$1.deserialize(SerializerPojo.java:74)
    at org.mapdb.SerializerPojo$1.deserialize(SerializerPojo.java:39)

This only occurs when Play is started with play run. Starting Play with play start loads the class correctly.

It would be a shame to sacrifice the class hot-swapping because of this behavior. Is there a known workaround?

Upvotes: 2

Views: 817

Answers (2)

Rich Dougherty
Rich Dougherty

Reputation: 3251

Play's HttpExecutionContext can also be used to propagate the ClassLoader across threads. The HttpExecutionContext also propagates the Http.Context thread local, if one is set.

See my answer here: How to use Http.Context.current() in a Promise in Play?

See also Play issue #2847 – classloader issues when using "run".

Upvotes: 1

Jonathan Schneider
Jonathan Schneider

Reputation: 27727

A hacky type fix for now is to wrap invocations of third party libraries that use Class.forName with a function like this:

  // see Play classloader bug https://github.com/playframework/playframework/issues/822
  def fixClassLoader[E](f: () => E) = {
    val old = Thread.currentThread().getContextClassLoader()
    try {
      Thread.currentThread().setContextClassLoader(Play.application.classloader)
      f()
    } finally {
      Thread.currentThread().setContextClassLoader(old)
    }
  }

Upvotes: 0

Related Questions