How to Build a Reusable Spring Boot Backend Template for Fast Project Kickoff

This article walks through the author's experience of creating a comprehensive Spring Boot backend template—including project scaffolding, one‑click scripts, business‑oriented package layout, automated test classification, logging, exception handling, distributed locks, Swagger API docs, Flyway migrations, multi‑environment builds, CORS setup, and static code checks—so new developers can start coding with minimal friction.

Java Web Project
Java Web Project
Java Web Project
How to Build a Reusable Spring Boot Backend Template for Fast Project Kickoff

Project Template Overview

A Spring Boot starter project is provided to standardise scaffolding, testing, logging, error handling, background jobs, API documentation, database migration, environment profiles, CORS and static analysis.

git clone https://github.com/e-commerce-sample/order-backend git checkout a443dace

One‑Click Local Build

Three helper scripts simplify the developer workflow: idea.sh – generates an IntelliJ project and opens it. run.sh – starts the application, launches a local MySQL container and opens port 5005 for debugging. local-build.sh – runs the Gradle build; only successful builds may be committed.

Typical workflow:

Pull the code.

Run idea.sh to open the IDE.

Write business code and tests.

Optionally run run.sh for manual testing.

Run local-build.sh to verify the build.

Pull again, ensure the build succeeds, then commit.

#!/usr/bin/env bash
./gradlew clean bootRun

Directory Structure

order-backend/
├── gradle/               # Gradle configuration (e.g., checkstyle)</n├── src/                  # Java source code
├── idea.sh               # IntelliJ project generator
├── run.sh                # Local run script
└── local-build.sh        # Pre‑commit build script

Business‑Oriented Package Layout

Code is grouped by domain aggregates rather than technical layers.

order/
│   ├── OrderApplicationService.java
│   ├── OrderController.java
│   ├── OrderNotFoundException.java
│   ├── OrderRepository.java
│   ├── OrderService.java
│   └── model/
│       ├── Order.java
│       ├── OrderFactory.java
│       ├── OrderId.java
│       ├── OrderItem.java
│       └── OrderStatus.java

product/
│   ├── Product.java
│   ├── ProductApplicationService.java
│   ├── ProductController.java
│   ├── ProductId.java
│   └── ProductRepository.java

common/
    ├── configuration/
    ├── exception/
    ├── logging/
    └── utils/

Automated Test Classification

Three test types are defined and isolated via Gradle sourceSets:

sourceSets {
    componentTest {
        compileClasspath += sourceSets.main.output + sourceSets.test.output
        runtimeClasspath += sourceSets.main.output + sourceSets.test.output
    }
    apiTest {
        compileClasspath += sourceSets.main.output + sourceSets.test.output
        runtimeClasspath += sourceSets.main.output + sourceSets.test.output
    }
}

Corresponding directories:

Unit tests: src/test/java Component tests: src/componentTest/java API tests: src/apiTest/java Component and API tests require a MySQL container; the Gradle docker‑compose plugin starts it automatically:

apply plugin: 'docker-compose'

dockerCompose {
    useComposeFiles = ['docker/mysql/docker-compose.yml']
}

bootRun.dependsOn composeUp
componentTest.dependsOn composeUp
apiTest.dependsOn composeUp

Log Handling

Two enhancements are added:

Request‑ID injection via Logback MDC for traceability.

Centralised log aggregation using a Redis appender that forwards logs to Logstash.

protected void doFilterInternal(HttpServletRequest request,
                                HttpServletResponse response,
                                FilterChain filterChain)
        throws ServletException, IOException {
    String headerRequestId = request.getHeader(HEADER_X_REQUEST_ID);
    MDC.put(REQUEST_ID, isNullOrEmpty(headerRequestId) ? newUuid() : headerRequestId);
    try {
        filterChain.doFilter(request, response);
    } finally {
        clearMdc();
    }
}
<appender name="REDIS" class="com.cwbase.logback.RedisAppender">
    <tags>ecommerce-order-backend-${ACTIVE_PROFILE}</tags>
    <host>elk.yourdomain.com</host>
    <port>6379</port>
    <password>whatever</password>
    <key>ecommerce-ordder-log</key>
    <mdc>true</mdc>
    <type>redis</type>
</appender>

Exception Handling

A hierarchical model uses a base AppException that carries an ErrorCode enum, HTTP status and a data map.

public abstract class AppException extends RuntimeException {
    private final ErrorCode code;
    private final Map<String, Object> data = newHashMap();
}

Specific exception example:

public class OrderNotFoundException extends AppException {
    public OrderNotFoundException(OrderId orderId) {
        super(ErrorCode.ORDER_NOT_FOUND,
              ImmutableMap.of("orderId", orderId.toString()));
    }
}

Uniform error response:

{
  "requestId": "d008ef46bb4f4cf19c9081ad50df33bd",
  "error": {
    "code": "ORDER_NOT_FOUND",
    "status": 404,
    "message": "没有找到订单",
    "path": "/order",
    "timestamp": 1555031270087,
    "data": {"orderId": "123456789"}
  }
}

Background Tasks and Distributed Lock

Spring async and scheduling are enabled with a custom thread pool. ShedLock provides a lightweight JDBC‑based distributed lock.

@Configuration
@EnableAsync
@EnableScheduling
public class SchedulingConfiguration implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(newScheduledThreadPool(10));
    }

    @Bean(destroyMethod = "shutdown")
    @Primary
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(10);
        executor.setTaskDecorator(new LogbackMdcTaskDecorator());
        executor.initialize();
        return executor;
    }
}
@Configuration
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class DistributedLockConfiguration {
    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(dataSource);
    }

    @Bean
    public DistributedLockExecutor distributedLockExecutor(LockProvider lockProvider) {
        return new DistributedLockExecutor(lockProvider);
    }
}
public class DistributedLockExecutor {
    private final LockProvider lockProvider;

    public DistributedLockExecutor(LockProvider lockProvider) {
        this.lockProvider = lockProvider;
    }

    public <T> T executeWithLock(Supplier<T> supplier, LockConfiguration configuration) {
        Optional<SimpleLock> lock = lockProvider.lock(configuration);
        if (!lock.isPresent()) {
            throw new LockAlreadyOccupiedException(configuration.getName());
        }
        try {
            return supplier.get();
        } finally {
            lock.get().unlock();
        }
    }
}

Usage example:

public String doBusiness() {
    return distributedLockExecutor.executeWithLock(
        () -> "Hello World.",
        new LockConfiguration("key", Instant.now().plusSeconds(60)));
}

Unified Code Style

Conventions enforced via Checkstyle and team guidelines include:

Request DTO suffix Command, response DTO suffix Representation.

Consistent three‑layer or DDD tactical architecture.

Standardised pagination classes.

Shared test‑base class and explicit test classification.

Static Code Checks

Gradle plugins used for continuous quality monitoring:

Checkstyle – code format.

SpotBugs – static analysis.

OWASP Dependency‑Check – dependency security.

Sonar – code quality dashboard.

Health Check Endpoint

A lightweight endpoint returns HTTP 200 with build metadata:

{
  "requestId": "698c8d29add54e24a3d435e2c749ea00",
  "buildNumber": "unknown",
  "buildTime": "unknown",
  "deployTime": "2019-04-11T13:05:46.901+08:00[Asia/Shanghai]",
  "gitRevision": "unknown",
  "gitBranch": "unknown",
  "environment": "[local]"
}

API Documentation

Swagger (Springfox) automatically generates up‑to‑date API docs.

@Configuration
@EnableSwagger2
@Profile({"local", "dev"})
public class SwaggerConfiguration {
    @Bean
    public Docket api() {
        return new Docket(SWAGGER_2)
                .select()
                .apis(basePackage("com.ecommerce.order"))
                .paths(any())
                .build();
    }
}

Running the application and visiting http://localhost:8080/swagger-ui.html displays the interactive UI.

Database Migration

Flyway manages schema evolution. Migration scripts live under src/main/resources/db/migration and follow the V{version}__{description}.sql naming convention.

resources/
└── db/
    └── migration/
        ├── V1__init.sql
        └── V2__create_product_table.sql

Scripts must not be edited after they have been applied because Flyway validates checksums.

Multi‑Environment Build

Six profiles are defined, each with its own configuration files: local – developer workstation. ci – continuous‑integration builds. dev – front‑end integration. qa – QA testing. uat – staging/acceptance. prod – production.

CORS Configuration

Global CORS mapping for Spring Boot:

@Configuration
public class CorsConfiguration {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }
}

If Spring Security is used, the CORS source must be applied before the security filter chain:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and();
        // other security config …
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

Common Third‑Party Libraries

Guava

Apache Commons

Mockito

DBUnit

Rest Assured

Jackson 2

jjwt

Lombok

Feign

Tika

iText

Zxing

XStream

Java Web Project
Written by

Java Web Project

Focused on Java backend technologies, trending internet tech, and the latest industry developments. The platform serves over 200,000 Java developers, inviting you to learn and exchange ideas together. Check the menu for Java learning resources.

0 followers
Reader feedback

How this landed with the community

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.