Learn Java

How to Build a Web Application with Spring Boot

By Updated 12 min read

Spring Boot is a framework, built on top of Spring, that sets up a Java web application for you and runs it on a web server inside your own program, so you start it like any other Java program and it answers requests from a browser. This guide builds a small one step by step: a project from Spring Initializr, a web page, JSON from a record, a request parameter and a path variable, and one jar file that runs anywhere Java 25 does.

Here is the whole idea in one class. In a project generated as shown below, this file is a complete web endpoint:

HelloController.java
read along
1package com.example.library;
2
3import org.springframework.web.bind.annotation.GetMapping;
4import org.springframework.web.bind.annotation.RequestParam;
5import org.springframework.web.bind.annotation.RestController;
6
7@RestController
8public class HelloController {
9 @GetMapping("/hello")
10 public String hello(@RequestParam(defaultValue = "world") String name) {
11 return "Hello, " + name + "!";
12 }
13}
Terminal
read along
1$ curl "http://localhost:8080/hello?name=Ada"
2Hello, Ada!

curl sends a request from the terminal and prints the answer; a browser at the same address shows the same text. Spring did the rest: it opened port 8080, read the request, chose this method because of @GetMapping("/hello"), filled name from ?name=Ada and sent the returned string back. The steps below build the project around it.

What you need#

A JDK 25 (Java 25, the current long-term support release) and either a terminal or an IDE such as IntelliJ IDEA, VS Code or Eclipse. Spring Boot 4 needs Java 17 or newer, and java -version tells you which one you have. You do not have to install Maven, the build tool: every generated project includes the Maven wrapper, a script named mvnw that downloads the right Maven version the first time you run it.

Create the project with Spring Initializr#

Spring Initializr, at start.spring.io, is the official project generator. You pick a few options on one page and download a ready project as a zip file:

  1. 1Project: Maven. Language: Java. Spring Boot: 4.1.1, or the newest 4.1 release it offers.
  2. 2Group com.example and Artifact library. The artifact names the project and its jar. Leave Packaging at Jar and choose Java 25.
  3. 3Press Add dependencies and pick Spring Web and Thymeleaf. Spring Boot DevTools is optional: it restarts the application by itself whenever your compiled classes change.
  4. 4Press Generate, unzip the download, and open the folder in your IDE, or cd into it in a terminal.

Editors with Spring support can create the same project from their New Project screen. They ask the same questions and call the same service. Either way, the pom.xml asks for two starters:

pom.xml (the parts that matter)
read along
1<parent>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-parent</artifactId>
4 <version>4.1.1</version>
5</parent>
6...
7<dependencies>
8 <dependency>
9 <groupId>org.springframework.boot</groupId>
10 <artifactId>spring-boot-starter-thymeleaf</artifactId>
11 </dependency>
12 <dependency>
13 <groupId>org.springframework.boot</groupId>
14 <artifactId>spring-boot-starter-webmvc</artifactId>
15 </dependency>
16 ...
17</dependencies>
18<build>
19 <plugins>
20 <plugin>
21 <groupId>org.springframework.boot</groupId>
22 <artifactId>spring-boot-maven-plugin</artifactId>
23 </plugin>
24 </plugins>
25</build>

A starter is one dependency that pulls in a set of libraries tested together. spring-boot-starter-webmvcis what Spring Web adds: Spring MVC (Spring's web framework), the Tomcat web server and Jackson, which turns Java objects into JSON. spring-boot-starter-thymeleaf adds Thymeleaf, a template engine that fills HTML pages with your data. The parent fixes the version of every library, so the dependencies list none, and the Spring Boot Maven plugin gives you the spring-boot:run command used below.

spring-boot-starter-web or spring-boot-starter-webmvc?
Spring Boot 4 renamed the web starter. Tutorials written for Spring Boot 3 use spring-boot-starter-web. Spring Boot 4 still has it but marks it deprecated in favor of spring-boot-starter-webmvc, which is what Initializr adds today.

What is inside the generated project#

library (the files that matter)
read along
1library/
2 mvnw, mvnw.cmd the Maven wrapper (macOS and Linux, Windows)
3 pom.xml the build file
4 src/main/java/com/example/library/
5 LibraryApplication.java the main class
6 src/main/resources/
7 application.properties settings
8 static/ files served as they are: CSS, images, scripts
9 templates/ Thymeleaf pages
10 src/test/java/com/example/library/
11 LibraryApplicationTests.java

The only Java code is the main class:

src/main/java/com/example/library/LibraryApplication.java
read along
1package com.example.library;
2
3import org.springframework.boot.SpringApplication;
4import org.springframework.boot.autoconfigure.SpringBootApplication;
5
6@SpringBootApplication
7public class LibraryApplication {
8
9 public static void main(String[] args) {
10 SpringApplication.run(LibraryApplication.class, args);
11 }
12
13}

An annotation such as @SpringBootApplicationis a label that a tool reads. This one tells Spring to search this class's package, and every package below it, for classes it should manage, such as your controllers, and to configure itself from the libraries it finds: Tomcat means a web server, Thymeleaf means HTML templates. SpringApplication.run does all of that and starts the server.

Keep your classes in the main class's package
Spring only looks in com.example.library and the packages below it. A controller placed in com.example.webis never found, so its address answers 404 Not Found. A browser then shows a page headed “Whitelabel Error Page” that says “This application has no explicit mapping for /error, so you are seeing this as a fallback.” Move the class into com.example.library, or a package under it such as com.example.library.web.

Add a web page with @Controller and Thymeleaf#

A controller is a class whose methods answer requests. Add this one next to LibraryApplication.java:

src/main/java/com/example/library/HomeController.java
read along
1package com.example.library;
2
3import java.util.List;
4import org.springframework.stereotype.Controller;
5import org.springframework.ui.Model;
6import org.springframework.web.bind.annotation.GetMapping;
7
8@Controller
9public class HomeController {
10
11 @GetMapping("/")
12 public String home(Model model) {
13 model.addAttribute("title", "My library");
14 model.addAttribute("books", List.of("Dune", "Emma", "Solaris"));
15 return "index";
16 }
17}
  • @Controller marks a class whose methods return the name of a page to show.
  • @GetMapping("/") says this method answers GET /, the home page.
  • The Model holds the values the page may use. Spring passes one in, and addAttribute puts a value in it under a name.
  • The return value "index" is a template name. Thymeleaf looks for src/main/resources/templates/index.html.

Then create the template:

src/main/resources/templates/index.html
read along
1<!DOCTYPE html>
2<html xmlns:th="http://www.thymeleaf.org">
3<head>
4 <meta charset="UTF-8">
5 <title th:text="${title}">Library</title>
6</head>
7<body>
8 <h1 th:text="${title}">Library</h1>
9 <ul>
10 <li th:each="book : ${books}" th:text="${book}">A book title</li>
11 </ul>
12</body>
13</html>

The th: attributes are Thymeleaf's. th:text="${title}"replaces the element's text with the model value title, and th:each repeats the <li>once for every book. The words inside the tags, such as “A book title”, only show if you open the file straight from disk. Served by Spring, the page has the heading “My library” and a list of Dune, Emma and Solaris.

Files in src/main/resources/static are served exactly as they are, with no controller: static/style.css is at http://localhost:8080/style.css, so the template can link to it.

Return JSON with @RestController#

A page is for people. Other programs, and the JavaScript in a page, want data, usually as JSON. A record describes one book, and an @RestController returns books instead of a page name:

Book.java and BookController.java
read along
1// Book.java
2package com.example.library;
3
4public record Book(long id, String title, String author) {
5}
6
7// BookController.java
8package com.example.library;
9
10import java.util.List;
11import org.springframework.web.bind.annotation.GetMapping;
12import org.springframework.web.bind.annotation.RestController;
13
14@RestController
15public class BookController {
16 private final List<Book> books = List.of(
17 new Book(1, "Dune", "Frank Herbert"),
18 new Book(2, "Emma", "Jane Austen"));
19
20 @GetMapping("/books")
21 public List<Book> all() {
22 return books;
23 }
24}
Terminal
read along
1$ curl http://localhost:8080/books
2[{"id":1,"title":"Dune","author":"Frank Herbert"},{"id":2,"title":"Emma","author":"Jane Austen"}]

Spring hands the list to Jackson, which writes each record as a JSON object with one field per component, in the order they are declared. A browser at http://localhost:8080/books shows the same text.

Key idea

An @Controller method returns the name of a template, which Thymeleaf turns into HTML. An @RestController method returns the data itself, which Spring sends as text or JSON.

Text from an @Controller is read as a page name
If hello() from the top of this page sat in an @Controller, Spring would take Hello, Ada! as the name of a template, find no such file, and answer with an error page instead of the greeting. Methods that return text or data belong in an @RestController.

Read request parameters and path variables#

A request can carry values in two places, and Spring has an annotation for each. The hello method above reads a request parameter, the part after ?. A path variable is a piece of the path itself, such as the 2 in /books/2. Add this method to BookController:

BookController.java (one more method)
read along
1// new imports: org.springframework.http.ResponseEntity
2// and org.springframework.web.bind.annotation.PathVariable
3
4@GetMapping("/books/{id}")
5public ResponseEntity<Book> one(@PathVariable long id) {
6 return books.stream()
7 .filter(b -> b.id() == id)
8 .findFirst()
9 .map(ResponseEntity::ok)
10 .orElse(ResponseEntity.notFound().build());
11}
Terminal
read along
1$ curl http://localhost:8080/books/2
2{"id":2,"title":"Emma","author":"Jane Austen"}
3$ curl -i http://localhost:8080/books/9
4HTTP/1.1 404
5Content-Length: 0
6Date: Mon, 19 Oct 2026 12:55:00 GMT

{id} in the mapping marks the variable part, and @PathVariable passes it to the parameter of the same name, converted to a long. ResponseEntity lets the method choose the status: ok(book) is 200 with the book, notFound() is 404 with no body. curl -i prints the status line, which is how you see the 404.

@RequestParam
@PathVariable
Reads the query part: /hello?name=Ada
Reads a piece of the path: /books/2
The mapping is the plain path: @GetMapping("/hello")
The mapping names the piece: @GetMapping("/books/{id}")
Required unless it has a defaultValue. Without one, a missing value means 400 Bad Request; with defaultValue = "world", /hello answers Hello, world!
Always present when the path matches. A value that is not a number, as in /books/abc, means 400 Bad Request.
Typical use: search text, a page number, a sort order.
Typical use: which item, by id or by name.

Run the application and open localhost:8080#

From the project folder, start it through the wrapper (on Windows, mvnw.cmd spring-boot:run, or .\mvnw.cmd spring-boot:run in PowerShell). In an IDE, run the main method of LibraryApplication like any other program. The end of the log looks like this, from a real run; your dates, times and process id will differ:

Terminal
read along
1$ ./mvnw spring-boot:run
2...
3 :: Spring Boot :: (v4.1.1)
4...
52026-10-19T15:54:52.707+03:00 INFO 59196 --- [library] [ main] o.s.boot.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
6...
72026-10-19T15:54:53.074+03:00 INFO 59196 --- [library] [ main] o.s.boot.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
82026-10-19T15:54:53.078+03:00 INFO 59196 --- [library] [ main] com.example.library.LibraryApplication : Started LibraryApplication in 0.975 seconds (process running for 1.165)

“Tomcat started on port 8080” and “Started LibraryApplication” mean it is ready. Open http://localhost:8080 in a browser for the page, and http://localhost:8080/books for the JSON. The program does not end by itself: it waits for requests until you press Ctrl+C. After you change the code, stop it and start it again, or let DevTools restart it if you added it.

Change the port with server.port#

Settings live in src/main/resources/application.properties. The generated file has one line, and the [library] in every log line comes from it. Add a second:

application.properties
read along
1spring.application.name=library
2server.port=9090

Restart, and the log says Tomcat started on port 9090: the page is now at http://localhost:9090.

Port 8080 already in use
Only one program can listen on a port. If an earlier run is still going, in another terminal or in your IDE, the new one stops during start-up with an error saying that port 8080 was already in use. Stop the old run, or pick another port with server.port.

Package the application as a runnable jar#

To run the application on a server, or send it to someone, build one jar file:

Terminal
read along
1$ ./mvnw package
2...
3$ java -jar target/library-0.0.1-SNAPSHOT.jar

package compiles the code, runs the tests and builds target/library-0.0.1-SNAPSHOT.jar (0.0.1-SNAPSHOT is the version Initializr writes into pom.xml). The Spring Boot Maven plugin then turns it into an executable jar that holds your classes and every library, Tomcat included. java -jar starts it with the same log and the same address on any computer with Java 25. Settings can also go on the command line: java -jar target/library-0.0.1-SNAPSHOT.jar --server.port=9090.

Where to go next: a database and a full REST API#

The books here live in a list, so they are fixed and vanish when the program stops. Three lessons take the same project further:

Questions people ask#

How do I create a web application in Spring Boot?
Generate a project at start.spring.io with the Spring Web dependency (and Thymeleaf for HTML pages), add a class marked @Controller or @RestController with a @GetMapping method, and start it with ./mvnw spring-boot:run. The application then answers at http://localhost:8080.
Which dependency makes a Spring Boot project a web application?
The one Spring Initializr calls Spring Web. In Spring Boot 4 it adds spring-boot-starter-webmvc, which brings Spring MVC, the embedded Tomcat server and Jackson for JSON. Spring Boot 3 projects used spring-boot-starter-web, which Spring Boot 4 marks as deprecated.
Is Spring Boot still relevant in 2026?
Yes. Spring Boot 4.1 is a 2026 release with open source support listed on spring.io until July 2027, and version 4.2 is scheduled for November 2026, so new versions keep arriving about every six months.
Does Netflix use Spring Boot?
Yes. In 2018 Netflix's engineering blog announced that Netflix was moving to Spring Boot as its core Java framework, and Netflix later open-sourced DGS, its framework for GraphQL services, which is built on Spring Boot.
Is Spring Boot only for Java?
Spring Boot is a Java framework, and you can also write a Spring Boot application in Kotlin or Groovy, the other two languages Spring Initializr offers. Whichever you choose, the application runs on the Java Virtual Machine.
What port does a Spring Boot application use?
Port 8080 by default, so it answers at http://localhost:8080. Put server.port=9090 in application.properties, or start the jar with --server.port=9090, to use another port.

Further reading#

Learn it step by step

These lessons teach the topic from the start, with more examples and questions to check yourself.

All guides

Spotted a mistake, or a part that did not make sense? Report it by email