Kotlin Gradle Could not find or load main class - spring-boot

I tried to copy the Spring Boot Kotlin sample project https://github.com/JetBrains/kotlin-examples/tree/master/tutorials/spring-boot-restful. I Added some more dependencies and when I tried to build the executable jar and run it, I got the error:
Could not find or load main class...
Gradle build script:
buildscript {
ext.kotlin_version = '1.1.3' // Required for Kotlin integration
ext.spring_boot_version = '1.5.4.RELEASE'
repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // Required for Kotlin integration
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#kotlin-spring-compiler-plugin
classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_version"
}
}
/*plugins {
id 'org.springframework.boot' version '2.0.0.RELEASE'
}*/
apply plugin: 'kotlin' // Required for Kotlin integration
apply plugin: "kotlin-spring" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#kotlin-spring-compiler-plugin
apply plugin: 'org.springframework.boot'
jar {
baseName = 'gs-rest-service'
version = '0.1.0'
from {
(configurations.runtime).collect {
it.isDirectory() ? it : zipTree(it)
}
}
manifest {
attributes 'Main-Class': 'org.jetbrains.kotlin.demo.Applicationkt'
}
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin/'
test.java.srcDirs += 'src/test/kotlin/'
}
repositories {
jcenter()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // Required for Kotlin integration
compile("org.springframework.boot:spring-boot-starter-web")
compile group: 'org.apache.camel', name: 'camel-quartz2', version: '2.20.2'
compile group: 'org.apache.camel', name: 'camel-http4', version: '2.20.2'
compile group: 'org.apache.camel', name: 'camel-docker', version: '2.20.2'
compile group: 'org.apache.camel', name: 'camel-aws', version: '2.20.2'
testCompile('org.springframework.boot:spring-boot-starter-test')
}

Change Applicationkt to ApplicationKt will work, and BTW you may upgrade Kotlin version to 1.3.50.
By Applicationkt I mean the one in this line:
attributes 'Main-Class': 'org.jetbrains.kotlin.demo.Applicationkt'

Kotlin compiles the Application file in two different files:
one file called Application.class with the Springboot things
another file called ApplicationKt.class with the main method
In this second file is where the main function is located at, so you have to use this name in the build.gradle file.
mainClassName = 'org.jetbrains.kotlin.demo.ApplicationKt'

Update your build.gradle to
jar {
manifest {
attributes 'Main-Class': 'org.jetbrains.kotlin.demo.ApplicationKt'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
with an upper case K in ApplicationKt.
This is required because of the way Kotlin compiles to Java Bytecode. The fun main() function in Kotlin is not attached to any class, but Java always requires a class and does not support classless functions.
The Kotlin compiler has to create a Java class. Because you already defined a class Application it created one with the suffix Kt for the functions in your Kotlin file org/jetbrains/kotlin/demo/Application.kt. You have to set this class so that the JVM can find it.
BTW a Jar file is just a Zip file, you can unpack it and see for yourself if the ApplicationKt.class is there.

For me the main function needed to be outside the class body
#SpringBootApplication
#Configuration
class Application
(private val locationRepository: LocationRepository,
) : CommandLineRunner {
override fun run(vararg args: String?) {
whatever()
}
}
fun main(args: Array<String>) {
runApplication<Application>(*args)
}

Indeed, Kotlin create file ApplicationKt.class in the jar if your main class file is named Application.kt. You have to add the following lines:
apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName = 'org.jetbrains.kotlin.demo.ApplicationKt'
If you use the classic jar plugin, you can do as below (which is described in previous responses):
jar {
manifest {
attributes 'Main-Class': 'org.jetbrains.kotlin.demo.ApplicationKt'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
However, my preference is to use bootJar plugin which is much clear and which allow me to use layered jars for example:
bootJar {
layered() // Not useful if you don't want to use layered jars
}

Related

I am getting a gradle build issue when updating gradle from 4.10.1 to 6.4.1

I am getting the following issue in Jenkins when updating gradle;
Execution failed for task ':xxx:extractModuleInfo'.
06:38:45 > Artifact xxxxx.jar wasn't produced by this build.
I have been researching on the issue and I think it's known issue when updating gradle.
Please refer to the section Publishing Spring Boot Applications in this link:
https://docs.gradle.org/current/userguide/upgrading_version_6.html
Here is the buildgradle of the application:
import java.time.format.DateTimeFormatter
import java.time.ZonedDateTime
apply plugin: 'org.springframework.boot'
apply plugin: 'war'
def checkpoint = version
description 'Spring Boot / MVC web application (controllers for SEAR services, etc)'
springBoot {
// Generates build info and will be used in /info endpoint
buildInfo()
}
/*
bootRun {
addResources = false
systemProperties = System.properties
main = 'creditcard.lifecycle.CreditCardApplication'
jvmArgs = [ "-Djavax.net.ssl.trustStore=..//cacerts"]
}
*/
dependencies {
implementation project(':xx')
implementation project(':xx')
implementation project(':xxxx')
implementation project(':xxxxxxxxxxxxxxxxxxxxxx')
implementation project(':xxxxxxxxxxxxxxxxxxxxxxx')
implementation project(':xxxxxxxxxxxxxxx')
implementation project(':xxxxxxxxxxxxxxxx')
implementation project(':xxx')
implementation project(':xxxxx)
implementation project(':xxxxx')
implementation project(':xxxxx')
implementation project(':xxxxx')
implementation project(':xxxxx')
implementation libraries.wf_retrofit
implementation libraries.commons_lang3
implementation ('org.springframework.boot:spring-boot-starter-web') {
exclude module: "spring-boot-starter-tomcat"
exclude module: "tomcat-embed-core"
exclude module: "tomcat-embed-el"
exclude module: "tomcat-embed-websocket"
exclude module: "tomcat-annotations-api"
}
implementation ('org.springframework.boot:spring-boot-starter-tomcat:1.5.8.RELEASE')
implementation ('wf.authx:authx:0.9+')
implementation libraries.jackson
implementation libraries.searj
implementation libraries.commonscodec
implementation ('org.springframework.boot:spring-boot-starter-actuator')
//include schema after it is built
//compile group: 'wf.ebs', name: 'schemas', version: '2018.1-SNAPSHOT'
runtimeOnly 'org.springframework.boot:spring-boot-starter-undertow'
}
configurations {
compile.exclude module: 'spring-boot-starter-tomcat'
compile.exclude group: 'org.apache.tomcat'
compile.exclude module: "tomcat-embed-el"
compile.exclude module: "searj-authx-spring-boot-starter"
}
task createCheckpointFile {
doLast {
//Need to pass variables as jenkins parameters
//def checkpoint = "xxxx_${Release}.${BUILD_NUMBER}"
println "Checkpoint = ${checkpoint}"
def TODAY_DT_US = ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z"))
String fileContent = "BUILD_CHECKPOINT=${checkpoint}\nBUILD TSTAMP: ${TODAY_DT_US} \nBUILD_DIR=$projectDir"
new File("${buildDir}/xxxxx.checkpoint").write(fileContent)
}
}
/*
jar {
from file("${buildDir}/xxxx.checkpoint")
baseName = 'xxxxxx'
manifest {
attributes("Implementation-Title": "xxxx",
"Implementation-Version": "$project.version")
}
}
*/
//bootRepackage.enabled = false
//jar.dependsOn createCheckpointFile
war {
enabled = true
archiveFileName = 'xxxx.war'
//copy-move
webInf { from("${buildDir}/xxxx.checkpoint") }
}
war.dependsOn createCheckpointFile
How is that components feature adding that check to the jar?
Add/modify jar task as below in build.gradle
jar {
enabled = true
}
This should fix the issue

Create custom plugin that defines plugins and dependencies

My organization uses the same Gradle plugins and dependencies for a lot of our projects. My custom plugin knowledge is pretty weak, but what I'd like to do is wrap these plugins and dependencies into a single, standalone plugin. I'm stuck on understanding how to separate the plugins/dependencies required for the plugin versus the ones that I want to use in the consuming project. Here's a simple example that I put together based on the gradle custom plugin docs, and some information about storing the plugin in a maven repo to allow it to automatically download dependencies:
// build.gradle from standalone plugin
plugins {
id 'java-gradle-plugin'
id 'maven-publish'
// these ones I don't need in the plugin, just in the project where I apply the plugin
id 'war'
id 'org.springframework.boot' version '2.2.4.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'org.asciidoctor.convert' version '1.5.8'
}
group = 'org.sample'
version = '1.0.0'
publishing {
repositories {
maven {
url "../maven-repo"
}
}
}
gradlePlugin {
plugins {
greeting {
id = "org.sample.greeter"
implementationClass = "org.sample.GreetingPlugin"
}
}
}
dependencies {
implementation gradleApi() // I think I need this for the imports in GreetingPlugin.java
implementation localGroovy() // I think I would need this if GreetingPlugin was written in Groovy
// these ones I don't need in the plugin, just in the project where I apply the plugin
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test' {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.junit.jupiter:junit-jupiter-engine'
}
// this is only needed in the project where I apply the plugin
// I believe this should be in the GreetingPlugin.java file though
test {
useJUnitPlatform()
}
and the backing class...
package org.sample;
import org.gradle.api.DefaultTask;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskAction;
class Greeting Plugin implements Plugin<Project> {
#Override
public void apply(Project project) {
project.getTasks().create("hello", MyTask.class);
}
public static class MyTask extends DefaultTask {
#TaskAction
public void myTask() {
System.out.println("Hello, World!");
}
}
}
In the project I'm trying to consume the plugin, I have the following files:
// settings.gradle
pluginManagement {
repositories {
maven {
url "../maven-repo"
}
gradlePluginPortal()
}
}
// build.gradle
plugins {
id 'org.sample.greeter' version '1.0.0'
}
My thinking is that using the plugin in this way, the project inherits the plugins and dependencies listed in the plugin code. I think I'm close, as when I ./gradlew publish I can see the plugin being applied, but it doesn't like that the spring-starter-web dependency doesn't have a version (I know that when I do a multi-project gradle repo, I need to include the dependencyManagement block with mavenBOM, so maybe that's the key?) I'm trying to follow the SpringBoot gradle plugin for insight, but it's a bit too complicated for me.
So, is this the correct way to create a standalone plugin that includes plugins/dependencies baked in? And why isn't the spring dependency manager applying the versioning?
EDIT: I followed the link from #Alan Hay, and instead of a custom plugin, I tried to use the 'apply from'. However, it still doesn't work. Here's files based on that approach:
// build.gradle from 'parent' build.gradle
plugins {
id 'war'
id 'org.springframework.boot' version '2.2.4.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'org.asciidoctor.convert' version '1.5.8'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test' {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.junit.jupiter:junit-jupiter-engine'
}
test {
useJUnitPlatform()
}
and attempting to reference from another project, it's the only line in the file:
apply from: '<path-to-above>/build.gradle'
This error I get is the following:
script '<path-to-above>/build.gradle': 15: Only Project build scripts can contain plugins {} blocks
See https://docs.gradle.org/5.5.1/userguide/plugins.html#sec:plugins_block for information on the plugins {} block
# line 15, column 1.
plugins {
^
1 error
A standalone, binary plugin is the preferred approach when you need to share the same build logic across multiple independent projects. Additionally, good plugin design separates capabilities from convention. In this case, the capabilities are provided by Gradle and some third-party plugins, but you're adding your own conventions on top in this plugin.
When you're implementing this, you essentially need to push the code down one level. Anything that would be configuration in the build.gradle needs to be in your plugin's source code. Anything that would impact the classpath of the buildscript (i.e. buildscript { } or plugins { }) belongs in the dependencies of your plugin. The plugins { } block in your plugin should only have the build plugins required the build the plugin itself.
// build.gradle from standalone plugin
// plugins {} should contain only plugins you need in the build of the plugin itself
plugins {
id 'java-gradle-plugin'
id 'maven-publish'
}
group = 'org.sample'
version = '1.0.0'
dependencies {
implementation gradleApi()
// Dependencies for plugins you will apply to the target build
implementation 'io.spring.gradle:dependency-management-plugin:1.0.9.RELEASE'
implementation 'org.asciidoctor:asciidoctor-gradle-jvm:2.4.0'
implementation 'org.springframework.boot:spring-boot-gradle-plugin:2.2.4.RELEASE'
}
gradlePlugin {
plugins {
greeting {
id = "org.sample.greeter"
implementationClass = "org.sample.GreetingPlugin"
}
}
}
publishing {
repositories {
maven {
url "../maven-repo"
}
}
}
package org.sample;
import org.gradle.api.DefaultTask;
import org.gradle.api.Plugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.testing.Test;
class Greeting Plugin implements Plugin<Project> {
#Override
public void apply(Project project) {
// Apply plugins to the project (already on the classpath)
project.getPluginManager().apply("war");
project.getPluginManager().apply("org.springframework.boot");
project.getPluginManager().apply("io.spring.dependency-management");
project.getPluginManager().apply(" org.asciidoctor.convert");
// Dependencies that you need for the code in the project that this plugin is applied
DependencyHandler dependencies = project.getDependencies();
dependencies.add(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME, "org.springframework.boot:spring-boot-starter-web");
dependencies.add(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME, "org.junit.jupiter:junit-jupiter-engine");
dependencies.add(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME, springBootStarterTest(dependencies));
projects.getTasks().withType(Test.class, test -> {
test.useJUnitPlatform();
});
}
private Dependency springBootStarterTest(DependencyHandler dependencies) {
Map<String, String> exclude = new HashMap<>();
exclude.put("group", "org.junit.vintage");
exclude.put("module", "junit-vintage-engine");
return ((ModuleDependency) dependencies.module("org.springframework.boot:spring-boot-starter-test")).exclude(exclude);
}
}
This is more verbose due to being written in Java, but it is functionally equivalent to putting this in your project's build.gradle:
plugins {
id 'war'
id 'org.springframework.boot' version '2.2.4.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'org.asciidoctor.convert' version '1.5.8'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test' {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.junit.jupiter:junit-jupiter-engine'
}
test {
useJUnitPlatform()
}

java.lang.NoClassDefFoundError in Intellij Plugin

I'm trying to build a plugin for Intellij but I'm getting a java.lang.NoClassDefFoundError at runtime every time my code point to a class in another module or to an external library.
Everything works fine in my tests and in the sandbox via runIde.
I also managed to reproduce the error by creating a new project with just an action and a module with a class and an empty method.
root gradle:
buildscript {
ext.kotlin_version = '1.2.31'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
plugins {
id 'org.jetbrains.intellij' version '0.3.12'
}
group 'test'
version '1.0-SNAPSHOT'
apply plugin: 'kotlin'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile project(':testmodule')
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
intellij {
version '2018.1.6'
}
patchPluginXml {
changeNotes """
Add change notes here.
most HTML tags may be used"""
}
action:
package action
import com.intellij.openapi.actionSystem.*
import packages.OtherModuleClass
class TestAction : AnAction() {
override fun actionPerformed(e: AnActionEvent?) {
OtherModuleClass().otherModuleMethod()
}
}
other module class:
package packages
class OtherModuleClass {
fun otherModuleMethod() {}
}
I found out what my problem was.
I was installing in my IDE the jar in build/libs instead of the zip in build/distributions.
If someone else is looking for a solution for similar problem, I fixed it by adding the java plugin in build.gradle
apply plugin "java"

how to create fat jar which does not overwrite entries under META-INF/services

I have the following gradle build config:
plugins {
id 'com.github.johnrengelman.shadow' version '1.2.3'
}
group 'abc'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'application'
mainClassName = "abc.Driver"
repositories {
mavenCentral()
}
dependencies {
compile (group: 'org.apache.hadoop', name: 'hadoop-client', version: '2.6.0')
}
sourceSets {
main {
java {
srcDir './src'
}
}
}
jar {
manifest {
attributes(
'Class-Path': configurations.compile.collect { it.getName() }.join(' '),
'Main-Class': mainClassName
)
}
}
task fatJar(type: Jar) {
manifest {
attributes 'Implementation-Title': 'Gradle Jar File Example',
'Implementation-Version': version,
'Main-Class': mainClassName
}
baseName = project.name + '-all'
from { (configurations.compile - configurations.provided).collect
{
//println it.getName()
it.isDirectory() ? it : zipTree(it)
}
}
{
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
with jar
}
The main method that I have is just the following piece of code:
public static void main(String[] args) {
Iterable<ClientProtocolProvider> frameworkLoader =
ServiceLoader.load(ClientProtocolProvider.class);
for(ClientProtocolProvider cpp: frameworkLoader) {
System.out.println(cpp.toString());
}
}
When I run the main method from IDE as expected I get the following output:
org.apache.hadoop.mapred.YarnClientProtocolProvider#4783da3f
org.apache.hadoop.mapred.LocalClientProtocolProvider#300ffa5d
But when I run the gradle fat jar task and I create the fat jar, after running the main method using (java -jar) through terminal I just get:
org.apache.hadoop.mapred.LocalClientProtocolProvider#7f31245a
I found that when fat jar is created, the entries under META-INF/services are merged for all dependencies and hence I lose the declaration for YarnClientProtocolProvider which I need further in my code.
YarnClientProtocolProvider is declared in hadoop-mapreduce-client-jobclient.jar
LocalClientProtocolProvider is declared in hadoop-mapreduce-client-common.jar
Does any body know how to create a fat jar which does not merges entries under META-INF/services?!
This should do the work
shadowJar {
mergeServiceFiles()
}

Why do I need to specify 'from files'?

I was trying to build a jar out of my first groovy script. My project structure is as follows:
- build.gradle
- src\main\groovy\app\Test.groovy
My original gradle script:
apply plugin: 'groovy'
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.3.11'
testCompile group: 'junit', name: 'junit', version: '4.11'
}
sourceSets.main.groovy.srcDirs = ["src/main/groovy"]
jar {
manifest {
attributes('Main-Class': 'app.Test')
}
}
From the guides I read, this should create a runnable jar. When I try to run it though I always get the error
Error: Could not find or load main class app.Test
I found out now that I need to add these two lines to the jar task:
from files(sourceSets.main.output.classesDir)
from configurations.runtime.asFileTree.files.collect { zipTree(it) }
The weird thing is that if I replace the groovy script with a Test.java class (same content), I don't need those two extra lines to run the jar.
I couldn't find out why I need them or what exactly they do. Can anyone explain that, or offer a documentation link?
I'm new to SO, please help me with my mistakes.
EDIT
The code suggested by tim_yates is translated to test.jar with the following content:
META-INF/MANIFEST.MF
Manifest-Version: 1.0
Main-Class: app.Test
app/Test.class
package app;
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class Test implements GroovyObject {
public Test() {
CallSite[] var1 = $getCallSiteArray();
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
}
public static void main(String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[0].callStatic(Test.class, "Hi!");
}
}
I execute with the following statement:
java -jar test.jar
Which results in the error message stated above.
You've got to remember that this jar contains Groovy compiled classes. The answer is in your decompiled source that you showed in the beginning. It imports Groovy runtime classes.
If you just run java -jar test.jar those classes are not on the classpath.
Either include groovy on the classpath of your command line or use the gradle application plugin to build a fat JAR (which is probably better for runnable jars) that contain all your application dependencies.
apply plugin: 'groovy'
apply plugin: 'application'
mainClassName='app.Test'
repositories {
mavenCentral()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.7'
testCompile 'junit:junit:4.12'
}
task uberjar(type: Jar,dependsOn:[':compileJava',':compileGroovy']) {
from files(sourceSets.main.output.classesDir)
from configurations.runtime.asFileTree.files.collect { zipTree(it) }
manifest {
attributes 'Main-Class': mainClassName
}
}
Then build your jar with gradle uberjar
Assuming Test.groovy looks something like:
package app
class Test {
static main(args) {
println "Hi!"
}
}
Then you only need the following build script:
apply plugin: 'groovy'
repositories {
mavenCentral()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.7'
testCompile 'junit:junit:4.12'
}
jar {
manifest {
attributes('Main-Class': 'app.Test')
}
}

Resources