---
title: Using WebMvc.fn with Embedded Tomcat Without Spring Boot
tags: ["Java", "Spring Boot", "Spring MVC", "WebMvc.fn"]
categories: ["Programming", "Java", "org", "springframework", "web", "servlet", "function"]
date: 2023-07-17T16:25:50Z
updated: 2023-07-17T16:33:03Z
---

> ⚠️ This article was automatically translated by OpenAI (gpt-4o).
> It may be edited eventually, but please be aware that it may contain incorrect information at this time.

Let's try using Spring MVC's [Functional Endpoints](https://docs.spring.io/spring-framework/reference/web/webmvc-functional.html), commonly known as WebMvc.fn, on Embedded Tomcat without Spring Boot.

First, we define the Functional Endpoints. We will write a lambda expression for an endpoint that outputs the classic "Hello World".

```java
package com.example;

import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerResponse;

public class Routing {
	public RouterFunction<ServerResponse> routes() {
		return RouterFunctions.route()
				.GET("/", request -> ServerResponse.ok().body("Hello World!"))
				.build();
	}
}
```

The class to start the Application will look like this. It only includes the definition of `DispatcherServlet` and the setup of Embedded Tomcat. Although it has a bit of boilerplate, it is quite simple.

```java
package com.example;

import java.util.List;
import java.util.Optional;

import org.apache.catalina.Context;
import org.apache.catalina.startup.Tomcat;

import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;
import org.springframework.web.servlet.function.support.RouterFunctionMapping;

public class Application {

	public static void main(String[] args) throws Exception {
		startTomcat();
	}

	static StaticWebApplicationContext applicationContext(RouterFunction<ServerResponse> routes) {
		StaticWebApplicationContext applicationContext = new StaticWebApplicationContext();
		applicationContext.registerBean(DispatcherServlet.HANDLER_MAPPING_BEAN_NAME,
				HandlerMapping.class, () -> {
					RouterFunctionMapping mapping = new RouterFunctionMapping(routes);
					mapping.setMessageConverters(List.of(
							new StringHttpMessageConverter(),
							new ByteArrayHttpMessageConverter(),
							new AllEncompassingFormHttpMessageConverter(),
							new MappingJackson2HttpMessageConverter()
					));
					return mapping;
				});
		return applicationContext;
	}


	static void startTomcat() throws Exception {
		Tomcat tomcat = new Tomcat();
		int port = Optional.ofNullable(System.getenv("PORT")).map(Integer::parseInt).orElse(8080);
		tomcat.getConnector().setPort(port);
		Context context = tomcat.addContext("", System.getProperty("java.io.tmpdir"));
		DispatcherServlet dispatcherServlet = new DispatcherServlet(applicationContext(new Routing().routes()));
		Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet).addMapping("/");
		tomcat.start();
		tomcat.getServer().await();
	}

}
```

The `pom.xml` will look like this. We use Spring Boot for dependency management, but we don't need to use Boot in the application.

```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 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>3.1.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>vanilla-mvcfn</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>vanilla-mvcfn</name>
	<description>vanilla-mvcfn</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-core</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-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>
```

Now, you can run the `Application` class or create an executable jar with `./mvnw clean package` and run it. Accessing http://localhost:8080 will return "Hello World!".

```
$ curl localhost:8080
Hello World!
```

You can write tests using MockMvc as follows. As of Spring 6.0, you cannot use MockMvc standalone like with @Controller, so you need to go through WebApplicationContext, which is a bit verbose. Once https://github.com/spring-projects/spring-framework/issues/30477 is addressed, it will be easier to write tests.

```java
package com.example;

import org.junit.jupiter.api.Test;

import org.springframework.mock.web.MockServletContext;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.support.StaticWebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

class ApplicationTests {
	MockMvc mockMvc = initMockMvc();

	MockMvc initMockMvc() {
		StaticWebApplicationContext applicationContext = Application.applicationContext(new Routing().routes());
		applicationContext.setServletContext(new MockServletContext());
		applicationContext.refresh();
		// https://github.com/spring-projects/spring-framework/issues/30477
		return MockMvcBuilders.webAppContextSetup(applicationContext).build();
	}

	@Test
	void hello() throws Exception {
		this.mockMvc.perform(get("/"))
				.andExpect(status().isOk())
				.andExpect(content().string("Hello World!"));
	}

}
```

The source code is available at https://github.com/making/vanilla-mvcfn.

---

By the way, if you use Spring Boot, you can simplify the code as follows.

```java
package com.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerResponse;

@Configuration
public class Routing {

	@Bean
	public RouterFunction<ServerResponse> routes() {
		return RouterFunctions.route()
				.GET("/", request -> ServerResponse.ok().body("Hello World!"))
				.build();
	}
}
```

```java
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

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

}
```

Changes to `pom.xml` and test code are omitted.

You can check the full diff at https://github.com/making/vanilla-mvcfn/compare/boot.
