35

I'm working on a Spring Boot app with multiple modules and we're using Gradle to build it. Unfortunately I can't get the Gradle configuration right.

The project structure is this:

parent
  |
  + build.gradle
  |
  + settings.gradle
  |
  + core
  |   |
  |   + build.gradle
  | 
  + apis
  |   |
  |   + build.gradle
  |
  + services
  |   |
  |   + build.gradle
  | 
  + data
  |   |
  |   + build.gradle

When I try to build the project, I get compilation errors like error: cannot find symbol saying, that the classes from data used in services aren't imported. And I assume this is true between all the modules.

My parent build.gradle looks like this:

buildscript {
    ext {
        springBootVersion = '2.0.0.BUILD-SNAPSHOT'
    }
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'eclipse'
apply plugin: 'idea'

allprojects {
    apply plugin: 'java'
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'

    group = 'com.example'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = 1.8
    targetCompatibility = 1.8

    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }

    dependencies {
        testCompile('org.springframework.boot:spring-boot-starter-test')
    }
}

dependencies {
    compile project(':data')
    compile project(':services')
    compile project(':apis')
    compile project(':core')
}

jar {
    baseName = 'my-jar'
    version = '0.0.1-SNAPSHOT'
}

settings.gradle:

rootProject.name = 'my-app'
include ':apis'
include ':core'
include ':data'
include ':services'

one of children (core) has this in it's build.gradle:

dependencies {
    compile('org.springframework.boot:spring-boot-starter-actuator')
    compile('org.springframework.boot:spring-boot-starter-quartz')
    compile('org.springframework.boot:spring-boot-starter-tomcat')
    runtime('com.h2database:h2')

    compile project(':data')
    compile project(':services')
    compile project(':apis')
}

services build.gradle looks like this:

dependencies {
    compile('org.springframework.boot:spring-boot-starter-quartz')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    runtime('com.h2database:h2')

    compile project(':data')
}

The other ones also declare only dependencies.

The dependency structure looks like this:

core - apis, services, data

apis - services, data

services - data

data -

Example of the compilation errors:

/my-app/services/src/main/java/com/example/my-app/business/IssuedTokenService.java:3: error: package com.examples.data.dao does not exist import com.examples.data.dao.IssuedToken;

/my-app/services/src/main/java/com/example/my-app/business/IssuedTokenService.java:4: error: package com.examples.data.repository does not exist import com.examples.data.repository.IssuedTokenRepository;

/my-app/services/src/main/java/com/example/my-app/business/IssuedTokenService.java:12: error: cannot find symbol 
    private IssuedTokenRepository issuedTokenRepository;

    symbol:   class IssuedTokenRepository
   location: class IssuedTokenService

/my-app/services/src/main/java/com/example/my-app/business/IssuedTokenService.java:15: error: cannot find symbol
   public void saveToken(IssuedToken issuedToken) {
                                  ^
   symbol:   class IssuedToken
   location: class IssuedTokenService
bajermi2
  • 418
  • 1
  • 5
  • 14
  • So core depends on data, services and apis, right? do you have one of them depending on core as well? What's your actual error, and your dependency tree? usually you would have something like services->data->apis->core as a dependency chain. – Kylar Dec 01 '17 at 17:53
  • Based on your description, services can't find data classes. So what matters is the configuration of the services module, that you didn't post. Post the relevant code, and the exact and complete message. – JB Nizet Dec 01 '17 at 17:54
  • 1
    @JBNizet I have added both error messages and dependencies. As far as the compilation error goes, it is quite simple. Every symbol from data module used in code in services module is unknown to the compiler. – bajermi2 Dec 01 '17 at 18:12
  • 2
    My guess is that it's because you're applying the boot plugin to all projects. They're not boot projects. They're just regular Java projects. Only the top-level one (the bizarrely named core I guess, which depends on everything else and is thus the shell rather than the core), should be a boot project. – JB Nizet Dec 01 '17 at 18:18
  • 2
    @JBNizet I have already tried to do that, but when the boot plugin isn't there, Gradle doesn't recognize any of the `org.springframework.boot:spring-boot-starter-` dependencies. – bajermi2 Dec 01 '17 at 18:22
  • did you try to build the `data` project? e.g., `gradlew :data:build` – Nikita Skvortsov Dec 03 '17 at 09:04

4 Answers4

85

In your utility (data) projects put:

bootJar {
    enabled = false
}

jar {
    enabled = true
}

If kotlin dsl

tasks.getByName<BootJar>("bootJar") {
    enabled = false
}

tasks.getByName<Jar>("jar") {
    enabled = true
}
Juan Rada
  • 3,513
  • 1
  • 26
  • 26
Prime
  • 1,165
  • 6
  • 6
  • 3
    My friend thanks very much! I've tried to solve my dependency problem for 2 days – Yore Aug 06 '18 at 01:07
  • 4
    Can you pls explain why do we need to add these lines? – Max Aug 07 '18 at 16:00
  • 5
    @Max because spring boot builds jars with JarLauncher which has different structure than normal jar, it's meant to be executable and your classes become dependencies inside the spring-boot-jar. The code above changes build structure and disabled bootJar and JarLauncher, compiles module to standard jar without BOOT-INF directory in it. which is a normal not executable library. – agilob Oct 11 '18 at 12:04
  • 1
    I put the above code in `subprojects { ... }`. According to @agilob's explanation the boot stuff should be sufficient on the root project. – andy Nov 14 '18 at 13:56
  • Thanks much! This answer should be marked as correct. – Deividi Cavarzan Mar 11 '19 at 04:21
  • my 2 cents, In case someone wants to have both the executable boot-jar and normal jar, change the name of the boot jar and just enable the jar `bootJar { baseName("base-server-boot") launchScript() } jar{ enabled(true) }` This way you'll have 2 jars one executable boot jar and another normal jar which can be used by submodules. – Kid101 Nov 14 '19 at 19:30
18

The answer is similar to this one.

Gradle documentation on multi-project builds states:

A “lib” dependency is a special form of an execution dependency. It causes the other project to be built first and adds the jar with the classes of the other project to the classpath.

A repackaged jar, thus, poses a problem.

If, say, project B depends on project A, and the project A has a org.springframework.boot plugin applied to it, a simple compile project(':A') dependency will not work because the project jar is repackaged during the bootRepackage or bootJar task. The resulting fat jar has different structure, preventing Gradle from loading its classes.

In this case, the dependencies should be written the following way:

dependencies {
  compile project(':A').sourceSets.main.output
}

Using output of a source set directly is equivalent to using the "normal" resulting jar of project A before it is repackaged.

jihor
  • 2,478
  • 14
  • 28
  • 1
    Hmmm... sourceSets does not seem to be a valid property for me anymore. – r590 Mar 02 '18 at 01:13
  • 1
    @r590 They're still there as of gradle 4.6: https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#N14D97 – jihor Mar 02 '18 at 07:46
  • 3
    Lot seems to be changing between versions of gradle and also versions of spring plugins for gradle – bajermi2 Mar 04 '18 at 12:18
5
bootJar {
    enabled = false
}

jar {
    enabled = true
}

This works for my. In my case use spring boot with spring cloud with multi project and gradle fail to build. With this solution works!

  • Even though It's late response, I have faced that issue now, and you helped me.. so I had to honor that :D – Milos May 12 '21 at 18:29
5

You should exclude org.springframework.boot from all submodules (root build.gradle file, allProjects block), that are stands as dependencies for your core module, which will be build to fat-jar.

Include dependencyManagement configuration into all of your spring-boot managed submodules:

dependencyManagement {
    imports {
        mavenBom "org.springframework.boot:spring-boot-starter-parent:${springBootVersion}"
    }
}

The reason of your problem is the absence of submodules compiled jar files inside your core module fat-jar.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
Alexandr Solo
  • 51
  • 1
  • 2