Kotlin Application with Gradle application plugin - gradle

I am trying to create a simple HelloWorld application using kotlin, gradle, and the gradle application plugin. When I run it with the below setup I get the following error:
Error: Main method is not static in class com.petarkolaric.helloworld.Main, please define the main method as:
public static void main(String[] args)
My build.gradle:
group 'helloworld'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.2.0'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName = "com.petarkolaric.helloworld.Main"
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
My src/main/kotlin/com/petarkolaric/helloworld/Main.kt:
package com.petarkolaric.helloworld
class Main {
fun main(args : Array<String>) {
println("Hello, World!")
}
}
According to this blog post I should be able to use the application plugin this way.
What do I need to change to allow the application plugin to run my main function when I run gradle run?

As the error says, your main method is not static. You have the following options:
1) Put the main into the companion object and make it JvmStatic
class Main {
companion object {
#JvmStatic
fun main(args : Array<String>) {
println("Hello, World!")
}
}
}
2) Change your class to object - than you more or less have a singleton class and make it JvmStatic
object Main {
#JvmStatic
fun main(args : Array<String>) {
println("Hello, World!")
}
}
3) Place the main outside the class
fun main(args : Array<String>) {
println("Hello, World!")
}
class Main {
}

Related

MapStruct not injected in Kotlin project

I am trying to create a project with current Kotlin, MapStruct and Java using Spring-Boot folowing some online examples, as I am new to MapStruct, however I am not able to inject the mapper into my service. Both Idea and Gradle (in build task test) complain that no bean has been found (UnsatisfiedDependencyException). Googling didn't help. What am I missing?
MWE:
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.7.4"
id("io.spring.dependency-management") version "1.0.14.RELEASE"
kotlin("jvm") version "1.7.20"
kotlin("plugin.spring") version "1.7.20"
kotlin("plugin.jpa") version "1.7.20"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17
configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
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")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
testImplementation("org.springframework.boot:spring-boot-starter-test")
implementation("org.mapstruct:mapstruct:1.5.3.Final")
annotationProcessor("org.mapstruct:mapstruct-processor:1.5.3.Final")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
DemoAppllication.kt
package com.example.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
#SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
App.kt
package com.example.demo
import org.mapstruct.Mapper
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
import javax.persistence.*
#Entity
#Table(name = "items")
class Item(#Id var id: Int = 0)
data class ItemDto(val id: Int)
#Repository
interface ItemRepo : JpaRepository<Item, Int>
#Mapper(componentModel = "spring")
interface ItemMapper {
fun entToDto(item: Item) : ItemDto
fun entsToDtos(items: List<Item>) : List<ItemDto>
fun dtoToEnt(itemDto: ItemDto) : Item
}
#Service
class Srvc(private val itemMapper: ItemMapper, // XXX: no bean found for this one
private val repo: ItemRepo)
{
fun items() = itemMapper.entsToDtos(repo.findAll())
}
// controller skipped
Kapt is in maintenance mode! See example: https://github.com/mapstruct/mapstruct-examples/tree/main/mapstruct-kotlin
plugin {
// ...
kotlin("kapt")
// ...
}
dependencies {
// ...
kapt("org.mapstruct:mapstruct-processor:$version")
implementation("org.mapstruct:mapstruct:$version")
// ...
}
EDIT (by mpts.cz — for future me or anyone as new to Mapstruct and/or Gradle as I am):
use plugin kotlin("kapt")
replace annotationProcessor("org.mapstruct:mapstruct-processor:$version")
with kapt("org.mapstruct:mapstruct-processor:$version")
put the previous line before implementation("org.mapstruct:mapstruct:$version")
With these changes all seems to work smoothly. Thanks Numichi!

Compilation fails using Gradle and Kotlin

I am going through the examples in Kotlin in Action book. The gradle buid script is as follows:
group 'kotlin-in-action'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.1.2-2'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin'
repositories {
mavenCentral()
}
apply plugin: 'java'
dependencies {
testCompile 'junit:junit:4.12'
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "junit:junit:4.12"
compile 'junit:junit:4.12'
compile 'junit:junit:4.12'
}
sourceSets {
main.kotlin.srcDirs += 'src'
}
All the scripts compile except for two classes that uses junit.
package ch06.ex1_8_1_LateinitializedProperties
import org.junit.Before
import org.junit.Test
import org.junit.Assert
class MyService {
fun performAction(): String = "foo"
}
class MyTest {
private var myService: MyService? = null
#Before fun setUp() {
myService = MyService()
}
#Test fun testAction() {
Assert.assertEquals("foo",
myService!!.performAction())
}
}
The compiler says it can't find junit. I have tried adding the jar files in IntelliJ, but this has not resolved the problem. The jar files I have added are junit and hamscrest-core. This is all version 4.12.
I have resolved this. I had to add the Junit and hamcrest-core jar files from the mavan depository.

Gradle build with fatJar plugin and SpringBoot application gives 'Application startup failed'

Everything was working fine when starting my app using Intellij. But when I made fatJar (with gradle plugin: eu.appsatori.fatjar) and execute:
java -jar myapp.jar
I'm getting something like this:
11:41:01.224 [main] ERROR org.springframework.boot.SpringApplication - Application startup failed
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [my.testing.MyAppMain]; nested exception is java.lang.IllegalArgumentException: No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.
at org.springframework.context.annotation.ConfigurationClassParser.processDeferredImportSelectors(ConfigurationClassParser.java:482)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:184)
...
It looks like it didn't found auto configuration classes in META-INF/spring.factories.
How to add this file? And what should be the content of it?
I've got following build script:
apply plugin: "java";
apply plugin: "idea";
apply plugin: 'application'
apply plugin: 'eu.appsatori.fatjar'
apply plugin: 'org.springframework.boot'
repositories {
mavenCentral()
}
buildscript {
ext {
springBootVersion = '1.4.3.RELEASE'
}
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
classpath "eu.appsatori:gradle-fatjar-plugin:0.3"
}
}
sourceSets {
main {
java {
srcDir 'src/main/java'
}
resources {
srcDir 'src/main/resources'
}
}
test {
java {
srcDir 'src/test/java'
}
}
}
fatJar {
manifest {
attributes("Main-Class": 'my.testing.MyAppMain')
}
exclude 'META-INF/*.DSA'
exclude 'META-INF/*.SF'
exclude 'META-INF/*.RSA'
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-jdbc'
runtime 'mysql:mysql-connector-java'
testCompile 'org.springframework.boot:spring-boot-starter-test'
}
And my example code is:
package my.testing;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
#SpringBootApplication
public class MyAppMain {
private ConfigurableApplicationContext springContext;
#Autowired
private SimpleDao dao;
public static void main(String[] args) throws Exception {
MyAppMain test = new MyAppMain();
try {
test.init();
test.doWhatYouGotToDo();
} finally {
test.stop();
}
}
private void doWhatYouGotToDo() {
System.out.println("Auto-wired dao: " + dao.hashCode());
System.out.println("Auto-wired jdbcTemplate: " + dao.jdbcTemplate.hashCode());
}
private void init() throws Exception {
springContext = SpringApplication.run(MyAppMain.class);
springContext.getAutowireCapableBeanFactory().autowireBean(this);
}
private void stop() throws Exception {
springContext.close();
}
}
#Component
class SimpleDao {
#Autowired
JdbcTemplate jdbcTemplate;
}
application.properties file:
spring.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/some_db?useSSL=false&serverTimezone=UTC
spring.datasource.username = some_user
spring.datasource.password = some_pass
NOTE: This question is based on SpringBoot - making jar files - No auto configuration classes found in META-INF/spring.factories
where are all answers are referring to building with Maven. Please put only answers related to Gradle here.
Although I mostly use Maven for Spring and Gradle for Android, but here is the gradle way for a Spring project:
gradle clean build
gradle bootRepackage
Result:
Here is my build.gradle file:

Using Gradle Dependency inside of Custom Plugin

I am trying to write a custom Gradle Plugin that invokes the flyway migration using their API:
https://flywaydb.org/documentation/api/
This is a minimal example:
buildscript {
repositories.jcenter()
dependencies.classpath "org.flywaydb:flyway-core:4.1.2"
}
apply plugin: DatabaseHandlerPlugin
class DatabaseHandlerPlugin implements Plugin<Project> {
void apply(Project project) {
project.task("databaseHandler").doLast {
org.flywaydb.Flyway f = new Flyway(); // <= How can I use the above declared dependency here and in my projects?
}
}
}
But my gradle complains that it cannot load the Flyway class.
The Flyway class is in the org.flywaydb.core package. You missed the core bit. My full code that works:
import org.flywaydb.core.Flyway; // << can import here
buildscript {
repositories { mavenCentral() }
dependencies {
classpath "org.flywaydb:flyway-core:4.1.2"
}
}
apply plugin: DatabaseHandlerPlugin
class DatabaseHandlerPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('databaseHandler') {
doLast {
Flyway f = new Flyway()
println "Flyway: $f"
}
}
}
}
Output:
> gradle databaseHandler
:databaseHandler
Flyway: org.flywaydb.core.Flyway#7b27e8f4

accessing the spring-restdocs generated content in a spring boot application

I'm using Spring Restdocs (v1.1.2) in a Spring Boot (v1.4.1) application.
In the jar task of the Gradle build file, I'm copying the generated output into public/docs:
jar {
dependsOn asciidoctor
from ("${asciidoctor.outputDir}/html5") {
into 'public/docs'
}
}
and I see in the generated JAR the document in
BOOT-INF/classes/public/docs/api-guide.html
However, when I run the JAR, I can't seem to address the api-guide.html at /docs, /public/docs, etc.
Can someone please explain what I'm doing wrong?
Thanks!
--john
buildscript {
ext {
springBootVersion = '1.4.1.RELEASE'
}
}
plugins {
id "org.asciidoctor.convert" version "1.5.3"
}
apply plugin: 'groovy'
apply plugin: 'spring-boot'
ext {
snippetsDir = file('build/generated-snippets')
springRestdocsVersion = '1.1.2.RELEASE'
}
test {
outputs.dir snippetsDir
}
asciidoctor {
attributes 'snippets': snippetsDir
inputs.dir snippetsDir
dependsOn test
}
jar {
dependsOn asciidoctor
from ("${asciidoctor.outputDir}/html5") {
into 'public/docs'
}
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-data-rest')
testCompile("org.springframework.restdocs:spring-restdocs-mockmvc:${springRestdocsVersion}")
}
=============================================================
here's the application config:
#SpringBootApplication
#EnableJpaRepositories
#EnableScheduling
class Application {
static void main(String[] args) {
SpringApplication.run Application, args
}
}
and the test config:
#RunWith(SpringJUnit4ClassRunner)
#SpringApplicationConfiguration(classes = Application)
class ApplicationTests {
...
}
OK, I finally figured out what I was doing wrong. I had the spring-boot-actuator-docs enabled:
compile('org.springframework.boot:spring-boot-actuator-docs')
and it was "taking over" the /docs path. As soon as I relocated the generated restdocs to a different path, e.g.
jar {
dependsOn asciidoctor
from ("${asciidoctor.outputDir}/html5") {
into 'static/api'
}
}
all was good.
Thanks Andy for your interest in my question and the very cool Spring REST Docs project!

Resources