Spring Boot Gradle build fails with "Execution failed for task 'bootWarMainClassName' - spring-boot

I have a multi-project gradle build with Spring Boot structured per default gradle conventions.
root
-- common
-- src/main/java
-- bootproject
-- src/main/java
My current project is to (A) upgrade gradle from 5.x to 7.3.x and (B) use embedded tomcat with Spring Boot.
This is a project that has existed for many years and is Spring Boot but has always been deployed traditionally as a WAR file in Tomcat.
I have upgraded gradle to 7.3.3 following the gradle migration guide and have "common" building correctly (java-library). I am now trying to make "bootproject" build correctly again. I have migrated my build.gradle, and compilation happens correctly now but upon executing 'gradlew sub-project:build' I get the error:
Execution failed for task ':tx-main:bootWarMainClassName'.
java.lang.IllegalArgumentException (no error message)
My ROOT build.gradle is simple:
plugins {
id "org.springframework.boot" version "2.6.3"
}
subprojects {
apply plugin: 'java'
group = 'com.blah'
version = '2.1.1'
repositories {
mavenCentral()
}
There is a library sub-project (common) that builds fine.
The Spring Boot subproject build.gradle is:
plugins {
id 'org.springframework.boot'
id 'io.spring.dependency-management'
}
dependencies {
implementation project(path:':common', configuration:'default')
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation ...
}
The main class was originally set up to make the app conventionally deployed (extends SpringBootServletInitializer), but has been replaced with (taken directly from docs):
package com.blah;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The compilation works, this class appears to be in the build/classpath so I don't understand why Gradle-Spring-Boot is not finding it. I have also tried explicitly identifying the class with the same result.
Unfortunately I find little documentation about multi-project gradle builds so I suspect that is part of the problem. Hopefully someone here can point me in the right direction as to what is wrong.
Thanks

I discovered the issue here. We have custom code in /buildSrc that is apparently causing this.

Related

How to setup an existing Gradle project on STS with springframework libraries?

I have downloaded a gradle spring boot project from GitHub. I'm using STS (Spring Tool Suite). I have downloaded the Gradle plugin from marketplace. and I have tried options such as manually adding the eclipse and eclipse-wtp plugins and the classpath like below to the build.gradle file:
plugins {
id 'eclipse-wtp'
id 'org.springframework.boot' version '2.4.13'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id 'jacoco'
id "org.unbroken-dome.test-sets" version "4.0.0"
}
eclipse {
classpath {
file {
whenMerged {
def source = entries.find { it.path == 'src/main/java' }
source.entryAttributes['ignore_optional_problems'] = 'true'
}
}
}
}
However, I cannot build the project and I get error messages on imports related to annotations such as below:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Can you please guide me in how to build this gradle project with STS and resolve the errors?
p.s. I have downloaded latest gradle too. if I navigate to the project folder on CMD and run it with "gradle bootRun", tomcat comes up properly. but I don't know how to set it up on STS!
If you download and use the latest Spring Tools 4 for Eclipse distribution, there is usually nothing you need to add in order to use Gradle, the Buildship tooling comes pre-installed.
With that, you can select "Import -> Gradle / Existing Gradle Project", select your Gradle project, and go from there.
If your Gradle build file works in general, this should work just fine in the IDE, including resolving all the necessary dependencies that you have defined in your build file. So if your Gradle build runs fine from the command line, the import into the IDE should work as well.

Spring Boot + Kotlin + Gradle - Error: Main method not found in class

I'm learning spring boot with Kotlin (since I come from Android with Kotlin). I set it up with gradle. In my local machine everything works just fine. But I'm having a few issues while trying to deploy it to Heroku.
This is the error I'm getting:
Error: Main method not found in class com.markoid.packit.PackitApplication, please define the main method as:
2021-07-01T20:58:51.075484+00:00 app[web.1]: public static void main(String[] args)
2021-07-01T20:58:51.075581+00:00 app[web.1]: or a JavaFX application class must extend javafx.application.Application
I read on other posts that I need to add system.properties file in the root, so I did, but nothing changes.
system.properties
java.runtime.version=11
And this is my build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.5.1"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.5.10"
kotlin("plugin.spring") version "1.5.10"
}
group = "com.markoid"
version = "1.0.0-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
}
dependencies {
// Spring Boot Core
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-mail")
implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
// Joda Time library
implementation("joda-time:joda-time:2.10")
// Json Web Token
implementation("io.jsonwebtoken:jjwt-impl:0.11.1")
implementation("io.jsonwebtoken:jjwt-api:0.11.1")
implementation("io.jsonwebtoken:jjwt-jackson:0.11.1")
// Serializers
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
// Documentation
implementation("io.springfox:springfox-swagger2:2.6.1")
// Kotlin related
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// Testing Frameworks
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.withType<Jar> {
manifest {
attributes["Main-Class"] = "com.markoid.packit.PackitApplication"
}
}
My app file is as simple as this:
#SpringBootApplication
class PackitApplication
fun main(args: Array<String>) {
runApplication<PackitApplication>(*args)
}
Does someone know what I'm missing? This is the first project on spring I'm trying to deploy on heroku, so please bare with me.
Thank you in advance.
I've faced with the same issue. My problematic configuration is:
Kotlin 1.5.21
Spring boot 2.5.2
Gradle 7.1.1
The problem was that in IDEA it can be run, but cannot not using gradlew command line. I went and checked all old projects. They were built and run without any problem. The difference was in versions. All my previous project had lower versions in all positions I mentioned above. So I suggested that the problem was in the version, but what tool it was? Kotlin, Spring, Gradle? I have not yet found the guilty (and no time to go into deep of this problem now), but I found a solution.
If you open your jar file in any archiver (eg. WinRAR) and look at the MANIFEST.MD you will see a line starting with 'Start-Class', there is your main class and it must end with 'Kt' suffix, for example
Start-Class: me.sigest.fiveplus.FiveplusApplicationKt
In my failed jar it was not. To fix it I changed the code in build.gradle.kts file and set
springBoot {
mainClass.set("com.example.MyMainClassKt")
}
Despite the fact that in reality my class is called MyMainClass (without suffix Kt) and it helped
P.S. all my old boot jars contain the correct name of main class with Kt. I suppose the problem is in Gradle
I just had to a few things to make it work:
In the build.gradle.kts, I removed the tasks with type jar, and added this:
springBoot {
mainClass.set("com.markoid.packit.PackitApplicationKt")
}
I needed to add a Procfile, with the following:
web: java -Dserver.port=$PORT $JAVA_OPTS -jar build/libs/packit-1.0.0-SNAPSHOT.jar
Such file will tell heroku the specific command I want to be executed to run the generated jar.
Have you tried changing the main method like it is being suggested in the error message, i.e. public static void main(String[] args)

SpringBoot WAR file deployed into Tomcat running but still returning 404

I want to demonstrate a simple SpringBoot application being deployed into a separately running Tomcat instance. Even though my application appears to have been deployed, I cannot access it in Tomcat.
I have followed the 'Traditional deployment' instructions in the Spring documentation.
I have tried using a different version of Tomcat and running the war file as ROOT.warbut get similar behaviour.
When I run the war standalone e.g. java -jar build/libs/springbootify.war I can successfully access the expected URL http://localhost:8080/doit.
Application.java
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
}
MyController.java
#RestController
public class MyController {
#GetMapping(value = "/doit")
public String getSomething() {
return "Something";
}
}
build.gradle
plugins {
id 'war'
id 'java'
id 'io.spring.dependency-management' version "1.0.8.RELEASE"
id 'org.springframework.boot' version '2.1.6.RELEASE'
id 'com.palantir.docker' version '0.22.1'
id 'com.palantir.docker-run' version '0.22.1'
}
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
compile 'org.springframework.boot:spring-boot-devtools'
}
See the reproducible example on Github.
I can see that the war file has been deployed in the Tomcat logs
29-Jun-2019 17:07:04.434 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/springbootify.war] has finished in [857] ms
However, when I access http://localhost:8080/springbootify/doit I get a 404 response.
I expect this to return the string "something" with a 200 response.
UPDATE - 2019-07-12
I have tried building the war using Maven, and manually deploying to Tomcat via the manager UI and this works correctly
Inspecting the difference between the war files generated by Gradle and Maven I found the following:
1) Maven adds these additional attributes to the META-INF/MANIFEST.MF:
Created-By: Maven Archiver 3.4.0
Build-Jdk-Spec: 12
Implementation-Title: springbootify
Implementation-Version: 0.0.1-SNAPSHOT
2) Maven adds this additional library to the war:
WEB-INF/lib/javax.annotation-api-1.3.2.jar whereas in the Gradle war it is in WEB-INF/lib-provided/javax.annotation-api-1.3.2.jar
I'm going to suppose it's one of these reasons that it's not working with Gradle, but so far I haven't managed to modify the Gradle generated war to include these differences.
The newly added pom.xml can also be viewed on the provided Github link.

Inconsistent build classes package structure with gradle java pugin

I am trying to run a simple gradle build with just one line in build.gradle:
apply plugin: 'java'
Java file is placed under- src/main/java/hello/Hello.java
When I run build.gradle, compiled Hello.class is generated under
build/classes/java/main/hello/Hello.class
What am I expecting is:
build/classes/main/java/hello/Hello.class
Hello.java-
package hello;
class Hello{
public static void main(String args[]){
System.out.println("Hello Java");
}
}
Can someone please explain this? Thanks.
This changed between Gradle 3.x and 4.x.
The reason we went with build/classes/java/main vs build/classes/main/java is that it was less likely to break in strange ways with builds and plugins that hardcoded the path to build/classes/main or snuck outputs into build/classes/main.
from Gradle forum discussion

How to share boilerplate Kotlin configuration across multiple Gradle projects?

The typical Kotlin configuration in a Gradle project is very boilerplate, and I'm looking for a way of abstracting it out into an external build script so that it can be reused.
I have a working solution (below), but it feels like a bit of a hack as the kotlin-gradle-plugin doesn't work out of the box this way.
It's messy to apply any non-standard plugin from an external script as you can't apply the plugin by id, i.e.
apply plugin: 'kotlin' will result in Plugin with id 'kotlin' not found.
The simple (well, usually) workaround is to apply by the fully qualified classname of the plugin, i.e.
apply plugin: org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper
which in this case throws a nice little exception indicating that the plugin probably wasn't meant to be called this way:
Failed to determine source cofiguration of kotlin plugin.
Can not download core. Please verify that this or any parent project
contains 'kotlin-gradle-plugin' in buildscript's classpath configuration.
So I managed to hack together a plugin (just a modified version of the real plugin) which forces it to find the plugin from the current buildscript.
kotlin.gradle
buildscript {
ext.kotlin_version = "1.0.3"
repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}
apply plugin: CustomKotlinPlugin
import org.jetbrains.kotlin.gradle.plugin.CleanUpBuildListener
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
import org.jetbrains.kotlin.gradle.plugin.KotlinPlugin
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
/**
* Wrapper around the Kotlin plugin wrapper (this code is largely a refactoring of KotlinBasePluginWrapper).
* This is required because the default behaviour expects the kotlin plugin to be applied from the project,
* not from an external buildscript.
*/
class CustomKotlinPlugin extends KotlinBasePluginWrapper {
#Override
void apply(Project project) {
// use String literal as KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY constant isn't available
System.setProperty("kotlin.environment.keepalive", "true")
// just use the kotlin version defined in this script
project.extensions.extraProperties?.set("kotlin.gradle.plugin.version", project.property('kotlin_version'))
// get the plugin using the current buildscript
def plugin = getPlugin(this.class.classLoader, project.buildscript)
plugin.apply(project)
def cleanUpBuildListener = new CleanUpBuildListener(this.class.classLoader, project)
cleanUpBuildListener.buildStarted()
project.gradle.addBuildListener(cleanUpBuildListener)
}
#Override
Plugin<Project> getPlugin(ClassLoader pluginClassLoader, ScriptHandler scriptHandler){
return new KotlinPlugin(scriptHandler, new KotlinTasksProvider(pluginClassLoader));
}
}
This can then be applied in any project (i.e. apply from: "kotlin.gradle") and you're up and running for Kotlin development.
It works, and I haven't had any issues yet, but I'm wondering if there is a better way? I'm not really keen on merging in changes to the plugin every time there's a new version of Kotlin.
Check out the nebula-kotlin-plugin. It seems very close to what you're trying to achieve there.
The problem here is that there is a known gradle bug about the inability to apply plugins by id from init scripts. That's why you need to use fully qualified class name as a workaround.
E.g. I have the following in the init script and it works:
apply plugin: org.jetbrains.kotlin.gradle.plugin.KotlinPlatformJvmPlugin
By the way, I created a gradle plugin for preparing custom gradle distributions with common setup defined in init script - custom-gradle-dist. It works perfectly for my projects, e.g. a build.gradle for a library project looks like this (this is a complete file, all repository, apply plugin, dependencies etc setup is defined in the init script):
dependencies {
compile 'org.springframework.kafka:spring-kafka'
}

Resources