Backend Development 9 min read

Build a Custom Spring Boot 3 Application with Embedded Tomcat – Step‑by‑Step Guide

This article walks through creating a Spring Boot 3 application from scratch, demonstrating how to customize the ApplicationContext, embed a Tomcat server, define a custom SpringApplication class, configure web components, and test REST endpoints, providing complete code snippets and explanations for each step.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Build a Custom Spring Boot 3 Application with Embedded Tomcat – Step‑by‑Step Guide

Overview

Spring Boot is an open‑source, widely used Java framework developed by the Pivotal team to simplify the creation and deployment of Spring applications. It follows the convention‑over‑configuration principle, offering automatic configuration, embedded web servers, powerful dependency management, and built‑in security features, enabling developers to quickly build and run applications.

Hands‑on Implementation

2.1 Custom Web‑type ApplicationContext

<code>public class PackAnnotationApplicationContext extends GenericWebApplicationContext {
    public PackAnnotationApplicationContext() {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this);
    }

    @Override
    protected void onRefresh() {
        super.onRefresh();
        try {
            // Create WebServer
            createWebServer();
        } catch (Exception e) {
            throw new ApplicationContextException("Unable to start web server", e);
        }
    }

    @Override
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        beanFactory.addBeanPostProcessor(new PackServletContextAwareProcessor(this));
        super.postProcessBeanFactory(beanFactory);
    }

    private void createWebServer() throws Exception {
        final int PORT = 8088;
        Tomcat tomcat = new Tomcat();
        // Server node
        Server server = tomcat.getServer();
        // Service node
        Service service = server.findService("Tomcat");
        // Connector configuration
        Http11NioProtocol protocol = new Http11NioProtocol();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        protocol.setExecutor(executor);
        protocol.setConnectionTimeout(20000);
        Connector connector = new Connector(protocol);
        connector.setPort(PORT);
        connector.setURIEncoding("UTF-8");
        service.addConnector(connector);
        // Engine configuration
        StandardEngine engine = new StandardEngine();
        engine.setDefaultHost("localhost");
        StandardHost host = new StandardHost();
        host.setName("localhost");
        host.setAppBase(System.getProperties().getProperty("user.home"));
        engine.addChild(host);
        service.setContainer(engine);
        // Context configuration
        StandardContext context = new StandardContext();
        context.addLifecycleListener(new FixContextListener());
        context.setPath("");
        host.addChild(context);
        context.addServletContainerInitializer(new ServletContainerInitializer() {
            @Override
            public void onStartup(Set<Class<?>> c, ServletContext servletContext) throws ServletException {
                PackAnnotationApplicationContext.this.setServletContext(servletContext);
                DispatcherServlet dispatcherServlet = PackAnnotationApplicationContext.this.getBean(DispatcherServlet.class);
                Dynamic dynamic = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
                dynamic.setLoadOnStartup(0);
                dynamic.addMapping("/*");
            }
        }, null);
        tomcat.start();
    }
}
</code>

2.2 Custom PackSpringApplication

<code>public class PackSpringApplication {
    private Class<?>[] primarySources;

    public PackSpringApplication(Class<?>[] primarySources) {
        this.primarySources = primarySources;
    }

    public ConfigurableApplicationContext run(String[] args) {
        ConfigurableApplicationContext context = new PackAnnotationApplicationContext();
        prepareEnvironment(context, args);
        prepareContext(context);
        refreshContex(context);
        return context;
    }

    private void refreshContex(ConfigurableApplicationContext context) {
        context.refresh();
    }

    private void prepareEnvironment(ConfigurableApplicationContext context, String[] args) {
        ConfigurableEnvironment environment = new StandardEnvironment();
        PropertySource<?> propertySource = new SimpleCommandLinePropertySource(args);
        environment.getPropertySources().addFirst(propertySource);
        context.setEnvironment(environment);
    }

    private void prepareContext(ConfigurableApplicationContext context) {
        BeanDefinitionRegistry registry = getBeanDefinitionRegistry(context);
        BeanNameGenerator generator = new DefaultBeanNameGenerator();
        for (Class<?> clazz : primarySources) {
            AbstractBeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(clazz).getBeanDefinition();
            registry.registerBeanDefinition(generator.generateBeanName(definition, registry), definition);
        }
    }

    private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
        if (context instanceof BeanDefinitionRegistry) {
            return (BeanDefinitionRegistry) context;
        }
        if (context instanceof AbstractApplicationContext) {
            return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
        }
        throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
    }

    public static ConfigurableApplicationContext run(Class<?> primarySource, String[] args) {
        return new PackSpringApplication(new Class<?>[]{primarySource}).run(args);
    }
}
</code>

2.3 Web Configuration

<code>@Configuration
public class WebConfig {
    @Bean
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(true);
        dispatcherServlet.setDispatchTraceRequest(true);
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
        dispatcherServlet.setPublishEvents(true);
        dispatcherServlet.setEnableLoggingRequestDetails(true);
        return dispatcherServlet;
    }
}
</code>

2.4 Custom RequestMappingHandlerAdapter

<code>@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
    List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
    messageConverters.add(new StringHttpMessageConverter());
    messageConverters.add(new MappingJackson2HttpMessageConverter());
    handlerAdapter.setMessageConverters(messageConverters);
    return handlerAdapter;
}
</code>

2.5 Testing Controllers

<code>@RestController
@RequestMapping("/demo")
public class DemoController {
    @GetMapping("/index")
    public Object index() {
        return "index......";
    }

    @GetMapping("/body")
    public User body() {
        return new User(1, "测试");
    }
}
</code>
<code>@Configuration
@ComponentScan({"com.pack.main.programmatic_tomcat_04"})
public class DemoApplication {
    public static void main(String[] args) {
        PackSpringApplication.run(DemoApplication.class, args);
    }
}
</code>

Running the application and accessing /demo/index and /demo/body initially returns a 406 error because the default RequestMappingHandlerAdapter cannot handle the custom return type. By defining a custom RequestMappingHandlerAdapter bean with appropriate HttpMessageConverter s, the endpoints respond correctly.

After adding the custom adapter and retesting, the endpoints return the expected JSON and plain‑text responses, completing a minimal yet functional Spring Boot application built programmatically.

Javabackend developmentSpring BootREST APIEmbedded Tomcat
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.