[Play 2.6.0 Scala] ClassNotFoundException as subproject is not in classloader's classpath

386 views
Skip to first unread message

chungonn

unread,
Jul 3, 2017, 3:51:24 AM7/3/17
to Play Framework
Hi,

I am trying to migrate my app from Play 2.4.6 to Play 2.6.0 and the experience is painful to say the least. Now I am faced with a showstopper, I encountered a ClassNotFoundException where the class is found in the subproject and it compiles successfully.

After a few days of tracing, I found the root of the problem is,  the subproject classes directory are not visible to ClassLoader.

Both the root and subprojects are Play scala apps and has been working since Play 2.2 to Play 2.4. To migrate to 2.6, I first migrate to Play 2.5 and ensures the project compiles successfully than I migrate to Play 2.6.

To prove that the the subproject classes directory is not visible to the class loader. I publish the subproject as a jar file and make it as a jar dependency in the root project and the ClassNotFoundException disappears.

Is there any workaround for this? Any help will be greatly appreciated.

Regards
chungonn

marcos....@lightbend.com

unread,
Jul 27, 2017, 10:56:41 AM7/27/17
to Play Framework
Hi chungonn,

Is it possible to have an example project that reproduces this problem?

Best.

Chung Onn Cheong

unread,
Jul 30, 2017, 2:21:20 AM7/30/17
to play-fr...@googlegroups.com
Hi Marcos,

Thanks for the response. 

The problem I reported will happen only during development, it is due to Play not setting ContextClassLoader to the DelegatedResouceClassLoader. My library (binary) uses Scala reflection and ContextClassLoader to instantiate an instance of the class parameter type declared at the call site. Since the ContextClassLoader is not set to DelegatedClassLoader, all the compiled classes will not be reachable and hence the ClassNotFound exception.

I tried looking at the source code and I only managed to trace till the Reloader line 436 where a new instance of DelegatedResouceClassLoader is created, however what I need is to update the contextClassLoader as well which I was not successful.

To get my project going, I have an ugly workaround, inject the a compiled class into my library for it to discover the DelegatedClassLoader.

I would appreciate if you can look into the matter. Alternatively, you can point me to the right direction where I can attempt to change the code and raise a PR

Regards
chungonn



--
You received this message because you are subscribed to a topic in the Google Groups "Play Framework" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/play-framework/CzLGIYnP8To/unsubscribe.
To unsubscribe from this group and all its topics, send an email to play-framework+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/play-framework/7aac78b1-ef64-4d18-be0b-a8ff7eb23a4b%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Rich Dougherty

unread,
Jul 30, 2017, 5:07:44 PM7/30/17
to play-framework
Hi chungonn

Can you post a stack trace? To do what you say I'd usually get the Play ClassLoader and use it explicitly when loading classes. If you use dependency injection you can get the ClassLoader from the play.api.Environment object.

Cheers
Rich

--
You received this message because you are subscribed to the Google Groups "Play Framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/play-framework/CAAWwvKejEWGqy652orN1HKyfbhsjFk5Nbk5iE2zpvxirSRjeVg%40mail.gmail.com.

For more options, visit https://groups.google.com/d/optout.



--
Rich Dougherty
Engineer, Lightbend, Inc

Amedeo Mantica

unread,
Aug 13, 2017, 5:48:40 PM8/13/17
to Play Framework
Hello,
I have a similar issue here

Caused by: java.lang.ClassNotFoundException: com.digitmovies.cinema.db.DCMovie
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at org.apache.cayenne.di.spi.DefaultAdhocObjectFactory.getJavaClass(DefaultAdhocObjectFactory.java:102)
at org.apache.cayenne.util.Util.getJavaClass(Util.java:681)
at org.apache.cayenne.map.ObjEntity.getJavaClass(ObjEntity.java:299)
at org.apache.cayenne.reflect.PersistentDescriptorFactory.getDescriptor(PersistentDescriptorFactory.java:57)
at org.apache.cayenne.reflect.ClassDescriptorMap.createDescriptor(ClassDescriptorMap.java:128)


Relevant code in DefaultAdhocObjectFactory.java:

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();  // I see that is is set to: DependencyClassLoader

    if (classLoader == null) {
        classLoader = DefaultAdhocObjectFactory.class.getClassLoader();
    }

    // use custom logic on failure only, assuming primitives and arrays are not that
    // common
    try {
        return Class.forName(className, true, classLoader);
    }
    catch (ClassNotFoundException e)


Regards
Amedeo
To unsubscribe from this group and all its topics, send an email to play-framewor...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Play Framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framewor...@googlegroups.com.

Amedeo Mantica

unread,
Aug 24, 2017, 11:11:25 AM8/24/17
to Play Framework
any help ?

Chung Onn Cheong

unread,
Aug 25, 2017, 3:53:49 AM8/25/17
to play-fr...@googlegroups.com
Hi Amedeo,

Sorry for the slow response as I am still stuck in a project.

This classloader problem will only happen under the following condition - your library (binary) trying to load a class which cannot be found from the ContextClassLoader. This will not happen in runtime as all the libraries including your project classes share the same classloader. 

In Play development environment, the compiled classes are loaded by a custom classloader and unfortunately in Play 2.6.x this custom classloader is not setup as the ContextClassLoader and thus your library will not be able to find any of your project compiled class.

Here are some possible workarounds which none is optimal
1. Expose your library as source code. This is really not feasible when you are using 3rd party library, even if you can it would take up extra time and system resources for compilation.
2. Expand your library api call to include an implicit classLoader argument. This will work for Scala only, it is still not a good idea as you will need to change the code from all the call-sites.
3. Setup the ContextClassLoader yourself. What we did is to "inject" any compiled class into our own library (binary), the library will use this compiled class' ClassLoader to load a class.

For your case, since it is written in java, you may want to create a wrapper api in your root project. Inside this api you can get the custom classloader and use it to set the ContextClassLoader and before invoking your library api.

Recently, my colleague  also found that other 3rd party in our project is having the same problem. We just leave it alone as we didn't have time to deal with it yet :-/

Hope this helps.

Regards
chungonn


To unsubscribe from this group and all its topics, send an email to play-framework+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/play-framework/b30c8d64-bddf-46de-b787-e6efd12dfd3d%40googlegroups.com.

Amedeo Mantica

unread,
Sep 8, 2017, 10:32:38 AM9/8/17
to Play Framework
Thank you, however I hope in a fix in play framework

Marcos Pereira

unread,
Sep 8, 2017, 4:01:03 PM9/8/17
to play-fr...@googlegroups.com
Hi Amedeo,

Don't use this:

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

Instead, use something like:

public class Foo {
    private final play.Environment environment;

    @Inject
    public Foo(play.Environemnt env) {
        this.environment = env;
    }

    public void bar() {
        ClassLoader loader = this.environment.classLoader();
    }
}

In other words, as Rich said before, use the classloader from Play environment.

Best.

To unsubscribe from this group and stop receiving emails from it, send an email to play-framework+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/play-framework/8fca7ff1-70e1-43d3-8627-a3f8ce5cb973%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Marcos Pereira
Software Engineer, Lightbend.com

Reply all
Reply to author
Forward
0 new messages