Initialize a Java Project

After creating a Java project, you need to do some initialization work. In this article, I will cover common initialization tasks for Java projects.

Create a Java Project

Create a Java Project with Gradle

Running the following commands to create a Java project with Gradle.

$ cd my-gradle-project
$ gradle init --use-defaults --type java-application \
--package com.taogen

Create a Java Project with Maven

Running the following commands to create a Java project with Maven.

$ mvn -B archetype:generate \
-DgroupId=com.taogen \
-DartifactId=my-maven-project \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.5

Create a Java Project using Spring Initializr

1. Open Spring Initializr web page

2. Setting project metadata

  • Group: com.taogen
  • Artifact: my-spring-project

3. Add dependencies

Add dependencies

Dependencies for Java projects

Basic dependencies for Java projects

  • Testing: JUnit, Mockito, Mockito JUnit Jupiter
  • Logging: SLF4J, Logback
  • Annotation Processing: Lombok
  • General Utilities: Apache Commons Lang 3, Apache Commons IO, Guava
  • JSON: Jackson, org.json
Using Gradle

gradle/libs.versions.toml

[versions]
# Testing
junit-jupiter = "5.12.1"
mockito = "5.19.0"
# Logging
slf4j-api = "2.0.17"
logback-classic = "1.5.18"
# Annotation Processing
lombok = "1.18.38"
# General Utilities
commons-lang3 = "3.18.0"
commons-io = "2.20.0"
guava = "33.4.8-jre"
# JSON
jackson = "2.20.0"
org-json = "20250517"

[libraries]
# Testing
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" }
# Logging
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-classic" }
# Annotation Processing
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
# General utilities
guava = { module = "com.google.guava:guava", version.ref = "guava" }
commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commons-lang3" }
commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" }
# JSON
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
org-json = { module = "org.json:json", version.ref = "org-json" }

[bundles]
testing = ["junit-jupiter", "mockito-core", "mockito-junit-jupiter"]
logging = ["slf4j-api", "logback-classic"]
generalUtilities = ["commons-lang3", "commons-io", "guava"]
json = ["jackson-databind", "org-json"]

build.gradle.kts

plugins {
java
// Apply the application plugin to add support for building a CLI application in Java.
application
id("com.gradleup.shadow") version "9.1.0"
}

group = "com.taogen"
version = "0.0.1-SNAPSHOT"

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

dependencies {
// Use JUnit Jupiter for testing.
testImplementation(libs.bundles.testing)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
// Logging
implementation(libs.bundles.logging)
// Lombok
compileOnly(libs.lombok)
annotationProcessor(libs.lombok)
testCompileOnly(libs.lombok)
testAnnotationProcessor(libs.lombok)
// General utilities
implementation(libs.bundles.generalUtilities)
// JSON
implementation(libs.bundles.json)
}

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

application {
// Define the main class for the application.
mainClass = "com.taogen.App"
}

tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}

tasks.jar {
// Set a constant name for the JAR
archiveFileName.set(project.name + ".jar")
// Configure the JAR task to include the main class in the manifest.
// Adds metadata to the JAR manifest
manifest {
attributes["Main-Class"] = "com.taogen.App" // Replace with your actual main class
}
}

// Configure Shadow JAR (fat JAR)
tasks.shadowJar {
archiveFileName.set(project.name + "-all.jar")
}

Running with Gradle

./gradlew run

Running with JAR

./gradlew build
# or skip tests
./gradlew -x test build

java -jar app/build/libs/app-all.jar
Using Maven

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.taogen</groupId>
<artifactId>my-maven-project</artifactId>
<version>1.0-SNAPSHOT</version>

<name>my-maven-project</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>21</maven.compiler.release>
<!-- Testing -->
<junit.version>5.12.1</junit.version>
<mockito.version>5.19.0</mockito.version>
<!-- Logging -->
<slf4j.version>2.0.17</slf4j.version>
<logback.version>1.5.18</logback.version>
<!-- lombok -->
<lombok.version>1.18.30</lombok.version>
<!-- General utilities -->
<guava.version>33.4.8-jre</guava.version>
<commons-lang3.version>3.18.0</commons-lang3.version>
<commons-io.version>2.20.0</commons-io.version>
<!-- JSON -->
<jackson.version>2.20.0</jackson.version>
<orgjson.version>20250517</orgjson.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${junit.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- General utilities -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<!-- JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>${orgjson.version}</version>
</dependency>
</dependencies>

<build>
<!-- Set final jar name -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- Maven Shade Plugin: To package a fat JAR -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<!-- Add manifest so you can run with java -jar -->
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.taogen.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Running with Maven

mvn exec:java -Dexec.mainClass="com.taogen.App"

Running with JAR

mvn clean package
# or skip tests
mvn clean package -Dmaven.test.skip=true

java -jar target/my-maven-project.jar

Dependencies for Spring Boot projects

Basic dependencies for Spring Boot projects

  • Web: spring-boot-starter-web (Including Logging and Jackson), spring-boot-starter-validation, spring-boot-starter-actuator, and Swagger.
  • Developer Tools: spring-boot-configuration-processor, spring-boot-devtools, and Lombok.
  • Testing: spring-boot-starter-test (Including JUnit, Mockito, Mockito JUnit Jupiter)
  • Logging: spring-boot-starter-web (Including SLF4J, Logback)
  • General Utilities: Apache Commons Lang 3, Apache Commons IO, Guava
  • JSON: org.json
  • Data Access: spring-boot-starter-data-jpa, mybatis-plus-spring-boot3-starter, mysql-connector-j, postgresql, spring-boot-starter-data-redis
Gradle

gradle/libs.versions.toml

[versions]
# Annotation Processing
lombok = "1.18.38"
# General Utilities
commons-lang3 = "3.18.0"
commons-io = "2.20.0"
guava = "33.4.8-jre"
# JSON
org-json = "20250517"
# Data Access
mybatis-plus-spring-boot3-starter = "3.5.14"
mysql-connector-j = "9.4.0"
postgresql = "42.7.7"

[libraries]
# Annotation Processing
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
# General utilities
guava = { module = "com.google.guava:guava", version.ref = "guava" }
commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commons-lang3" }
commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" }
# JSON
orgjson = { module = "org.json:json", version.ref = "org-json" }
# Data Access
mybatisPlus = { module = "com.baomidou:mybatis-plus-spring-boot3-starter", version.ref = "mybatis-plus-spring-boot3-starter" }
mysqlConnectorJ = { module = "com.mysql:mysql-connector-j", version.ref = "mysql-connector-j" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }

[bundles]
generalUtilities = ["commons-lang3", "commons-io", "guava"]

app/build.gradle.kts

plugins {
java
id("org.springframework.boot") version "3.5.5"
id("io.spring.dependency-management") version "1.1.7"
}

group = "com.taogen"
version = "0.0.1-SNAPSHOT"
description = "Demo project for Spring Boot"

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

dependencies {
// Web
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.11")
// Developer Tools
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
developmentOnly("org.springframework.boot:spring-boot-devtools")
developmentOnly("org.springframework.boot:spring-boot-docker-compose")
compileOnly(libs.lombok)
annotationProcessor(libs.lombok)
testCompileOnly(libs.lombok)
testAnnotationProcessor(libs.lombok)
// Testing
testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
// General utilities
implementation(libs.bundles.generalUtilities)
// JSON
implementation(libs.orgjson)
// Data Access
implementation(libs.mybatisPlus)
implementation(libs.postgresql)
implementation("org.springframework.boot:spring-boot-starter-data-redis")
}

configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}

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

tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}

Running with Gradle

./gradlew bootRun

Running with JAR

./gradlew bootJar
# or skip tests
./gradlew -x test bootJar

java -jar app/build/libs/app-0.0.1-SNAPSHOT.jar
Maven

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.taogen</groupId>
<artifactId>my-maven-project</artifactId>
<version>1.0-SNAPSHOT</version>

<name>my-maven-project</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>21</maven.compiler.release>
<spring-boot.version>3.5.3</spring-boot.version>
<swagger.version>2.8.11</swagger.version>
<!-- lombok -->
<lombok.version>1.18.38</lombok.version>
<!-- General utilities -->
<guava.version>33.4.8-jre</guava.version>
<commons-lang3.version>3.18.0</commons-lang3.version>
<commons-io.version>2.20.0</commons-io.version>
<!-- JSON -->
<orgjson.version>20250517</orgjson.version>
<!-- Data Access -->
<mybatis-plus-boot.version>3.5.14</mybatis-plus-boot.version>
<mysql.version>9.4.0</mysql.version>
<postgresql.version>42.7.7</postgresql.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<!-- Developer Tools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- Using the provided scope ensures that the Lombok JAR is available for compilation but is not packaged into your final application artifact -->
<scope>provided</scope>
<version>${lombok.version}</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- General utilities -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<!-- JSON -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>${orgjson.version}</version>
</dependency>
<!-- Data Access -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus-boot.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.mysql</groupId>-->
<!-- <artifactId>mysql-connector-j</artifactId>-->
<!-- <version>${mysql.version}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>

<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<!-- With the goal added, you have to call only mvn package -->
<!-- Otherwise you would need to call the plugin explicitly as mvn package spring-boot:repackage -->
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<!-- To prevent lombok being packaged into jar -->
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

Running with Maven

mvn spring-boot:run

Running with JAR

mvn package
# or
mvn package spring-boot:repackage
# or skip tests
mvn clean package -Dmaven.test.skip=true

java -jar target/my-maven-project.jar

Project Configurations

Logging Configurations

Logback Configurations

Refer to Configuring Logback with Spring Boot - A Complete Example

Spring Boot Configurations

Basic Spring Boot Configurations

application.yaml

spring:
application:
name: my-spring-project
profiles:
active: dev

springdoc:
swagger-ui:
path: "/swagger-ui.html"

server:
port: 8080

Data Access

PostgreSQL

spring:
datasource:
url: jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/crud_web_application
username: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
driver-class-name: org.postgresql.Driver

Redis

spring:
data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
password: ${REDIS_PASSWORD}
database: 0

Tools Configurations

Gradle Configurations

gradle.properties

# Enable Configuration Cache
org.gradle.configuration-cache=true
# Enable Build Cache (reuse build outputs between runs)
org.gradle.caching=true
# Parallel Execution (build independent modules at the same time)
org.gradle.parallel=true
# Configure Workers & Threads. Set based on your CPU cores (cores - 1 is often a good rule).
org.gradle.workers.max=7
# Configure Incremental Compilation. Only recompile files that actually changed.
kotlin.incremental=true
java.incremental=true
# Avoid Scanning for Updates When Not Needed. Only configures the modules you actually use in a build.
org.gradle.configureondemand=true
########################################
# Optional tuning
########################################
# Setting JVM arguments for the Gradle daemon. Parallel & sensible JVM args
org.gradle.jvmargs=-Xmx1g -XX:+UseParallelGC
# Run Kotlin compiler in-process (faster for small/medium projects)
kotlin.compiler.execution.strategy=in-process

Git Configurations

.gitignore

# Log files
*.log

# Environment variables files
.env
.env.*
!.env.example

# OS junk
.DS_Store
Thumbs.db

# Temporary files
*.temp

# Compiled files
*.class

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

### Gradle ###
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### Maven ###
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### Eclipse ###
.project
.classpath
.settings
.metadata

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/

### virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

Docker Configurations

Dockerfile

Refer to Using JLink to Create Smaller JRE Docker Images

Dockerfile-native

Refer to GraalVM Native Image with Docker - Spring Boot Application with GraalVM Native Image

compose.yml

services:
postgres:
image: 'postgres:17-alpine'
environment:
- TZ=Asia/Shanghai
# Note: Password can only be set on first startup. You can delete the volume to set a new password.
- 'POSTGRES_PASSWORD=${POSTGRES_PASSWORD}'
volumes:
- 'postgres_data:/var/lib/postgresql/data'
ports:
- '${POSTGRES_PORT}:5432'
redis:
image: 'redis:7-alpine'
ports:
- '${REDIS_PORT}:6379'
command: 'redis-server --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD}'
volumes:
- 'redis_data:/data'

volumes:
redis_data:
driver: local
postgres_data:
driver: local

compose-all.yml (Optional. Using spring-boot-docker-compose or this)

services:
app:
image: eclipse-temurin:21-alpine
working_dir: /app
command: ./gradlew bootRun --no-daemon
ports:
- "8080:8080"
env_file:
- .env.docker
volumes:
# Mount project source code
- .:/app
# Mount Gradle cache from host
- gradle-cache-1:/app/.gradle
- gradle-cache-2:/root/.gradle
postgres:
image: 'postgres:latest'
environment:
- TZ=Asia/Shanghai
# Note: Password can only be set on first startup. You can delete the volume to set a new password.
- 'POSTGRES_PASSWORD=${POSTGRES_PASSWORD}'
volumes:
- 'postgres_data:/var/lib/postgresql/data'
ports:
- '${POSTGRES_PORT}:5432'
redis:
image: 'redis:7.2.4-alpine'
ports:
- '${REDIS_PORT}:6379'
command: 'redis-server --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD}'
volumes:
- 'redis_data:/data'

volumes:
redis_data:
postgres_data:
gradle-cache-1:
gradle-cache-2:

.dockerignore

# Git
.git
.gitignore

# copy from gitignore
...

Environment Variables

  • .env: This file stores environment variables specific to a particular deployment or development environment. It typically contains sensitive information such as API keys, database credentials, and other configuration settings that should not be committed to version control (e.g., Git). Applications read values from this file to configure their behavior at runtime. The .env file is usually excluded from version control using a .gitignore entry to prevent accidental exposure of sensitive data.
  • .env.example: A template or example of the .env file.

For example:

.env

# Postgres database information. The superuser username is postgres
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=YOUR_PASSWORD
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=YOUR_PASSWORD

.env.docker

# Postgres database information. The superuser username is postgres
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=YOUR_PASSWORD
# Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=YOUR_PASSWORD

System Properties

Gradle

./gradlew bootRun --args="--spring.profiles.active=dev --server.port=8081"

Maven

mvn spring-boot:run -Dspring.profiles.active=dev -Dserver.port=8081

Java

java [JVM-options/-Dsystem-properties] -jar app.jar

Add system properties

java -Dspring.profiles.active=dev -Dserver.port=8081 -jar target/my-maven-project.jar

JVM Configurations

Running with Maven

Add JVM arguments for running with mvn spring-boot:run

1. Add JVM options to .mvn/jvm.config file. For example:

Java 21

-Xlog:gc*:my_maven_app_gc.log:time
-XX:InitialHeapSize=200m -XX:MaxHeapSize=200m -XX:MinHeapSize=200m

Java 8

-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:myapp-gc.log  
-XX:+UseParallelGC -XX:+UseStringDeduplication
-Xms<xxx>m -Xmx<xxx>m -XX:MetaspaceSize=<xxx>m -XX:MaxMetaspaceSize=<xxx>m -XX:NewSize=<xxx>m

2. Pass JVM options to maven command line

mvn spring-boot:run -Dspring-boot.run.jvmArguments="-XX:InitialHeapSize=600m -XX:MaxHeapSize=600m -XX:MinHeapSize=600m"

Running with Gradle

Add JVM arguments for running withgradle bootRun

1. Add JVM options to build.gradle.kts:

tasks.bootRun {
jvmArgs("-Xlog:gc*:my_gradle_app_gc.log:time", "-XX:InitialHeapSize=200m", "-XX:MaxHeapSize=200m", "-XX:MinHeapSize=200m")
}

Passing JVM argument when running a JAR file

java [JVM-options/-Dsystem-properties] -jar app.jar
java "-Xlog:gc*:my_maven_app_gc.log:time" -XX:InitialHeapSize=200m -XX:MaxHeapSize=200m -XX:MinHeapSize=200m -Dspring.profiles.active=prod  -Dserver.port=8081 -jar target/my-maven-project.jar

Verify JVM configurations

Find the PID

lsof -i -P -n | grep 8080
# or
jps

Get JVM flags of the Java application

jcmd <pid> VM.flags
jinfo <pid> flags

Get VM Arguments of the Java application

# Get Java System Properties, VM Flags, VM Arguments, java_class_path
jinfo <pid>

# Get VM Arguments
jps -lvm | grep <pid>

VM Flags vs. VM Arguments

  • VM flags: specific options that configure the JVM’s operation
  • VM Arguments: includes all command-line arguments passed to the java command before the main class name. This includes: VM Flags, System Properties, Classpath Configuration.

VM Arguments = VM Flags + System Properties

Others

Specify SDK version

If you use SDKMAN to manage JDK versions, you can add the following configuration file to specify the SDK version of the project.

.sdkmanrc

# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=21.0.8-tem

.sdkmanrc for GraalVM native image

# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=21.0.8-graal

More details refer to Env Command, Configuration

EditorConfig

.editorconfig

root = true

[*.{adoc,bat,groovy,html,java,js,jsp,kt,kts,md,properties,py,rb,sh,sql,svg,txt,xml,xsd}]
charset = utf-8

[*.{groovy,java,kt,kts,xml,xsd}]
indent_style = tab
indent_size = 4
end_of_line = lf

Makefile

Makefile example:

# Project name
APP_NAME=my-app

# Default target
.DEFAULT_GOAL := help

help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
| sort \
| awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'

run: ## Run application
./gradlew bootRun

buildJar: ## Build application
./gradlew bootJar

init-docker-database: ## Initialize Docker database
docker compose up -d postgres
sleep 10
bash ./sql/init-db.sh

test: ## Test application
./gradlew test

docker-build: ## Build Docker image
./gradlew bootJar
docker build -t $(APP_NAME) .

docker-run: ## Run Docker image
docker compose up -d
docker run --rm -p 8080:8080 --env-file .env.docker --network $(APP_NAME)_default $(APP_NAME)

docker-push: ## Push Docker image to Docker Hub
export DOCKER_BUILDKIT=1
docker login
docker build \
--builder my-multi-platform-builder \
--platform linux/amd64,linux/arm64 \
-f Dockerfile \
--tag tagnja/$(APP_NAME):0.1.0 \
--push .

clean: ## Remove build artifacts and Gradle cache
docker compose down -v
rm -rf build .gradle