ServiceLoader do not work in packaged Spring Boot apps - java

(copy from my GitHub issue: https://github.com/spring-projects/spring-boot/issues/22955)
I noticed that Java's ServiceLoader mechanism doesn't work in packaged Spring Boot apps.
Background
I've tried to use javax.script.ScriptEngineManager which relies on ServiceLoaders. I was able to successfully launch the app from the IDE but not from the command line.
Repro
// build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '2.3.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
}
tasks.withType(JavaCompile) {
options.release.set(11) // required Gradle >= 6.6
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.python:jython-slim:2.7.2'
}
// Main.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.script.ScriptEngineManager;
import java.util.Objects;
#SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
var engine = new ScriptEngineManager().getEngineByName("python");
Objects.requireNonNull(engine);
System.out.println("success");
}
}
It is possible to launch this from the IDE (IntelliJ in my case) but not via the command line:
gradle bootJar && java -jar build/libs/XXX.jar
Temporary workaround
Instead of using the ScriptEngineManager it is possible to directly use Jython's ScriptEngine implementation:
var engine = new org.python.jsr223.PyScriptEngineFactory().getScriptEngine();

The ServiceLoader mechanism is correctly finding PyScriptEngineFactory. The problem is then a silent failure when it attempts to create a script engine from it. Unfortunately, when you call getEngineByName(String), ScriptEngineManager swallows any exception thrown by getScriptEngine():
try {
ScriptEngine engine = spi.getScriptEngine();
engine.setBindings(getBindings(), ScriptContext.GLOBAL_SCOPE);
return engine;
} catch (Exception exp) {
if (DEBUG) exp.printStackTrace();
}
Your work around doesn't work for me, but that's useful as it allows me to see the exception swallowed by ScriptEngineManager. It is the following:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:109)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:58)
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:88)
Caused by: java.lang.IllegalArgumentException: URI is not hierarchical
at java.io.File.<init>(File.java:418)
at org.python.core.PrePy.getJarFileNameFromURL(PrePy.java:427)
at org.python.core.PrePy._getJarFileName(PrePy.java:362)
at org.python.core.PrePy.getJarFileName(PrePy.java:345)
at org.python.core.PySystemState.doInitialize(PySystemState.java:1195)
at org.python.core.PySystemState.initialize(PySystemState.java:1130)
at org.python.core.PySystemState.initialize(PySystemState.java:1085)
at org.python.core.PySystemState.initialize(PySystemState.java:1080)
at org.python.core.PySystemState.initialize(PySystemState.java:1075)
at org.python.core.PySystemState.initialize(PySystemState.java:1070)
at org.python.core.PySystemState.<init>(PySystemState.java:207)
at org.python.util.PythonInterpreter.threadLocalStateInterpreter(PythonInterpreter.java:80)
at org.python.jsr223.PyScriptEngine.<init>(PyScriptEngine.java:27)
at org.python.jsr223.PyScriptEngineFactory.getScriptEngine(PyScriptEngineFactory.java:85)
at com.example.demo.Gh22955Application.main(Gh22955Application.java:11)
... 8 more
PrePy is making some assumptions about jar:file: URLs that don't hold true in a Spring Boot fat jar. Spring Boot provides an escape hatch for this, allowing a jar file to be automatically unpacked from the fat jar when it's launched. In this case, it's the jython-slim jar that needs to be unpacked. To do that, add the following to your build.gradle:
bootJar {
requiresUnpack "**/jython-slim*"
}

Related

Spring Application's build file cannot execute by java.lang.NoClassDefFoundError

what happend and What I want to do
I made a Springboot Project with spring initializr (https://start.spring.io/).
But I cannot executed built jar file with this error.
java -jar build/libs/spring302-0.0.1-SNAPSHOT-plain.jar
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
at com.kaede.spring302.Spring302ApplicationKt.main(Spring302Application.kt)
Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
... 1 more
What should I do ?
I want to do this because I need to build to Docker Container.
How I made this project
I selected
Kotlin
Gradle 7.2
Spring 3.0.2
Java 17
And I used IntelliJ to build.
Project Name is spring302
NameSpace is com.kaede
1. Tried to Add Manifest Main-Class
java -jar build/libs/kotlin1-0.0.1-SNAPSHOT-plain.jar
Error: Could not find or load main class
First, I tried to just build, then happend this error.
tasks.withType<Jar> {
manifest {
attributes["Main-Class"] = "com.kaede.spring302.Spring302Application"
}
}
So I added Manifest Main-Class in spring302/build.gradle.kts
java -jar build/libs/spring302-0.0.1-SNAPSHOT-plain.jar
Error: Main method not found in class com.kaede.spring302.Spring302Application,
please define the main method as:
public static void main(String[] args)
Then I got it is not Java Main Method Error.
2. Changed Main-Class to classNameKt
I changed Main-Class from Spring302Application to Spring302ApplicationKt
I heard Kotlin class will change classNameKt.java.
tasks.withType<Jar> {
manifest {
attributes["Main-Class"] = "com.kaede.spring302.Spring302ApplicationKt"
}
}
So I changed my Main-Class .
java -jar build/libs/spring302-0.0.1-SNAPSHOT-plain.jar
Exception in thread "main" java.lang.NoClassDefFoundError:
kotlin/jvm/internal/Intrinsics
at com.kaede.spring302.Spring302ApplicationKt.main(Spring302Application.kt)
Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
... 1 more
Then I got NoClassDefFoundError in Spring302ApplicationKt.main.
javap spring302-0.0.1-SNAPSHOT-plain/com/kaede/spring302/Spring302ApplicationKt.class
Compiled from "Spring302Application.kt"
public final class com.kaede.spring302.Spring302ApplicationKt {
public static final void main(java.lang.String[]);
}
Even my output has main.
3. Removed .m2/repository and rebuild
rm -rf ~/.m2/repository
I thought dependencies are complicated, So I removed all and re-build.
But things did not change.
what I expect
I can execute jar file
4. Changed Main Method from runApplication to SpringApplication.run
In main
runApplication<Spring302Application>(*args)
to
SpringApplication.run(Spring302Application::class.java, *args
But NoClassDefFoundError does not change
build.gradle.kts
This is my full dependencies.
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "3.0.2"
id("io.spring.dependency-management") version "1.1.0"
kotlin("jvm") version "1.7.22"
kotlin("plugin.spring") version "1.7.22"
}
group = "com.kaede"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.withType<Jar> {
manifest {
attributes["Main-Class"] = "com.kaede.spring302.Spring302ApplicationKt"
}
}

How do I configure my Eclipse project to use the log4j 1.2 to 2.16 bridge

I am trying to use the log4j 1.2 to 2.16 bridge - log4j-1.2-api-2.16.0.jar
I thought that by using the bridge jar I would not have to change my code.
I am using these three jars in my classpath:
log4j-1.2-api-2.16.0.jar
log4j-core-2.16.0.jar
log4j-api-2.16.0.jar
In the Eclipse project properties Java Build Path, Libraries tab, I see:
Modulepath
>log4j-1.2-api-2.16.0.jar - Log4jBridgeExample/lib
>log4j-api-2.16.0.jar - Log4jBridgeExample/lib
>log4j-core-2.16.0.jar - Log4jBridgeExample/lib
>JRE System Library [JavaSE-9]
Classpath
Here is the the one class definition.
package test;
import org.apache.log4j.Logger;
public class MyClass {
//Here we use old log4j syntax like exists in thousands
//of other classes in our other projects.
private static Logger logger = Logger.getLogger(MyClass.class);
public static void main(String[] args) {
logger.info("Howdy");
}
}
When I run it, I get the following runtime error:
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/logging/log4j/Logger
at Log4jBridgeExample/test.MyClass.<clinit>(MyClass.java:6)
Caused by: java.lang.ClassNotFoundException: org.apache.logging.log4j.Logger
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 1 more

Java Project Build and run problem with FX

i'm trying run a project with this architecture...
And, when i press "Run" button the code gives me this error...
Exception in Application start method
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:465)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:364)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1071)
Caused by: java.lang.RuntimeException: Exception in Application start method
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:901)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:196)
at java.base/java.lang.Thread.run(Thread.java:832)
Caused by: java.lang.IllegalAccessError: class com.sun.javafx.fxml.FXMLLoaderHelper (in unnamed module #0x709a3ba3) cannot access class com.sun.javafx.util.Utils (in module javafx.graphics) because module javafx.graphics does not export com.sun.javafx.util to unnamed module #0x709a3ba3
at com.sun.javafx.fxml.FXMLLoaderHelper.<clinit>(FXMLLoaderHelper.java:38)
at javafx.fxml.FXMLLoader.<clinit>(FXMLLoader.java:2135)
at ui.Main.start(Main.java:32)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:847)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:484)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
... 1 more
Exception running application ui.Main
My code of my Main...
package ui;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import model.FIBAManager;
import java.io.IOException;
public class Main extends Application {
private static final String FOLDER = "fxml/";
private FIBAManager manager;
private MainGUIController MGC;
private EmergentGUIController EGC;
public Main() throws IOException {
manager = new FIBAManager();
EGC = new EmergentGUIController(manager);
MGC = new MainGUIController(manager, EGC);
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage window) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(FOLDER + "MainWindow.fxml"));
fxmlLoader.setController(MGC);
Parent root = fxmlLoader.load();
Scene scene = new Scene(root, null);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
window.setScene(scene);
window.setTitle("");
window.show();
}
}
And i have this run cofiguration:
--module-path "C:\Users\Giova\Downloads\javafx-sdk-17.0.0.1\lib" --add-modules=javafx.controls
I need help to solve this problem and run the project please
You are using fxml so you need to add the fxml module, otherwise it will not be visible for use:
--add-modules javafx.controls,javafx.fxml
See the openjfx.io documentation titled Run HelloWorld using JavaFX SDK.
Example execution command for Linux from that source (modified to add the javafx.fxml module):
java --module-path $PATH_TO_FX --add-modules javafx.controls,javafx.fxml HelloFX
Alternately, you can define a module-info.java file which requires javafx.fxml and any other modules required for your application. See: Understanding Java 9 modules.
module <your module name> {
requires javafx.controls;
requires javafx.fxml;
opens <your package> to javafx.fxml;
exports <your package>;
}
Substituting your info for:
module <your module name> any name you want, but best to put it as the name of the primary package of your app, e.g. com.example.javafx.myapppackage. Follow Java 9 module naming conventions.
opens <your package> to javafx.fxml a package which uses fxml (opens is required to allow the fxml package to reflect on your code).
exports <your package> a package you want to make visible (via export) to users of your application's module code; e.g. the package which contains the class in your appliction with a main(args) method.
Or, you can ignore the module system using the hack provided in mipa's answer, as mipa does have a very good point about the additional complexity involved in using the module system. It is not a supported configuration by the JavaFX developers, but will likely work OK for your app.
Just append this line to the file containing your Main class.
class MainLauncher {public static void main(String[] args) {Main.main(args);}}
Then put all your dependencies on the classpath (also JavaFX) and in your command line remove everything that is related to modules. Then launch the main from the MainLauncher class and be happy. You'll see a little warning. Just ignore it. This module stuff is simply unnecessarily complicated.
There is a jdk created by Liberica that has JavaFX integrated into it so you can compile with javac and run with java. Make sure you download the "Full" JDK. This eliminates a lot of headaches.

How to correctly use shadowJar while deploying a Ktor + Exposed backend?

Here's my shadowJar task
shadowJar {
archiveName "server.jar"
mainClassName = "myapp.ApplicationKt"
manifest {
attributes(
'Class-Path': project.configurations.compile.collect { it.getName() }.join(' ')
)
}
minimize {
// exclude(dependency('.*:.*:.*'))
}
from sourceSets.main.output
}
Note the commented exclude line removing any dependency from being minimized (effectively disabling shadowJar). When I uncomment this, then the resulting jar works. However, there seems to be some problematic dependency that shadowJar's minimize chops away that is actually loaded at runtime:
Here's the error that I keep getting:
2020-02-09 16:39:01.843 [main] INFO ktor.application - No ktor.deployment.watch patterns specified, automatic reload is not active
Exception in thread "main" java.util.ServiceConfigurationError: org.jetbrains.exposed.sql.DatabaseConnectionAutoRegistration: Provider org.jetbrains.exposed.jdbc.ExposedConnectionImpl not found
at java.util.ServiceLoader.fail(ServiceLoader.java:239)
at java.util.ServiceLoader.access$300(ServiceLoader.java:185)
at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:372)
at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:404)
at java.util.ServiceLoader$1.next(ServiceLoader.java:480)
at kotlin.collections.CollectionsKt___CollectionsKt.firstOrNull(_Collections.kt:224)
at org.jetbrains.exposed.sql.Database.<clinit>(Database.kt:64)
at myapp.DB.init(DB.kt:23)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:293)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:137)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:257)
at io.ktor.server.netty.NettyApplicationEngine.start(NettyApplicationEngine.kt:126)
at myApp.ApplicationKt.main(Application.kt:35)
at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$1.invokeSuspend(IntrinsicsJvm.kt:199)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
at kotlin.coroutines.jvm.internal.RunSuspendKt.runSuspend(RunSuspend.kt:19)
I use Exposed database library with mysql driver. Here's how I'm connecting to exposed database
Database.connect(
driver = "com.mysql.cj.jdbc.Driver",
I believe the problem is somewhere around this. It's not able to find the database driver at the runtime.
I've tried the obvious: Excluding exposed, sql, mysql-connector-java dependencies to no avail.

Class EmailAddressCriteria not found (SimpleJavaMail)

I try to use the SimpleJavaMail library, but I think I missed something when importing the JAR of the API and it's dependencies. I use Java 8 (openjdk), and here is a list of the external JARs I added in my Eclipse Oxygen project configuration :
slf4j-api-1.7.13.jar (the 1.7.13 version seems to be the version of SLF4J used in SimpleJavaMAil since version 2.5.1 according to its GitHub)
slf4j-simple-1.7.13.jar
javax.mail.jar version 1.6.1
simple-java-mail-5.0.3.jar
Here is a code sample:
import org.simplejavamail.email.Email;
import org.simplejavamail.email.EmailBuilder;
import org.simplejavamail.mailer.Mailer;
import org.simplejavamail.mailer.MailerBuilder;
import org.simplejavamail.mailer.config.TransportStrategy;
public class Main {
public static void main(String[] args) throws Exception {
Email notif = EmailBuilder.startingBlank()
.to("someone#somewhere.fr")
.withSubject("Bla")
.withPlainText("Lorem ipsum\nLorem ipsum")
.buildEmail();
Mailer mailer = MailerBuilder
.withSMTPServer("smtp.gmail.com", 587, "foo#gmail.com", "bar")
.withTransportStrategy(TransportStrategy.SMTP_TLS)
.withSessionTimeout(10 * 1000)
.clearEmailAddressCriteria() // turns off email validation
.withDebugLogging(true)
.buildMailer();
mailer.sendMail(notif);
}
}
The exception I have running that code trought Eclipse on my Linux is:
Exception in thread "main" java.lang.NoClassDefFoundError: org/hazlewood/connor/bottema/emailaddress/EmailAddressCriteria
at org.simplejavamail.mailer.MailerGenericBuilder.<init>(MailerGenericBuilder.java:152)
at org.simplejavamail.mailer.MailerBuilder$MailerRegularBuilder.<init>(MailerBuilder.java:136)
at org.simplejavamail.mailer.MailerBuilder.withSMTPServer(MailerBuilder.java:49)
at Main.main(Main.java:18)
Caused by: java.lang.ClassNotFoundException: org.hazlewood.connor.bottema.emailaddress.EmailAddressCriteria
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 4 more
Any idea on how to fix this?
Looking at Maven Central's pom.xml for simple-java-mail-5.0.3 you are missing the following dependency
<dependency>
<groupId>com.github.bbottema</groupId>
<artifactId>emailaddress-rfc2822</artifactId>
<version>1.0.1</version>
<scope>compile</scope>
</dependency>
I think you should use a build tool like Maven or Gradle to set up your project instead of adding the JAR files manually to avoid problems with missing dependencies.

Categories