Error with Building Fatjar Using Kotlin DSL - gradle

I am trying to build a fatjar using ShadowJar. My app and gradle code are below. I'm using Gradle 5.0 for the build. When I run ./gradlew run, the code works. When I run 'gradle shadowjar', and run the fatjar using 'java -jar' in the 'build/lib' folder, I get the below error.
I'm guessing the dependencies are not getting loaded in the fatjar? I have also used Groovy to build the Gradle file, and I get the same error.
Am I correct in that I am not including all of the dependencies in the fatjar file? If that is the case, any idea on how to modify the Gradle file to make sure this is included?
Gradle Kotlin DSL
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
// Apply the Kotlin JVM plugin to add support for Kotlin on the JVM.
id("org.jetbrains.kotlin.jvm").version("1.3.21")
id("com.github.johnrengelman.shadow") version "5.0.0"
// Apply the application plugin to add support for building a CLI application.
application
}
repositories {
// Use jcenter for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
mavenCentral()
}
dependencies {
// Use the Kotlin JDK 8 standard library.
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// Use the Kotlin test library.
testImplementation("org.jetbrains.kotlin:kotlin-test")
// Use the Kotlin JUnit integration.
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
implementation("org.http4k:http4k-core:3.143.1")
implementation("org.http4k:http4k-server-jetty:3.143.1")
implementation("org.http4k:http4k-client-okhttp:3.143.1")
implementation("org.http4k:http4k-client-apache:3.143.1")
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
application {
// Define the main class for the application.
mainClassName = "com.benito.AppKt"
}
tasks.withType<ShadowJar> {
archiveBaseName.set("${project.name}-all")
}
Kotlin Code
import org.http4k.core.HttpHandler
import org.http4k.core.Method
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.routing.bind
import org.http4k.routing.path
import org.http4k.routing.routes
import org.http4k.server.Jetty
import org.http4k.server.asServer
fun main(args: Array<String>) {
println("Starting")
val app: HttpHandler = routes(
"/ping" bind Method.GET to { _: Request -> Response(OK).body("pong!") },
"/greet/{name}" bind Method.GET to { req: Request ->
val path: String? = req.path("name")
Response(OK).body("hello ${path ?: "anon!"}")
}
)
app.asServer(Jetty(8080)).start()
}
Error Message
2019-05-15 11:15:13.925:INFO::main: Logging initialized #287ms to org.eclipse.jetty.util.log.StdErrLog
2019-05-15 11:15:14.039:INFO:oejs.Server:main: jetty-9.4.z-SNAPSHOT; built: 2019-04-29T20:42:08.989Z; git: e1bc35120a6617ee3df052294e433f3a25ce7097; jvm 1.8.0_211-b12
Exception in thread "main" java.lang.ExceptionInInitializerError
at org.eclipse.jetty.http.MimeTypes$Type.<init>(MimeTypes.java:103)
at org.eclipse.jetty.http.MimeTypes$Type.<clinit>(MimeTypes.java:58)
at org.eclipse.jetty.http.MimeTypes.<clinit>(MimeTypes.java:191)
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:836)
at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:278)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:167)
at org.eclipse.jetty.server.Server.start(Server.java:418)
at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:110)
at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:113)
at org.eclipse.jetty.server.Server.doStart(Server.java:382)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
at org.http4k.server.Jetty$toServer$3.start(jetty.kt:33)
at com.benito.AppKt.main(App.kt:38)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
at org.eclipse.jetty.http.PreEncodedHttpField.<clinit>(PreEncodedHttpField.java:70)
... 14 more

The problem here is that during the process of merging all the JARs in to one FatJAR, the contents of the META-INF folder in the http4k-core JAR (which contains the mime.types file used for matching a request to a content type) is somehow omitted or overridden by the merging process.
This isn't http4k specific and is (quite) a common problem with the use of the shadow-jar gradle plugin. But it can be easily solved by just correctly configuring the plugin as below:
shadowJar {
mergeServiceFiles() // <-- this!
}
From the http4k side, the exception message generated has been changed to highlight this problem.

Related

Error while trying to get build script read the java file - Could not resolve all artifacts for configuration ':classpath'

I'm trying to follow this by adding my own custom java file into the classpath
https://github.com/gigaSproule/swagger-gradle-plugin#model-converters
This is shown in the example above
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.custom:model-converter:1.0.0'
}
}
...
swagger {
apiSource {
...
modelConverters = [ 'com.custom.model.Converter' ]
}
}
This is my code
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("com.test.app.profile.component.MyOpenApiCustomiser:1.0.0")
}
}
swagger {
apiSource {
...
modelConverters = [ 'com.test.app.profile.component.MyOpenApiCustomiser' ]
}
}
This is the error I'm getting
A problem occurred configuring root project 'profile'.
> Could not resolve all artifacts for configuration ':classpath'.
> Could not find com.test.app.profile.component.MyOpenApiCustomiser:1.0.0:.
Required by:
project :
Possible solution:
- Declare repository providing the artifact, see the documentation at https://docs.gradle.org/current/userguide/declaring_repositories.html
I tried removing 1.0.0
Caused by: org.gradle.api.IllegalDependencyNotation: Supplied String module notation 'com.test.app.profile.component.MyOpenApiCustomiser' is invalid. Example notations: 'org.gradle:gradle-core:2.2', 'org.mockito:mockito-core:1.9.5:javadoc'
Not sure how I would get my build script to the use the MyOpenApiCustomiser in my spring boot application
Is there any other way or how to fix this?
The classpath dependency given in the buildscript.dependencies {} block needs to be a external library, given in the standard group:modulde:version notation; in the example from github project it's "com.custom : model-converter : 1.0.0" ( it's a "fake" library, does not really exist in maven central repo, it's just an example)
In your case, it seems you try to refer your class MyOpenApiCustomiser as the classpath library , which cannot work. It needs to be a real library.
If you want to use your own Converter, you'll need to implement it in another library/module, publish it to a private repository and then consume it in your buildscript classpath.
Another simpler way, would be to implement this converter as a class within the buildSrc project: these classes will then be automatically available in your build script classpath, and you can use it in the apiSource configuration.
Sample:
In your buildSrc project
build.gradle
plugins {
id("java")
}
repositories {
mavenCentral()
}
dependencies {
implementation "io.swagger:swagger-core:1.6.2"
}
Your custom ModelConverter class goes under src/main/java, e.g. com.sample.MyCustomConverter
In your root build.gradle script:
You can reference your MyCustomConverter class, it's already available in the script classpath, no need to define a classpath dependency in buildscript
swagger {
apiSource {
modelConverters = [ 'com.sample.MyCustomConverter' ]
// ....

Dockerfile can not build jar file : Main class name has not been configured

I am building a dockerfile for a microservices architecture (spring) but I find some difficulties.
I got this error when buidling the boot jar
Main class name has not been configured and it could not be resolved
my dockerfile is
FROM gradle:6.5-jdk11 AS TEMP_BUILD_IMAGE
ENV APP_HOME=/usr/app/
WORKDIR $APP_HOME
COPY build.gradle.kts settings.gradle.kts $APP_HOME
COPY gradle $APP_HOME/gradle
COPY --chown=gradle:gradle . /home/gradle/src
USER root
RUN chown -R gradle /home/gradle/src
RUN gradle bootJar || return 0
COPY . .
RUN gradle clean bootJar
FROM openjdk:11-jdk
ENV ARTIFACT_NAME=app.jar
ENV APP_HOME=/usr/app/
WORKDIR $APP_HOME
COPY --from=TEMP_BUILD_IMAGE $APP_HOME/build/libs/$ARTIFACT_NAME .
ENTRYPOINT exec java -jar ${ARTIFACT_NAME}
The error is in 9th step
RUN gradle bootJar || return 0
I tried to put the name of the class in the gradle file, and i got this error
Exception in thread "main" java.lang.NoSuchMethodException: com.test.config.ConfigServerApp.main([Ljava.lang.String;)
this is my gradle file
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.springframework.boot.gradle.tasks.bundling.BootJar
plugins {
id("io.spring.dependency-management") version "1.0.9.RELEASE"
id("org.springframework.boot") version "2.3.0.RELEASE"
kotlin("plugin.spring") version "1.3.72"
kotlin("jvm") version "1.3.72"
}
repositories {
mavenCentral()
}
group = "com.test"
version = "1.0.0"
java.sourceCompatibility = JavaVersion.VERSION_11
extra["springCloudVersion"] = "Hoxton.SR5"
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.springframework.cloud:spring-cloud-config-server")
implementation("org.springframework.cloud:spring-cloud-config-monitor")
implementation("org.springframework.cloud:spring-cloud-starter-bus-amqp")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
}
dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}")
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs = listOf("-Xjsr305=strict")
}
}
tasks.withType<BootJar> {
archiveFileName.set("app.jar")
mainClassName = "com.test.config.ConfigServerApp"
}
this is my main class (i m using kotlin)
package com.test.config
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.cloud.config.server.EnableConfigServer
#EnableConfigServer
#SpringBootApplication
class ConfigServerApp
fun main(args : Array<String>)
{
runApplication<ConfigServerApp>(*args)
}
when I launch the application from my editor it works, but when I launch it from the jar file or from docker, it does not work
how can i fix this please?
Just to make a formal answer from my comment since it worked. As I explained in the comment: When we use Kotlin, the real main class is generated behind the scenes with Kt suffix, you should find it in your build folder after running gradle bootJar.
You should update the mainClassName to com.test.config.ConfigServerAppKt as shown below:
tasks.withType<BootJar> {
archiveFileName.set("app.jar")
mainClassName = "com.test.config.ConfigServerAppKt"
}
As explained in the Kotlin docs :
All the functions and properties declared in a file app.kt inside a package org.example, including extension functions, are compiled into static methods of a Java class named org.example.AppKt.
So your main method will be part of java class named ConfigServerAppKt after compilation. So you should change your mainClassName to com.test.config.ConfigServerAppKt.

IntelliJ does not pick up own spring configuration metadata

I'm having problems for IntelliJ to pickup custom spring configuration metadata with Gradle.
If I create a new Spring Boot project with the Initializer, include the Configuration Processor in the dependencies, on the Gradle task set the following tasks,
create a class with the content:
package com.example.demo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
#Component
#ConfigurationProperties("mycustomconfig")
public class MyCustomConfig {
private String name;
public String getName() {
return name;
}
public MyCustomConfig setName(String name) {
this.name = name;
return this;
}
}
then IntelliJ complains in the class file "Spring Boot Configuration Annotation Processor not found in classpath", even though it is definitely on the classpath.
After running the application, there is a file generated in build/classes/java/main/META-INF/spring-configuration-metadata.json with the following content:
{
"groups": [
{
"name": "mycustomconfig",
"type": "com.example.demo.MyCustomConfig",
"sourceType": "com.example.demo.MyCustomConfig"
}
],
"properties": [
{
"name": "mycustomconfig.name",
"type": "java.lang.String",
"sourceType": "com.example.demo.MyCustomConfig"
}
],
"hints": []
}
But IntelliJ then complains in application.properties: Cannot resolve configuration property "mycustomconfig.name".
The same experiment works flawlessly with Maven. Is there anything I'm doing wrong?
I'm using IntelliJ 2018.3 Ultimate.
My build.gradle is:
plugins {
id 'org.springframework.boot' version '2.1.3.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Finally, I've found the cause of the issue.
The annotation processor outputs the spring-configuration-metadata.json to build/classes/java/main/META-INF`.
But: IntelliJ uses a different classpath for the resolvement. Going to Project structure/Modules/main module/Paths, you can see that for the compiler output is set to "use module compile output path" and points to out/production/classes. This is resolved from Gradle automatically; changing it will be reverted once you have any changes in Gradle.
I've found that there are two possibilites:
Configure the Spring Boot Annotation Processor manually in IntelliJ Preferences/Build, Execution, Deployment/Compiler/Annotation Processors, with the following settings:
This has the benefit, that you do not need to run a complete gradle build - Just compiling from IntelliJ works. Unfortunately, every user in the project seems to manually set this up.
The second possiblity is mentioned at at this Stack overflow question. Set this idea options in Gradle:
idea{
module{
inheritOutputDirs = false
outputDir = compileJava.destinationDir
testOutputDir = compileTestJava.destinationDir
}
}
This basically now uses one compiled target class both for IntelliJ as well as Gradle. There seem to be some caveats, though, as mentioned in the linked urls.
Not sure if you solved this but I just upgraded to 2018.3 Ultimate and ran into the same problem. I suspect it is an IntelliJ issue but regardless I solved it by adding the following line
implementation('org.springframework.boot:spring-boot-configuration-processor')
Just above the "annotationProcessor" line in my gradle file...
Hope that helps
If you are using Maven, adding this dependency would resolve the issue.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

Stackoverflow error when uses Apache Jmeter Core

I am new in Gradle. I am using my gradle with kotlin dsl script. When I execute using
gradle bootRun
Then it throw StackoverFlow error for log4J
Exception in thread "main" java.lang.StackOverflowError at
java.lang.reflect.InvocationTargetException.(InvocationTargetException.java:72)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source) at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498) at
org.apache.logging.log4j.util.StackLocator.getCallerClass(StackLocator.java:110)
at
org.apache.logging.log4j.util.StackLocator.getCallerClass(StackLocator.java:123)
at
org.apache.logging.log4j.util.StackLocatorUtil.getCallerClass(StackLocatorUtil.java:55)
As Soon as I comment the implementation( "org.apache.jmeter:ApacheJMeter_core:5.0") from the build script, it doesn't throw any Error, what I should do? Please help.
Build.Gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
java
kotlin("jvm") version "1.3.11"
"kotlin-spring"
id("org.springframework.boot") version "2.1.1.RELEASE"
}
group = "org.kayd"
version = "1.0-DEV"
repositories {
mavenCentral()
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
implementation("org.apache.commons:commons-collections4:4.2")
implementation("org.springframework.boot:spring-boot-starter:2.1.1.RELEASE")
implementation( "org.apache.jmeter:ApacheJMeter_core:5.0")
// implementation("org.apache.jmeter:ApacheJMeter_java:5.0")
// implementation( "org.apache.jmeter:ApacheJMeter_http:5.0")
// implementation( "org.antlr:antlr:3.1.3")
// implementation( "org.json:json:20180813")
testCompile("junit", "junit", "4.12")
}
configure<JavaPluginConvention> {
sourceCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
Main Fun file
package org.kayd
import org.springframework.boot.Banner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.runApplication
#EnableConfigurationProperties
#SpringBootApplication
open class StartApplication
fun main(args: Array<String>) {
runApplication<StartApplication>(*args) {
setBannerMode(Banner.Mode.OFF)
setLogStartupInfo(true)
}
}
If you list your project dependencies you will see that
org.apache.jmeter:ApacheJMeter_core:5.0 has transitive dependency on commons-collections:commons-collections:3.2.2
which in its turn clashes with your org.apache.commons:commons-collections4:4.2
So you're suffering from a form of a Jar Hell. You cannot reliably use 2 different versions of a same JAR in one application as the order of loaded classes in the CLASSPATH is a big question mark. Theoretically you can control this using a custom classloader however it might be an overkill for your project.
The easiest would be just removing your implementation("org.apache.commons:commons-collections4:4.2") dependency and re-write the impacted code to use Commons Collections 3 syntax.
Alternatively you will have to reconsider the way you're using JMeter in your application and go for one of the alternative JMeter execution options.

How to force a specific dependency version in a gradle buildscript

There's an issue for the gradle-docker-plugin and SpringBootVersion 2.0.0.M4
M4 uses a newer jersey client and using the docker-plugin ends in an Exception:
ERROR com.github.dockerjava.core.async.ResultCallbackTemplate - Error during callback
java.lang.IllegalStateException: InjectionManagerFactory not found.
at org.glassfish.jersey.internal.inject.Injections.lambda$lookupInjectionManagerFactory$0(Injections.java:98)
at java.util.Optional.orElseThrow(Optional.java:290)
at org.glassfish.jersey.internal.inject.Injections.lookupInjectionManagerFactory(Injections.java:98)
at org.glassfish.jersey.internal.inject.Injections.createInjectionManager(Injections.java:68)
at org.glassfish.jersey.client.ClientConfig$State.initRuntime(ClientConfig.java:432)
at org.glassfish.jersey.internal.util.collection.Values$LazyValueImpl.get(Values.java:341)
at org.glassfish.jersey.client.ClientConfig.getRuntime(ClientConfig.java:826)
at org.glassfish.jersey.client.ClientRequest.getConfiguration(ClientRequest.java:285)
at org.glassfish.jersey.client.JerseyInvocation.validateHttpMethodAndEntity(JerseyInvocation.java:143)
at org.glassfish.jersey.client.JerseyInvocation.<init>(JerseyInvocation.java:112)
at org.glassfish.jersey.client.JerseyInvocation.<init>(JerseyInvocation.java:108)
at org.glassfish.jersey.client.JerseyInvocation.<init>(JerseyInvocation.java:99)
at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:456)
at org.glassfish.jersey.client.JerseyInvocation$Builder.post(JerseyInvocation.java:357)
at com.github.dockerjava.jaxrs.async.POSTCallbackNotifier.response(POSTCallbackNotifier.java:29)
at com.github.dockerjava.jaxrs.async.AbstractCallbackNotifier.call(AbstractCallbackNotifier.java:50)
at com.github.dockerjava.jaxrs.async.AbstractCallbackNotifier.call(AbstractCallbackNotifier.java:24)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
My BuildScript in my main project:
buildscript {
ext {
springBootVersion = "2.0.0.M4"
}
repositories {
maven { url "https://repo.spring.io/plugins-snapshot" }
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
}
}
....
As you can see, we load the spring-boot-gradle-plugin version=2.0.0.M4 and all its dependencies.
My subproject build.gradle:
apply plugin: "org.springframework.boot"
apply from: "docker.gradle"
....
Most important the docker.gradle file in the same directory as the build.gradle of the subproject:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.bmuschko:gradle-docker-plugin:3.0.11'
}
}
apply plugin: com.bmuschko.gradle.docker.DockerRemoteApiPlugin
import com.bmuschko.gradle.docker.tasks.image.*
...
task buildImage(type: DockerBuildImage, dependsOn: copyDockerFiles) {
version.release = true
dockerFile = file("${projectDir}/build/docker/Dockerfile")
inputDir = file("${projectDir}/build/docker")
tags = ['...']
}
My Questions:
How do I know which Version of the jersey client loads SpringBoot 2.0.0.M4?
How do I force gradle in docker.gradle to use a specific version of the jersey client?
Adding to the classpath didnt work. I think gradle will just use the newest version, wich will be loaded by SpringBoot 2.0.0.M4
You have to add the following in your build.gradle dependencies as pointed in: This Link
dockerJava 'com.nirima:docker-java-shaded:0.16.2'
dockerJava 'org.slf4j:slf4j-simple:1.7.5'
dockerJava 'cglib:cglib:3.2.0'
After that you have to separate "buildImage" and "tagImage" tasks as suggested in This link
I have tested this with: spring boot 2.0.0.M6
I simply did:-
dependencies {
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4+"
classpath "org.mozilla:rhino:1.7.14"
classpath ( group: 'org.apache.commons', name: 'commons-text'){
version{
strictly '1.10.0'
}
}
}

Resources