Creating Projects with Gradle - Two Ways

In this post, we will explore how to create a Java project structure using Gradle. Gradle provides the init command for creating projects. Let's create a Java project for demonstration purposes.

I will be using Gradle version 8.6 in this post.

➜  p3 gradle --version

------------------------------------------------------------
Gradle 8.6
------------------------------------------------------------

Build time:   2024-02-02 16:47:16 UTC
Revision:     d55c486870a0dc6f6278f53d21381396d0741c6e

Kotlin:       1.9.20
Groovy:       3.0.17
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          17.0.10 (Eclipse Adoptium 17.0.10+7)
OS:           Mac OS X 14.6.1 aarch64

Option 1: Interactive mode

In this mode, we just run the command gradle init, and Gradle's init plugin will interactively ask the user for the necessary information.

➜  p3 gradle init

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 2

Select implementation language:
  1: C++
  2: Groovy
  3: Java
  4: Kotlin
  5: Scala
  6: Swift
Enter selection (default: Java) [1..6] 3

Generate multiple subprojects for application? (default: no) [yes, no] no

Select build script DSL:
  1: Kotlin
  2: Groovy
Enter selection (default: Kotlin) [1..2] 2

Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit Jupiter) [1..4] 4

Project name (default: p3): myapp

Enter target version of Java (min. 7) (default: 17): 17

Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] no


> Task :init
To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.6/samples/sample_building_java_applications.html

BUILD SUCCESSFUL in 39s
1 actionable task: 1 executed

In this case, I have decided to use Java as the language for application development, Groovy DSL for the build configurations, JUnit Jupiter as the test framework, "myapp" as the project name, and Java version 17.

The following files are generated as a result of the above interaction.

➜  p3 tree
.
├── app
│   ├── build.gradle
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── org
│       │   │       └── example
│       │   │           └── App.java
│       │   └── resources
│       └── test
│           ├── java
│           │   └── org
│           │       └── example
│           │           └── AppTest.java
│           └── resources
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

15 directories, 9 files

The Gradle configurations are as follows, making use of the Groovy DSL.

➜  p3 cat settings.gradle
/*
 * This file was generated by the Gradle 'init' task.
 *
 * The settings file is used to specify which projects to include in your build.
 * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.6/userguide/multi_project_builds.html in the Gradle documentation.
 */

plugins {
    // Apply the foojay-resolver plugin to allow automatic download of JDKs
    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}

rootProject.name = 'myapp'
include('app')
➜  p3 cat app/build.gradle
/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Java application project to get you started.
 * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.6/userguide/building_java_projects.html in the Gradle documentation.
 */

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation libs.junit.jupiter

    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    // This dependency is used by the application.
    implementation libs.guava
}

// Apply a specific Java toolchain to ease working on different environments.
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

application {
    // Define the main class for the application.
    mainClass = 'org.example.App'
}

tasks.named('test') {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

Challenges with this approach

If a developer is creating one project at a time, the interactive mode is good enough. Suppose we want to generate multiple projects with similar configurations, one might want to use the Gradle init command from an automation script. Here, interactive mode will pose several challenges. First, it will be hard to pass values into the command in interactive mode (though possible).

Second, in an upcoming version of the Gradle init plugin, if they add an additional argument in between, our existing automation will break due to the change in the order of input values. Fortunately, the init command offers alternate options.

Option 2: non-interactive mode

In this non-interactive mode, we pass all the information required to create the project structure as command-line arguments. If the user fails to pass the required information, the Gradle init plugin will enter interactive mode for the necessary information.

➜  p4 gradle init \
  --type java-application \
  --java-version 17  \
  --dsl groovy \
  --test-framework junit-jupiter \
  --package com.nareshak.demo \
  --project-name my-app  \
  --no-split-project  \
  --no-incubating

> Task :init
To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.6/samples/sample_building_java_applications.html

BUILD SUCCESSFUL in 288ms
1 actionable task: 1 executed

The project structure is as follows, after running the above command.

➜  p4 tree
.
├── app
│   ├── build.gradle
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── com
│       │   │       └── nareshak
│       │   │           └── demo
│       │   │               └── App.java
│       │   └── resources
│       └── test
│           ├── java
│           │   └── com
│           │       └── nareshak
│           │           └── demo
│           │               └── AppTest.java
│           └── resources
├── gradle
│   ├── libs.versions.toml
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

17 directories, 9 files

Also, the Gradle configuration is as follows.

➜  p4 cat settings.gradle
/*
 * This file was generated by the Gradle 'init' task.
 *
 * The settings file is used to specify which projects to include in your build.
 * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.6/userguide/multi_project_builds.html in the Gradle documentation.
 */

plugins {
    // Apply the foojay-resolver plugin to allow automatic download of JDKs
    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}

rootProject.name = 'my-app'
include('app')
➜  p4 cat app/build.gradle
/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Java application project to get you started.
 * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.6/userguide/building_java_projects.html in the Gradle documentation.
 */

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation libs.junit.jupiter

    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    // This dependency is used by the application.
    implementation libs.guava
}

// Apply a specific Java toolchain to ease working on different environments.
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

application {
    // Define the main class for the application.
    mainClass = 'com.nareshak.demo.App'
}

tasks.named('test') {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

Notice the similarities in the results of both commands.

Note that in the command-line argument mode, we do not need to worry about the order of the arguments. By the time the processing starts, the command will have the necessary arguments, and it can consume the arguments in the way it needs.

The interactive and non-interactive modes I explained here are generic for any console application, not just Gradle's init command.