Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jetty's use of java.util.ServiceLoader$Provider breaks on Android (as it is not present) #8912

Open
chefchefchef opened this issue Nov 17, 2022 · 9 comments
Labels
Bug For general bugs on Jetty side Third Party Issues with third party libraries or projects

Comments

@chefchefchef
Copy link

Jetty version(s)
Jetty 11

Java version/vendor (use: java -version)
Dalvik

OS type/version
Android SDK 33

Description
With Javalin 4 (Jetty 9) and Android I had no problems. After migrating to Javalin 5 (Jetty 11) my server doesn't startup anymore. in JDK 11 I can find the missing class (ServiceLoader$Provider) but in android sdk I cannot (https://developer.android.com/reference/java/util/ServiceLoader). I use Java 11 and compileSdkVersion 33. I cannot find ServiceLoader$Provider in SDK of Android API 33 (when decompiled).

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: ch.xxx.yyy, PID: 6634
    java.lang.NoClassDefFoundError: Failed resolution of: Ljava/util/ServiceLoader$Provider;
        at org.eclipse.jetty.util.TypeUtil.serviceProviderStream(TypeUtil.java:800)
        at org.eclipse.jetty.http.PreEncodedHttpField.(PreEncodedHttpField.java:42)
        at org.eclipse.jetty.http.MimeTypes$Type.(MimeTypes.java:91)
        at org.eclipse.jetty.http.MimeTypes$Type.(MimeTypes.java:49)
        at org.eclipse.jetty.http.MimeTypes$Type.values(MimeTypes.java:47)
        at org.eclipse.jetty.http.MimeTypes.lambda$static$0(MimeTypes.java:169)
        at org.eclipse.jetty.http.MimeTypes$$ExternalSyntheticLambda4.get(Unknown Source:0)
        at org.eclipse.jetty.util.Index$Builder.withAll(Index.java:346)
        at org.eclipse.jetty.http.MimeTypes.(MimeTypes.java:166)
        at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:883)
        at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:306)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:93)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:171)
        at org.eclipse.jetty.server.Server.start(Server.java:470)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:114)
        at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:89)
        at org.eclipse.jetty.server.Server.doStart(Server.java:415)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:93)
        at io.javalin.jetty.JettyServer.start(JettyServer.kt:74)
        at io.javalin.Javalin.start(Javalin.java:171)
        at io.javalin.Javalin.start(Javalin.java:148)
        at ch.xxx.yyy.server.YYYServer.start(YYYServer.kt:237)
        at ch.xxx.yyy.server.ServerBinder.start(ServerBinder.kt:31)
        at ch.xxx.yyy.startup.ServerStateHandler$manage$1$1.invokeSuspend(ServerStateHandler.kt:30)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
        at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
    	Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@1766ac3, Dispatchers.Main.immediate]
    Caused by: java.lang.ClassNotFoundException: Didn't find class "java.util.ServiceLoader$Provider" on path: DexPathList[[zip file "/data/app/~~vM8Ki57CqJhIAaTm3OsTqw==/ch.xxx.yyy-S1y5IOg5qayhzEybJnMheg==/base.apk"],nativeLibraryDirectories=[/data/app/~~vM8Ki57CqJhIAaTm3OsTqw==/ch.xxx.yyy-S1y5IOg5qayhzEybJnMheg==/lib/arm64, /system/lib64, /system_ext/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:218)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        	... 32 more

How to reproduce?
Run Javalin 5 / Jetty 11 on Android

@joakime
Copy link
Contributor

joakime commented Nov 17, 2022

We cannot fix this.

java.util.ServiceLoader.Provider was added in OpenJDK 9.

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.Provider.html

There many components that rely on this, and Jetty is only a small part of this reliance.
This is problem outside of our control, and seems to be entirely within Android, or your use of the Android SDK.

Looking at the android source tree, their JDK 11 has the above class.
https://android.googlesource.com/platform/libcore/+/refs/tags/jdk11u/jdk-11.0.18+3/src/java.base/share/classes/java/util/ServiceLoader.java#440

@joakime joakime added the Third Party Issues with third party libraries or projects label Nov 17, 2022
@joakime
Copy link
Contributor

joakime commented Nov 17, 2022

Closing, this works for others on Android.
You have to configure your project to compile on Java 11.
This error only shows up if you haven't done that properly.

@joakime joakime closed this as completed Nov 17, 2022
@zugazagoitia
Copy link
Contributor

zugazagoitia commented Feb 22, 2023

I can't get it working any way, using JDK11 and Android SDK33.

I tested for JDK 11 using this API:
https://github.com/zugazagoitia/jett11-android13-mpoc/blob/6bda1e48c7dcaeedde9a88b9d378a5c2ddc0894d/app/src/main/java/com/example/sample/MainActivity.java#L34

You can find a minimal working example here: https://github.com/zugazagoitia/jett11-android13-mpoc

The java.lang.NoClassDefFoundError is raised as soon as a request is made. Javalin just used another Jetty class to trigger the same result earlier.

The reason, as stated earlier, is clear: the ServiceLoader class in the android SDK is missing the Provider interface used by Jetty.

My question is will this be addressed or is it a priority at all?

@sbordet
Copy link
Contributor

sbordet commented Feb 22, 2023

@zugazagoitia did you open an issue to the Android SDK to add the Provider interface?

@zugazagoitia
Copy link
Contributor

@zugazagoitia did you open an issue to the Android SDK to add the Provider interface?

I've just commented on this issue, which seems they were considering.

@joakime joakime reopened this Feb 22, 2023
@joakime joakime changed the title Android and Jetty 11 (ServiceLoader$Provider) Jetty's use of java.util.ServiceLoader$Provider breaks on Android (as it is not present) Feb 22, 2023
@zugazagoitia
Copy link
Contributor

Update: They implemented the rest of the ServiceLoader class into the main working tree. This change is however not included in Android 14 (API Level 34) [1][2], likely in a future Android 15 and/or API Level 35 release scheduled for Q3 2024.

@LukeXeon
Copy link

This works for me, you can create a new Android module and make sure its build.gradle uses Java 1.8,here we temporarily call it "ponyfill".

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

Then create a placeholder ServiceLoader type under this module (you don’t need to actually implement it), and add the Provider interface definition inside it. Android will always load the system class first when loading a class, so it It will not use the ServiceLoader we used to place the placeholder, but when it cannot find the Provider, it will use the class we provided.

package java.util;

import java.util.function.Supplier;

public final class ServiceLoader<S> {
    public interface Provider<S> extends Supplier<S> {
        Class<? extends S> type();

        S get();
    }
}

Finally, you also need to "desugar" Java in your application module

android{

    compileOptions {
        coreLibraryDesugaringEnabled true
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility JavaVersion.VERSION_11
    }

}

dependencies {
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.3'
    implementation project(":ponyfill")
}

@LukeXeon
Copy link

LukeXeon commented Oct 23, 2023

However, things are always full of compromises, and I found a case where Jetty called a method that didn't exist on Android. On Android's non-standard jdk, ServiceLoader has no stream() method no matter what version it is, and Google's "desugar" doesn't handle this, which is frustrating and causes crashes
org/eclipse/jetty/util/security/Credential.java
Ideally it would be great if Jetty also used ServiceLoaderSpliterator to implement Credential
org/eclipse/jetty/util/ServiceLoaderSpliterator.java
This is even just a piece of cake, maybe I can raise a PR if needed?

@iammmmmmm
Copy link

iammmmmmm commented Feb 17, 2024

I successfully run javalin(Depends on jetty11) 6.0.1 on Android (sdk 33)! ! ! Although it is just a hello pal, it returns as expected。
I embedded javalin in a javafx program, and then successfully packaged the apk through the maven plug-in and graalvm provided by gluon, and successfully ran it on Android 9
No special code is used, just packaged into apk with javafx

G%33TO8@Z%WTUS25 JYI3O3
image
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug For general bugs on Jetty side Third Party Issues with third party libraries or projects
Projects
None yet
Development

No branches or pull requests

6 participants