We've reached the point where refresh() calls onRefresh() on the context. For web applications, onRefresh() is where the embedded web server is created and started — we saw this moment on the diagram in Part 6. Let's break down how Spring Boot picks the server, creates the DispatcherServlet, and handles errors.
7.1. Servlet vs Reactive — Two Parallel Worlds
Spring Boot supports two stacks:
| Stack | WebApplicationType | Default server | Context |
|---|---|---|---|
| Spring MVC | SERVLET |
Tomcat | AnnotationConfigServletWebServerApplicationContext |
| Spring WebFlux | REACTIVE |
Reactor Netty | AnnotationConfigReactiveWebServerApplicationContext |
The choice happens in the SpringApplication constructor via deduceWebApplicationType(). If DispatcherServlet (Spring MVC) is on the classpath → SERVLET. If DispatcherHandler (WebFlux) is present without MVC → REACTIVE.
Important: if both are on the classpath, SERVLET wins. Spring Boot gives MVC priority as the "older" stack.
7.2. Web Server Auto-configuration — ServletWebServerFactoryAutoConfiguration
The main class for the Servlet stack:
@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({
ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class
})
public class ServletWebServerFactoryAutoConfiguration {
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
}
}
Key points:
-
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)— the server is created before most other auto-configurations. -
@Importpulls in three nested configurations — for Tomcat, Jetty, and Undertow. Each one is activated via@ConditionalOnClass.
7.2.1. EmbeddedTomcat — Picking the Server
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class,
search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
// ...
return factory;
}
}
The selection logic:
- If
Tomcat.classis on the classpath and there's no customServletWebServerFactory— aTomcatServletWebServerFactoryis created. - If you swap Tomcat for
spring-boot-starter-jetty—@ConditionalOnClass(Tomcat.class)fails, andEmbeddedJettykicks in. - If you declare your own
ServletWebServerFactory—@ConditionalOnMissingBeanbacks off, and your factory wins.
7.3. ServletWebServerApplicationContext.onRefresh() — Creating the Server
The entry point is onRefresh() in ServletWebServerApplicationContext:
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
} catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
createWebServer():
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = getApplicationStartup()
.start("spring.boot.webserver.create");
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.tag("factory", factory.getClass().toString());
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
} else if (servletContext != null) {
// ... embedded container
}
}
Key steps:
-
getWebServerFactory()— fetches theServletWebServerFactoryfrom the context. -
factory.getWebServer(getSelfInitializer())— creates theWebServerand passes a callback for initializing theServletContext. - Two lifecycle beans are registered:
-
webServerStartStop— starts/stops the server -
webServerGracefulShutdown— graceful shutdown
-
7.3.1. getSelfInitializer() — the Bridge to ServletContextInitializer
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
ServletContextInitializer is a Spring Boot SPI interface:
@FunctionalInterface
public interface ServletContextInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
Every bean implementing ServletContextInitializer is invoked when the server starts. This is exactly how the DispatcherServlet, filters, and listeners get registered.
7.3.2. ServletContextInitializerBeans — Collecting All the Initializers
public class ServletContextInitializerBeans
extends AbstractCollection<ServletContextInitializer> {
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
// 1. Collects all ServletContextInitializer beans
// 2. Collects all ServletRegistrationBean, FilterRegistrationBean,
// ServletListenerRegistrationBean instances
// 3. Sorts them via @Order / Ordered
}
}
What ends up in here:
-
DispatcherServletRegistrationBean— registers theDispatcherServlet(fromDispatcherServletAutoConfiguration) - Custom
Filters(Spring Security, etc.) -
@WebServlet,@WebFilter,@WebListener(with@ServletComponentScan)
7.4. WebServerStartStopLifecycle — Starting Tomcat
Once the WebServer is created, a lifecycle bean is registered:
class WebServerStartStopLifecycle implements SmartLifecycle {
private final ServletWebServerApplicationContext applicationContext;
private final WebServer webServer;
private volatile boolean running;
@Override
public void start() {
this.webServer.start();
this.running = true;
this.applicationContext.publishEvent(
new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
}
@Override
public void stop() {
this.webServer.stop();
this.running = false;
}
@Override
public int getPhase() {
return Integer.MAX_VALUE - 1; // starts almost last
}
}
SmartLifecycle callbacks fire in finishRefresh() → lifecycleProcessor.onRefresh(). getPhase() = Integer.MAX_VALUE - 1 means the server starts after all the other lifecycle beans.
The order:
-
refresh()→finishRefresh()→lifecycleProcessor.onRefresh() -
WebServerStartStopLifecycle.start()→tomcat.start() -
ServletWebServerInitializedEventis published
7.5. DispatcherServletAutoConfiguration — Creating the DispatcherServlet
@AutoConfiguration(after = ServletWebServerFactoryAutoConfiguration.class)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DispatcherServlet.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(
webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(
webMvcProperties.isDispatchTraceRequest());
// ...
return dispatcherServlet;
}
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties,
ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration =
new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
}
Two key beans:
-
dispatcherServlet— theDispatcherServletitself. -
dispatcherServletRegistration— aDispatcherServletRegistrationBean, which implementsServletContextInitializer. When the server starts, it registers theDispatcherServletin theServletContext.
Ordering: @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) guarantees that the DispatcherServlet is created after the ServletWebServerFactory has been defined.
7.6. WebMvcAutoConfiguration — Wiring Up Spring MVC
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class,
TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() { ... }
@Bean
@ConditionalOnMissingBean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(...) { ... }
@Bean
@ConditionalOnMissingBean
public RequestMappingHandlerMapping requestMappingHandlerMapping(...) { ... }
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(...) { ... }
}
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) — the key point. If you add @EnableWebMvc, Spring Boot backs off, and the entire MVC auto-configuration is disabled. You get vanilla Spring MVC without Boot's customizations.
What WebMvcAutoConfiguration creates:
-
RequestMappingHandlerMapping— mapping URLs → controller methods -
RequestMappingHandlerAdapter— invoking controller methods -
HttpMessageConverters— Jackson, String, ByteArray, etc. -
InternalResourceViewResolver— for JSPs -
MessageSource— i18n -
Validator— JSR-380
7.7. ErrorMvcAutoConfiguration — Error Handling
/error is not part of Spring MVC. It's a Spring Boot add-on.
@AutoConfiguration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
errorViewResolvers.orderedStream().toList());
}
@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}
}
7.7.1. BasicErrorController — the /error Handler
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(
getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request,
getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
}
What BasicErrorController does:
- Handles
/error - If Accept:
text/html→ renders an HTML page (the Whitelabel Error Page) - If Accept:
application/json→ returns JSON
7.7.2. DefaultErrorAttributes — the Error Model
public class DefaultErrorAttributes implements ErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest,
ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, options);
addPath(errorAttributes, webRequest, options);
return errorAttributes;
}
}
The standard JSON:
{
"timestamp": "2026-09-23T12:00:00.000+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "...",
"path": "/api/order"
}
7.7.3. ErrorPageCustomizer — Registering the Error Page
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(
this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(errorPage);
}
}
It registers /error as the error page in Tomcat. When Tomcat catches an unhandled exception (or a 404/405), it forwards the request to /error, where BasicErrorController produces the response.
The chain:
Controller → DispatcherServlet → HandlerExceptionResolver → not handled
→ Tomcat → forward to /error → BasicErrorController
7.8. Server Customization — ServerProperties
All the server.* settings are read into ServerProperties:
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
private Integer port;
private InetAddress address;
private String contextPath;
private final Servlet servlet = new Servlet();
private final Tomcat tomcat = new Tomcat();
private final Jetty jetty = new Jetty();
// ...
}
Key settings:
| Property | What it does |
|---|---|
server.port |
the port (8080 by default) |
server.address |
the address to listen on |
server.servlet.context-path |
the application's root path |
server.servlet.session.timeout |
session timeout |
server.tomcat.threads.max |
Tomcat's max threads |
server.tomcat.threads.min-spare |
min spare threads |
server.shutdown=graceful |
graceful shutdown |
spring.lifecycle.timeout-per-shutdown-phase |
graceful shutdown timeout |
7.8.1. ServletWebServerFactoryCustomizer — Applying the Properties
public class ServletWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final ServerProperties serverProperties;
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(this.serverProperties::getPort).to(factory::setPort);
map.from(this.serverProperties::getAddress).to(factory::setAddress);
map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
map.from(this.serverProperties::getSsl).to(factory::setSsl);
map.from(this.serverProperties::getCompression).to(factory::setCompression);
map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
map.from(this.serverProperties::getError).to(factory::setErrorPages);
}
@Override
public int getOrder() {
return 0; // before user-defined customizers
}
}
@Order(0) — auto-configured customizers run before user-defined ones. If you want to override a setting, declare your own WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> — it will run later and overwrite the value.
7.8.2. Your Own WebServerFactoryCustomizer
@Component
public class MyTomcatCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers(connector -> {
connector.setProperty("maxKeepAliveRequests", "100");
});
}
}
7.9. The Reactive Stack — WebFlux and Netty
7.9.1. ReactiveWebServerFactoryAutoConfiguration
@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ReactiveHttpInputMessage.class)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@EnableConfigurationProperties(ServerProperties.class)
@Import({
ReactiveWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ReactiveWebServerFactoryConfiguration.EmbeddedTomcat.class,
ReactiveWebServerFactoryConfiguration.EmbeddedJetty.class,
ReactiveWebServerFactoryConfiguration.EmbeddedUndertow.class,
ReactiveWebServerFactoryConfiguration.EmbeddedNetty.class
})
public class ReactiveWebServerFactoryAutoConfiguration {
@Bean
public ReactiveWebServerFactoryCustomizer reactiveWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new ReactiveWebServerFactoryCustomizer(serverProperties);
}
}
EmbeddedNetty is activated via @ConditionalOnClass({ HttpServer.class, ... }) and creates a NettyReactiveWebServerFactory.
7.9.2. NettyReactiveWebServerFactory
public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory {
@Override
public WebServer getWebServer(HttpHandler httpHandler) {
HttpServer httpServer = createHttpServer();
// ... configuration
return new NettyWebServer(httpServer, handler,
getRouteProvider(), this::getServerShutdownTimeout);
}
}
The HttpHandler is built by WebHttpHandlerBuilder, which wires up WebFlux's DispatcherHandler. No Servlet API — Netty talks to the HttpHandler directly.
7.9.3. Key Differences from Servlet
| Aspect | Servlet (Tomcat) | Reactive (Netty) |
|---|---|---|
| Model | Thread-per-request | Event loop |
| API |
ServletContext, HttpServletRequest
|
ServerHttpRequest, ServerHttpResponse
|
| Handler | DispatcherServlet |
DispatcherHandler |
| Errors |
/error + BasicErrorController
|
DefaultErrorWebExceptionHandler |
| Lifecycle | WebServerStartStopLifecycle |
NettyWebServer |
7.10. Spring Boot 3 vs Spring Boot 4 — Key Differences
7.10.1. Modularization
The biggest change. In Boot 3, everything lives in spring-boot-autoconfigure. In Boot 4, there are 47 modules:
| Boot 3 | Boot 4 |
|---|---|
spring-boot-autoconfigure (everything) |
spring-boot-webmvc (MVC) |
spring-boot-webflux (WebFlux) |
|
spring-boot-tomcat (Tomcat) |
|
spring-boot-jetty (Jetty) |
|
spring-boot-jackson (Jackson) |
|
spring-boot-starter-web |
spring-boot-starter-webmvc (renamed) |
spring-boot-starter-webflux |
spring-boot-starter-webflux (unchanged) |
The auto-configuration packages have moved too:
-
org.springframework.boot.autoconfigure.web.servlet→org.springframework.boot.webmvc.autoconfigure -
org.springframework.boot.autoconfigure.web.reactive→org.springframework.boot.webflux.autoconfigure
7.10.2. Undertow Is Gone
Spring Boot 4 requires a Servlet 6.1 baseline, and Undertow isn't compatible with it. Undertow has been removed entirely:
"Spring Boot 4.0 requires a Servlet 6.1 baseline, with which Undertow is not yet compatible. As a result, Undertow support is dropped, including the Undertow starter and the ability to use Undertow as an embedded server."
What remains: Tomcat, Jetty, and Netty (for WebFlux).
7.10.3. Class Relocations
The embedded server classes have moved:
| Boot 3 | Boot 4 |
|---|---|
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory |
org.springframework.boot.tomcat.TomcatServletWebServerFactory |
org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory |
org.springframework.boot.netty.NettyReactiveWebServerFactory |
7.10.4. What Hasn't Changed
- The
ServletWebServerFactorymechanism — the same abstraction. -
ServletContextInitializer— the same SPI. -
ErrorMvcAutoConfiguration/BasicErrorController— the same logic. -
WebMvcAutoConfiguration— the same beans, just in different packages. -
WebServerFactoryCustomizer— the same interface.
7.11. Diagram: Creating the Web Server (Servlet)
refresh()
└─ AbstractApplicationContext.refresh()
├─ ...
└─ onRefresh() ← overridden in ServletWebServerApplicationContext
│
└─ createWebServer()
├─ getWebServerFactory()
│ └─ TomcatServletWebServerFactory (from ServletWebServerFactoryAutoConfiguration)
│
├─ factory.getWebServer(getSelfInitializer())
│ └─ TomcatServletWebServerFactory.getWebServer()
│ ├─ new Tomcat()
│ ├─ context = tomcat.addContext()
│ ├─ initializer.onStartup(servletContext) ← the callback
│ │ └─ ServletContextInitializerBeans
│ │ ├─ DispatcherServletRegistrationBean
│ │ ├─ FilterRegistrationBean
│ │ └─ ServletListenerRegistrationBean
│ └─ return new TomcatWebServer(tomcat)
│
├─ registerSingleton("webServerStartStop",
│ new WebServerStartStopLifecycle(...))
│
└─ registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(...))
│
└─ finishRefresh()
└─ lifecycleProcessor.onRefresh()
└─ WebServerStartStopLifecycle.start() ← phase = MAX-1
├─ tomcat.start()
├─ publishEvent(ServletWebServerInitializedEvent)
└─ running = true
7.12. Key Takeaways
-
ServletWebServerFactoryAutoConfigurationpicks Tomcat/Jetty/Undertow from the classpath.@AutoConfigureOrder(HIGHEST_PRECEDENCE). -
onRefresh()→createWebServer()is where the server is born. It runs insiderefresh(), beforefinishRefresh(). -
ServletContextInitializeris the SPI for registering servlets, filters, and listeners.DispatcherServletRegistrationBeanis one of its implementations. -
WebServerStartStopLifecycleis aSmartLifecyclewithphase = MAX-1— it starts Tomcat after all the other beans. -
DispatcherServletAutoConfigurationcreates theDispatcherServletand its registration. Ordered afterServletWebServerFactoryAutoConfiguration. -
WebMvcAutoConfigurationwires up MVC:HandlerMapping,HandlerAdapter,HttpMessageConverters. Opt out via@EnableWebMvc. -
ErrorMvcAutoConfiguration—/error+BasicErrorController. This isn't MVC; it's a Boot add-on. -
ServletWebServerFactoryCustomizerapplies theserver.*properties.@Order(0)— user-defined customizers run after it. - The reactive stack:
NettyReactiveWebServerFactory+DispatcherHandler. A different model, different APIs. 10 Boot 4: modularization (47 modules),spring-boot-starter-webmvc, Undertow removed, packages relocated. The mechanism itself — unchanged.
Top comments (0)