When you tick “Spring Web” in Spring Initializr, spring-boot-starter-web lands in your pom.xml. Things work as the tutorial promises, but many developers freeze when asked “what exactly does this bring in?” or “how is it different from webflux or webmvc?”

In one sentence: spring-boot-starter-web is the starter for building web applications and REST APIs with Spring MVC and embedded Tomcat. This article focuses on this single artifact and walks through what it contains, how to declare it, how to swap out Tomcat, how it differs from webflux, and the rename in Spring Boot 4. The starter mechanism itself is covered in What Is a Spring Boot Starter?, so we will not go deep into it here. Spring Boot 3.x is the primary focus, with notes where 4.x differs.

What Is spring-boot-starter-web?

spring-boot-starter-web is a starter that bundles servlet-based Spring MVC, embedded Tomcat, and Jackson for JSON conversion. Add this one dependency, write a @RestController, and http://localhost:8080 responds as soon as you start the app. That is because the required libraries and the auto-configuration that wires them together all arrive as a set.

A common point of confusion is how it differs from spring-boot-starter. That one is just the “foundation”: Spring Boot core, auto-configuration, and logging, with no web features. starter-web includes starter, so there is no need to declare both.

Expanding the Contents with mvn dependency:tree

The best way to learn “what is in there” is to look for yourself. With Maven, the following command expands the transitive dependencies.

mvn dependency:tree
# Gradle の場合
./gradlew dependencies --configuration runtimeClasspath

Here is the part of the output under spring-boot-starter-web from a Spring Boot 3.3 project.

+- org.springframework.boot:spring-boot-starter-web:jar:3.3.5:compile
|  +- org.springframework.boot:spring-boot-starter:jar:3.3.5:compile
|  |  +- org.springframework.boot:spring-boot:jar:3.3.5:compile
|  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:3.3.5:compile
|  |  \- org.springframework.boot:spring-boot-starter-logging:jar:3.3.5:compile
|  +- org.springframework.boot:spring-boot-starter-json:jar:3.3.5:compile
|  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.17.2:compile
|  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.17.2:compile
|  |  \- (jdk8 / parameter-names モジュールなど)
|  +- org.springframework.boot:spring-boot-starter-tomcat:jar:3.3.5:compile
|  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:10.1.31:compile
|  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:10.1.31:compile
|  +- org.springframework:spring-web:jar:6.1.14:compile
|  \- org.springframework:spring-webmvc:jar:6.1.14:compile

There are five direct children.

  • spring-boot-starter brings Boot core, auto-configuration, and Logback-based logging
  • spring-boot-starter-json is the Jackson bundle. The modules that handle LocalDateTime and Optional are included here
  • spring-boot-starter-tomcat is embedded Tomcat. This is why a standalone jar can bring up a web server
  • spring-web is the shared HTTP foundation (RestTemplate, HttpMessageConverter, and so on)
  • spring-webmvc is Spring MVC itself, providing DispatcherServlet and @RequestMapping

It is just as important to know what is not included. Bean Validation (Hibernate Validator) was removed from starter-web in Spring Boot 2.3, so to use @Valid you must add spring-boot-starter-validation separately. When someone asks “I added starter-web but @Valid is ignored,” this is almost always the cause. See Why @Valid Does Not Work and How to Fix It for details. Likewise, Thymeleaf, Spring Data, and Spring Security are not included, so add their respective starters if you need them.

When you suspect duplicate libraries or version conflicts, getting into the habit of checking dependency:tree first will speed up your troubleshooting.

Adding It with Maven / Gradle

The coordinates can be copied and pasted as-is. The key point is to omit the version.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
// build.gradle(Groovy DSL)
implementation 'org.springframework.boot:spring-boot-starter-web'
// build.gradle.kts(Kotlin DSL)
implementation("org.springframework.boot:spring-boot-starter-web")

You can leave out the version because spring-boot-starter-parent in Maven, or the io.spring.dependency-management plugin in Gradle (which imports the spring-boot-dependencies BOM internally), manages the value that matches your Boot version. If you do not use the parent, you can achieve the same thing by importing spring-boot-dependencies into dependencyManagement with the import scope.

Swapping Embedded Tomcat for Jetty / Undertow

If you want a server other than Tomcat, simply exclude spring-boot-starter-tomcat from starter-web and add the replacement starter.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
implementation('org.springframework.boot:spring-boot-starter-web') {
    exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
implementation 'org.springframework.boot:spring-boot-starter-jetty'

In Gradle, you can also write configurations.implementation { exclude module: 'spring-boot-starter-tomcat' } to exclude it across the whole project at once.

One version caveat applies. Spring Boot 3.0 and 3.1 were based on Jetty 11 (Servlet 5.0), so they required a property tweak to downgrade the jakarta.servlet-api version. From 3.2 onward, Jetty 12 is supported, so a straight swap just works. Undertow is available in 3.x, but its support has been removed in Spring Boot 4.0. If you plan to upgrade to 4.x in the future, Jetty is the safer choice.

After swapping, common properties such as server.port continue to work. However, server.tomcat.* will no longer have any effect. If you want to stay on Tomcat and tune its performance, see Tuning the Embedded Tomcat Thread Pool.

Differences from spring-boot-starter-webflux

Initializr lists “Spring Web” alongside “Spring Reactive Web”, and the latter is spring-boot-starter-webflux.

Aspectstarter-webstarter-webflux
Programming modelServlet, blockingReactive, non-blocking
Default serverTomcatReactor Netty
How controllers are writtenReturn values directly from @RestControllerSame annotation, but return Mono / Flux
Good companionsJDBC / JPA / MyBatisR2DBC / WebClient

For ordinary web applications and REST APIs, or when you have existing JDBC or JPA assets, starter-web is perfectly fine. Consider webflux only when requirements such as handling a very large number of concurrent connections with few threads, or streaming as the primary use case, come up. The reactive mindset itself is summarized in Getting Started with Reactive Programming in Spring WebFlux.

What Happens If You Add Both?

Both starters sometimes end up together, for example when you add webflux just to use WebClient. In that case Spring Boot prefers Spring MVC and starts as a servlet application on Tomcat. If you want to be explicit, you can set the application type in application.properties.

# servlet / reactive / none のいずれか
spring.main.web-application-type=servlet

Renamed to spring-boot-starter-webmvc in Spring Boot 4

Some readers may have been puzzled recently by a starter called “spring-boot-starter-webmvc”. In Spring Boot 4.0, modules were reorganized by technology (for example spring-boot-webmvc), and the starter was renamed to spring-boot-starter-webmvc to match.

 <dependency>
   <groupId>org.springframework.boot</groupId>
-  <artifactId>spring-boot-starter-web</artifactId>
+  <artifactId>spring-boot-starter-webmvc</artifactId>
 </dependency>

As of 4.0, the old name spring-boot-starter-web remains as deprecated, so nothing breaks immediately. It is scheduled for removal in a future release, however, so the safest approach is to rewrite it when you upgrade to 4.x. Other starters were renamed as well, so check the full list in the official migration guide.

Conversely, if you are on Spring Boot 3.x, no change is needed. Writing spring-boot-starter-webmvc in 3.x yields the Could not find artifact error described below, because no such artifact exists there. The answer to “should I write web or webmvc?” comes down to one thing: the major version of Boot you are using.

A Minimal @RestController for Verification

You can confirm the starter is working with a single file.

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @RestController
    static class HelloController {

        @GetMapping("/hello")
        Map<String, String> hello() {
            return Map.of("message", "Hello, Spring Boot!");
        }
    }
}

If you start the app and see the following log, starter-web and starter-tomcat are functioning.

$ ./mvnw spring-boot:run
...  Tomcat started on port 8080 (http) with context path '/'

$ curl http://localhost:8080/hello
{"message":"Hello, Spring Boot!"}

The fact that returning a Map produces JSON is also proof that spring-boot-starter-json (Jackson) is active. The steps for splitting this into Controller, Service, and Repository layers and building CRUD continue in Implementing CRUD in a REST API.

Two Common Stumbling Blocks Right After Setup

Only the Whitelabel Error Page Appears

This is the case where you open the browser and see a blank page with “Whitelabel Error Page” and a 404. Tomcat is running, so the dependencies are correct. The problem lies in mapping or placement. Check in this order.

  1. Does a @GetMapping for that path actually exist?
  2. Is the Controller in the same package as the @SpringBootApplication class, or in a subpackage? Placing it outside the scan range is the classic cause. The mechanism is explained in What Is @SpringBootApplication?
  3. Have you mixed up @Controller and @RestController? With the former, the return value is interpreted as a template name

Could not find artifact

If the build stops with an error like Could not find artifact org.springframework.boot:spring-boot-starter-web:jar:unknown, check these three points.

  1. A typo in the artifactId, such as writing spring-boot-starter-webmvc on 3.x or dropping a hyphen
  2. Omitting the version with neither a parent nor a BOM in place. If you see version:unknown or version is missing, this is the cause
  3. A corporate proxy or offline environment that cannot reach Maven Central. Check the proxy settings in ~/.m2/settings.xml and re-fetch with mvn -U

Note that if startup itself fails with “APPLICATION FAILED TO START”, the problem is almost always on the configuration side rather than the dependencies. For that, see Troubleshooting a Spring Boot App That Will Not Start.

Summary

spring-boot-starter-web is a starter that brings in Spring MVC, embedded Tomcat, and Jackson in a single line. You can inspect its contents with dependency:tree, and since Bean Validation is not included, add starter-validation if you need it. Declare the coordinates without a version, and swapping Tomcat is a two-step process of an exclusion plus a replacement starter. When it coexists with webflux, MVC takes priority, and from Spring Boot 4 the starter is named spring-boot-starter-webmvc.

If you are wondering “why can I skip the version?” or “what other starters exist?”, reading What Is a Spring Boot Starter? for the big picture will make the content of this article click even more.