00:00

Spring Initializr & Project Setup

Last updated:

Spring Initializr is the official project generator for Spring Boot. You pick a build tool, a Java version and the features you need, and it gives you a ready-to-run project: build file, main class, test, configuration file and build wrapper, all set up to work together. It is the fastest and safest way to start a Spring Boot project, because the versions it picks are known to be compatible.

This tutorial explains how Spring Initializr works and what each option means: build tool, Spring Boot version, Java version, packaging and dependencies. It shows how to generate a project from the website, your IDE or the command line, and walks through every file you get. You'll then turn the generated project into a small product catalog API on Spring Boot 4.1, run and package it with Maven or Gradle, and fix the setup mistakes that most often stop a new project from starting.

1. What Is Spring Initializr?

Spring Initializr is a web service, run by the Spring team at start.spring.io, that generates the skeleton of a Spring Boot project. It doesn't write your business code. It does the error-prone setup work instead:

  • creates a pom.xml or build.gradle with the Spring Boot plugin and the right starters, which are dependencies that bundle everything one feature needs, such as spring-boot-starter-webmvc for a web API;
  • adds a build wrapper (mvnw or gradlew), a script that downloads the correct Maven or Gradle version, so nobody needs to install a build tool;
  • creates the main application class, a first test and application.properties, in the package you chose.

If you are new to Spring Boot itself, start with the Spring Boot introduction first.

2. Three Ways to Use Spring Initializr

WayHowBest for
WebsiteOpen start.spring.io, choose the options, click Generate and unzip the downloadLearning, and seeing every option at once
IDEIntelliJ IDEA (New Project, then Spring Boot), VS Code with the Spring Boot Extension Pack, or Eclipse with Spring ToolsCreating and opening the project in one step
Command line / APIcurl https://start.spring.io/starter.zip -d ...Scripts, templates and repeatable setups

All three call the same service, so they produce the same project. This is the command used for the example in this tutorial:

Terminal
curl https://start.spring.io/starter.zip \
  -d type=maven-project -d language=java \
  -d bootVersion=4.1.2 -d javaVersion=21 \
  -d groupId=com.javabytechie -d artifactId=catalog-service -d name=catalog-service \
  -d description="Product catalog service" \
  -d packageName=com.javabytechie.catalog -d packaging=jar \
  -d dependencies=web,validation,actuator,devtools \
  -o catalog-service.zip

unzip catalog-service.zip -d catalog-service

Without baseDir, the files are placed directly at the top level of the zip, with no folder around them. Add -d baseDir=catalog-service if you want one.

3. How Spring Initializr Generates a Project

How Spring Initializr generates a projectYour choices, such as Maven, Spring Boot 4.1.2, Java 21 and the web and validation dependencies, are sent from the website, an IDE or curl to start.spring.io. The service checks them against its metadata and maps each dependency to starters for the chosen Boot version. It fills templates for the build file, main class, test and wrapper, and returns catalog-service.zip, ready to build and run. For example, Spring Web with Boot 4.1.2 becomes spring-boot-starter-webmvc plus spring-boot-starter-webmvc-test. Your choices Maven, Boot 4.1.2, Java 21, web, validation, ... website, IDE or curl start.spring.io checks choices against its metadata, maps them to starters Templates build file, main class, test, wrapper catalog-service.zip ready to build and run "Spring Web" + Boot 4.1.2 → spring-boot-starter-webmvc and spring-boot-starter-webmvc-test
Figure 1: the same checkbox can map to different starters for different Spring Boot versions, which is why the generated build file is always consistent.

The service keeps metadata: the list of supported Spring Boot and Java versions, every dependency and which starter it maps to. The website and IDEs read this metadata to build their forms. When you generate, the service resolves your choices for the selected Boot version, adds matching test dependencies, renders the build file and source templates, and streams back a zip. Because the mapping is versioned, the same checkbox can produce different starters for different Boot versions, as the version note in section 5 shows.

4. Choosing the Project Options

OptionDefault on start.spring.ioRecommendation
Project (build tool)Gradle - GroovyWhatever your team uses; Maven if you have no preference (section 4.1)
LanguageJavaJava, unless your team writes Kotlin
Spring BootLatest stable release (4.1.2 at the time of writing)Keep the default; avoid SNAPSHOT and RC versions
Groupcom.exampleYour organisation's reversed domain, e.g. com.javabytechie
ArtifactdemoA short, lower-case name with hyphens, e.g. catalog-service
Package nameGroup + artifactCheck it: a hyphen in the artifact becomes an underscore (com.javabytechie.catalog_service), so set a clean name yourself, e.g. com.javabytechie.catalog
PackagingJarJar (section 4.3)
ConfigurationPropertiesProperties or YAML; see application.properties vs YAML
Java17The newest LTS version your servers run: 21 or 25 (section 4.2)

4.1 Maven vs Gradle

MavenGradle
Build filepom.xml (XML)build.gradle (Groovy) or build.gradle.kts (Kotlin)
StyleDeclarative and very uniformScriptable and flexible
SpeedGoodFaster on large projects, thanks to incremental builds and the build cache
Run the app./mvnw spring-boot:run./gradlew bootRun
Build the jar./mvnw package → target/./gradlew build → build/libs/

The same choices produce this build.gradle when you pick Gradle. DevTools uses Gradle's developmentOnly configuration, the equivalent of Maven's optional runtime dependency:

build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '4.1.2'
    id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.javabytechie'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-webmvc'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-actuator-test'
    testImplementation 'org.springframework.boot:spring-boot-starter-validation-test'
    testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
    useJUnitPlatform()
}

4.2 Picking the Java Version

Spring Boot 4 needs Java 17 or later, and Initializr offers 17, 21, 25 and 27. Choose a long-term support (LTS) release, such as 21 or 25, that is installed both on your machine and on your servers. The choice ends up as <java.version> in Maven or a toolchain in Gradle. If it is newer than the JDK that runs the build, compilation fails (section 8.2).

4.3 Jar or War Packaging

A jar contains your app together with an embedded web server (Tomcat by default), so it runs anywhere with java -jar. That fits containers and cloud platforms. Choose war only if the app must be deployed into an existing external servlet container. Initializr then adds a ServletInitializer class for you.

4.4 Choosing Dependencies

Add only what you need now. You can always add starters to the build file later. The example uses four:

Initializr name (id)Starter addedGives you
Spring Web (web)spring-boot-starter-webmvcSpring MVC, JSON and embedded Tomcat
Validation (validation)spring-boot-starter-validationJakarta Bean Validation (@NotBlank, @Valid)
Spring Boot Actuator (actuator)spring-boot-starter-actuatorHealth and info endpoints
Spring Boot DevTools (devtools)spring-boot-devtoolsAutomatic restart while developing

5. What's Inside a Generated Spring Boot Project

Project structure
catalog-service/
├── .gitattributes
├── .gitignore
├── .mvn/
│   └── wrapper/
│       └── maven-wrapper.properties
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src/
    ├── main/
    │   ├── java/com/javabytechie/catalog/
    │   │   └── CatalogServiceApplication.java
    │   └── resources/
    │       └── application.properties
    └── test/
        └── java/com/javabytechie/catalog/
            └── CatalogServiceApplicationTests.java
FilePurpose
pom.xmlBuild file: inherits versions from spring-boot-starter-parent, lists the starters, and adds the Spring Boot Maven plugin, which builds the runnable jar
mvnw, mvnw.cmd, .mvn/wrapper/Maven Wrapper: downloads the pinned Maven version on first use
CatalogServiceApplication.javaEntry point; its package is the root of component scanning
application.propertiesConfiguration; starts with only the application name
CatalogServiceApplicationTests.javaA test that checks the application context starts
.gitignore, .gitattributesReady-made Git settings: ignore target/ and IDE files, fix line endings for the wrapper scripts
HELP.mdLinks to the documentation for each chosen dependency (ignored by Git)

The generated build file and classes, unchanged:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.1.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.javabytechie</groupId>
    <artifactId>catalog-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>catalog-service</name>
    <description>Product catalog service</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>21</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</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-webmvc</artifactId>
        </dependency>

        <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-starter-actuator-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webmvc-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

@SpringBootApplication turns on component scanning and auto-configuration. The empty <url/>, <licenses> and <scm> elements are deliberate placeholders. They stop those values from being inherited from Spring Boot's parent POM, and you can fill them in or delete them.

Version note: with Spring Boot 4, Initializr's "Spring Web" adds spring-boot-starter-webmvc (it was spring-boot-starter-web in Spring Boot 3.x). Each starter also gets a matching test starter, such as spring-boot-starter-webmvc-test, instead of the single spring-boot-starter-test that 3.x projects had. The Maven Wrapper is now script-only (distributionType=only-script), so there is no maven-wrapper.jar to commit.

6. A Complete Example: From Generated Project to Product API

Starting from the generated project, add a product sub-package with a small in-memory product API. Keep new packages below com.javabytechie.catalog, so component scanning finds them. Requests are checked with Bean Validation, invalid input returns 400, and an unknown SKU returns 404, both as ProblemDetail responses.

Which packages component scanning coversCatalogServiceApplication with @SpringBootApplication sits in com.javabytechie.catalog. Component scanning covers that package and everything below it, such as catalog.product with ProductController and ProductService, and a later catalog.order package. A sibling package such as com.javabytechie.product is not scanned, so a ProductController placed there is never registered and requests return 404 No static resource api/products. Scanned: com.javabytechie.catalog and everything below it CatalogServiceApplication @SpringBootApplication catalog.product ProductController, ProductService catalog.order (later) found automatically too Not scanned: a sibling package com.javabytechie.product ProductController never registered: 404 "No static resource api/products."
Figure 2: put every feature package below the main class's package; siblings are invisible to component scanning.

6.1 The Product Feature

package com.javabytechie.catalog.product;

import java.net.URI;
import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.validation.Valid;

@RestController
@RequestMapping("/api/products")
public class ProductController {

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @PostMapping
    public ResponseEntity<Product> create(@Valid @RequestBody CreateProductRequest request) {
        Product product = productService.create(request);
        return ResponseEntity.created(URI.create("/api/products/" + product.sku())).body(product);
    }

    @GetMapping("/{sku}")
    public Product findBySku(@PathVariable String sku) {
        return productService.findBySku(sku);
    }

    @GetMapping
    public List<Product> findAll() {
        return productService.findAll();
    }

    @ExceptionHandler(ProductNotFoundException.class)
    public ProblemDetail notFound(ProductNotFoundException ex) {
        return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
    }
}

6.2 Configuration

Extend the generated application.properties. Problem details make validation errors return a structured 400, and two Actuator endpoints are exposed:

application.properties
spring.application.name=catalog-service

# Return validation and other MVC errors as RFC 9457 problem details
spring.mvc.problemdetails.enabled=true

# Expose health and info over HTTP
management.endpoints.web.exposure.include=health,info
management.info.env.enabled=true
info.app.description=Product catalog service

The validation tutorial shows how to return field-level messages instead of the generic "Invalid request content."

6.3 A Test for the API

@WebMvcTest comes from the spring-boot-starter-webmvc-test dependency that Initializr already added. MockMvcTester gives AssertJ-style assertions. Each test checks the stored products before and after the request:

ProductControllerTest.java
package com.javabytechie.catalog.product;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.assertj.MockMvcTester;

@WebMvcTest(ProductController.class)
@Import(ProductService.class)
class ProductControllerTest {

    @Autowired
    MockMvcTester mvc;

    @Autowired
    ProductService productService;

    @Test
    void createThenFetchProduct() {
        assertThat(productService.findAll()).isEmpty();

        assertThat(mvc.post().uri("/api/products").contentType(MediaType.APPLICATION_JSON)
                .content("""
                        {"sku":"KB-100","name":"Mechanical Keyboard","price":89.00}"""))
                .hasStatus(HttpStatus.CREATED)
                .hasHeader("Location", "/api/products/KB-100");

        assertThat(mvc.get().uri("/api/products/KB-100"))
                .hasStatusOk()
                .bodyJson().extractingPath("$.name").isEqualTo("Mechanical Keyboard");
        assertThat(productService.findAll()).hasSize(1);
    }

    @Test
    void invalidProductIsRejectedAndNotStored() {
        assertThat(mvc.post().uri("/api/products").contentType(MediaType.APPLICATION_JSON)
                .content("""
                        {"sku":"keyboard","name":"","price":0}"""))
                .hasStatus(HttpStatus.BAD_REQUEST);

        assertThat(productService.findAll()).isEmpty();
    }

    @Test
    void unknownProductReturns404() {
        assertThat(mvc.get().uri("/api/products/XX-999"))
                .hasStatus(HttpStatus.NOT_FOUND)
                .bodyJson().extractingPath("$.detail").isEqualTo("Product not found: XX-999");
    }
}

7. Running, Testing and Packaging the Project

Start the app with the wrapper. With DevTools on the classpath, the log confirms it is active, and the app restarts whenever you recompile:

Console (./mvnw spring-boot:run)
INFO c.j.catalog.CatalogServiceApplication    : Starting CatalogServiceApplication using Java 21.0.7
INFO .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
INFO o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoints beneath base path '/actuator'
INFO o.s.boot.tomcat.TomcatWebServer          : Tomcat started on port 8080 (http) with context path '/'
INFO c.j.catalog.CatalogServiceApplication    : Started CatalogServiceApplication in 0.582 seconds (process running for 0.7)

Try the API:

Terminal
$ curl -s -i -X POST localhost:8080/api/products -H 'Content-Type: application/json' \
    -d '{"sku":"KB-100","name":"Mechanical Keyboard","price":89.00}'
HTTP/1.1 201
Location: /api/products/KB-100
{"sku":"KB-100","name":"Mechanical Keyboard","price":89.00}

$ curl -s localhost:8080/api/products
[{"sku":"KB-100","name":"Mechanical Keyboard","price":89.00},{"sku":"MS-200","name":"Wireless Mouse","price":25.50}]

$ curl -s -i -X POST localhost:8080/api/products -H 'Content-Type: application/json' \
    -d '{"sku":"keyboard","name":"","price":0}'
HTTP/1.1 400
{"detail":"Invalid request content.","instance":"/api/products","status":400,"title":"Bad Request"}

$ curl -s -i localhost:8080/api/products/XX-999
HTTP/1.1 404
{"detail":"Product not found: XX-999","instance":"/api/products/XX-999","status":404,"title":"Not Found"}

$ curl -s localhost:8080/actuator/health
{"groups":["liveness","readiness"],"status":"UP"}

$ curl -s localhost:8080/actuator/info
{"app":{"description":"Product catalog service"}}

Run the tests and build the runnable jar with ./mvnw package (Gradle: ./gradlew build), then start it like any Java program:

Terminal
$ ./mvnw package
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 -- in com.javabytechie.catalog.CatalogServiceApplicationTests
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 -- in com.javabytechie.catalog.product.ProductControllerTest
[INFO] --- spring-boot:4.1.2:repackage (repackage) @ catalog-service ---
[INFO] BUILD SUCCESS

$ java -jar target/catalog-service-0.0.1-SNAPSHOT.jar

The jar is about 24 MB because it contains every dependency and Tomcat. DevTools is automatically left out of it, so production never runs with development settings. Gradle also writes a second -plain.jar to build/libs that holds only your classes. Deploy the one without -plain. The DevTools tutorial covers restarts and LiveReload.

8. Common Setup Problems and How to Fix Them

8.1 "Port 8080 Was Already in Use"

Another program, often a second copy of your own app, already listens on the port:

Console
***************************
APPLICATION FAILED TO START
***************************

Description:

Web server failed to start. Port 8080 was already in use.

Action:

Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

How to fix it: stop the other process (lsof -i :8080 on macOS and Linux shows it) or choose another port with server.port=8081.

8.2 "Release Version 25 Not Supported"

The project was generated for a newer Java than the JDK that runs the build. Here a project set to Java 25 was built with JDK 21:

Console
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.15.0:compile (default-compile) on project catalog-service: Fatal error compiling: error: release version 25 not supported

How to fix it: install the matching JDK and point JAVA_HOME (and your IDE's project SDK) at it, or lower <java.version> to the version you have.

8.3 404 "No Static Resource" for Your Controller

The controller was placed in com.javabytechie.product, a sibling of the main class's package rather than below it. Component scanning never found it, so Spring treated the URL as a static file:

Terminal
$ curl -s -i localhost:8080/api/products
HTTP/1.1 404
{"detail":"No static resource api/products.","instance":"/api/products","status":404,"title":"Not Found"}

How to fix it: move the class below the main package, as in Figure 2, or leave the main class in the package that Initializr created and don't move it deeper.

8.4 The App Starts and Immediately Exits

Without a web starter there is no web server to keep the JVM running. The log shows "Started CatalogServiceApplication" and the process exits with code 0. How to fix it: add Spring Web (spring-boot-starter-webmvc) if you meant to build a web app. For command-line jobs, exiting is the correct behaviour.

8.5 The IDE Shows Red Imports

The project was opened as a plain folder, or the IDE hasn't finished downloading dependencies. How to fix it: open the folder that contains pom.xml or build.gradle (not a parent folder), import it as a Maven or Gradle project, and wait for the first sync. Using the wrapper the first time downloads the build tool, about a minute on a normal connection.

9. Best Practices

  • Start every project from Spring Initializr instead of copying an old project, so you get current, compatible versions.
  • Use the latest stable Spring Boot version, never a SNAPSHOT or release candidate, for real work.
  • Choose an LTS Java version that your servers actually run.
  • Set the package name yourself and keep all code below it.
  • Leave the main class where Initializr put it, at the root of your package tree.
  • Commit the wrapper scripts and always build with ./mvnw or ./gradlew, so everyone uses the same build tool version.
  • Add dependencies only when you need them; every starter adds auto-configuration and startup work.
  • Keep the generated test; it is the cheapest check that the application still starts.
  • Script repeatable setups with the curl API, and save the command alongside your templates.

10. Summary

Spring Initializr creates a working Spring Boot project in seconds, from the website, your IDE or a single curl command. Choose a build tool, the latest stable Spring Boot version, an LTS Java version that matches your JDK, jar packaging, a clear package name and only the starters you need. The generated project contains a build file, a wrapper, a main class whose package roots component scanning, a configuration file and a first test. From there, add features in sub-packages, run with ./mvnw spring-boot:run or ./gradlew bootRun, and package a runnable jar with ./mvnw package. The main recommendation: let Spring Initializr choose compatible versions, keep your code below the main class's package, and always build with the wrapper.