How to Upgrade the Mall Project to Spring Boot 3 & JDK 17 – Complete Guide
This article walks through upgrading the open‑source Mall e‑commerce system to Spring Boot 3 and JDK 17, covering dependency version updates, migration from SpringFox to SpringDoc, new Spring Data Elasticsearch usage, revised Spring Security configuration, Docker deployment tips, and essential code changes.
Mall Project Overview
The Mall project is a SpringBoot + Vue + uni‑app e‑commerce system with Dockerized deployment, boasting over 60K GitHub stars. It includes a front‑end store and a back‑end admin system that support the full order workflow, covering products, orders, carts, permissions, coupons, members, payments, and more.
Project address: https://github.com/macrozheng/mall
Video tutorials: https://www.macrozheng.com/video/
Upgraded Versions
All dependencies have been upgraded to the latest mainstream versions:
SpringBoot: 2.7.5 → 3.2.2 (Java application framework)
SpringSecurity: 5.7.4 → 6.2.1 (Authentication and authorization framework)
MyBatis: 3.5.10 → 3.5.14 (ORM framework)
MyBatisGenerator: 1.4.1 → 1.4.2 (Data‑layer code generator)
SpringDataRedis: 2.7.5 → 3.2.2 (Redis data‑operation framework)
SpringDataElasticsearch: 4.4.5 → 5.2.2 (Elasticsearch data‑operation framework)
SpringDataMongoDB: 3.4.5 → 4.2.2 (MongoDB data‑operation framework)
Druid: 1.2.14 → 1.2.21 (Database connection pool)
Hutool: 5.8.9 → 5.8.16 (Java utility library)
PageHelper: 5.3.2 → 6.1.0 (MyBatis pagination plugin)
Swagger‑UI: SpringFox → SpringDoc (API documentation tool)
logstash‑logback‑encoder: 7.2 → 7.4 (Logstash log collector plugin)
docker‑maven‑plugin: 0.40.2 → 0.43.3 (Maven plugin for building Docker images)
New Usage After Upgrade
During the upgrade to Spring Boot 3, several frameworks changed their usage. Notably, the API documentation library switched from SpringFox to SpringDoc, and both Spring Data Elasticsearch and Spring Security have new APIs.
Migrating from SpringFox to SpringDoc
SpringFox no longer supports Spring Boot 3, so migration to SpringDoc is required.
After migration, add SpringDoc configuration to application.yml:
springdoc:
swagger-ui:
path: /swagger-ui.html
enabled: true
doc-expansion: 'none'
api-docs:
path: /v3/api-docs
enabled: true
group-configs:
- group: 'default'
packages-to-scan: com.macro.mall.controller
default-flat-param-object: falseJava configuration must also be updated. Example SpringDocConfig class:
@Configuration
public class SpringDocConfig implements WebMvcConfigurer {
private static final String SECURITY_SCHEME_NAME = "Authorization";
@Bean
public OpenAPI mallAdminOpenAPI() {
return new OpenAPI()
.info(new Info().title("mall后台系统")
.description("mall后台相关接口文档")
.version("v1.0.0")
.license(new License().name("Apache 2.0").url("https://github.com/macrozheng/mall-learning")))
.externalDocs(new ExternalDocumentation()
.description("SpringBoot实战电商项目mall(60K+Star)全套文档")
.url("http://www.macrozheng.com"))
.addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME))
.components(new Components()
.addSecuritySchemes(SECURITY_SCHEME_NAME,
new SecurityScheme()
.name(SECURITY_SCHEME_NAME)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// Configure /swagger-ui/ to redirect to /swagger-ui/index.html
registry.addViewController("/swagger-ui/").setViewName("redirect:/swagger-ui/index.html");
}
}Annotation mapping from SpringFox to SpringDoc:
@Api → @Tag
@ApiIgnore → @Parameter(hidden = true) / @Operation(hidden = true) / @Hidden
@ApiImplicitParam → @Parameter
@ApiImplicitParams → @Parameters
@ApiModel / @ApiModelProperty → @Schema
@ApiOperation → @Operation
@ApiParam → @Parameter
@ApiResponse → ApiResponse
When generating documentation with SpringDoc, the Bearer prefix is added automatically, so you no longer need to include it in request headers.
Spring Data Elasticsearch New Usage
Simple queries via ElasticsearchRepository remain unchanged. For complex queries, ElasticsearchRestTemplate has been removed; use ElasticsearchTemplate instead (see EsProductServiceImpl ).
/**
* 搜索商品管理Service实现类
* Created by macro on 2018/6/19.
*/
@Service
public class EsProductServiceImpl implements EsProductService {
private static final Logger LOGGER = LoggerFactory.getLogger(EsProductServiceImpl.class);
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Override
public Page<EsProduct> search(String keyword, Long brandId, Long productCategoryId, Integer pageNum, Integer pageSize, Integer sort) {
Pageable pageable = PageRequest.of(pageNum, pageSize);
NativeQueryBuilder nativeQueryBuilder = new NativeQueryBuilder();
// pagination
nativeQueryBuilder.withPageable(pageable);
// filter
if (brandId != null || productCategoryId != null) {
Query boolQuery = QueryBuilders.bool(builder -> {
if (brandId != null) {
builder.must(QueryBuilders.term(b -> b.field("brandId").value(brandId)));
}
if (productCategoryId != null) {
builder.must(QueryBuilders.term(b -> b.field("productCategoryId").value(productCategoryId)));
}
return builder;
});
nativeQueryBuilder.withFilter(boolQuery);
}
// search
if (StrUtil.isEmpty(keyword)) {
nativeQueryBuilder.withQuery(QueryBuilders.matchAll(builder -> builder));
} else {
List<FunctionScore> functionScoreList = new ArrayList<>();
functionScoreList.add(new FunctionScore.Builder()
.filter(QueryBuilders.match(builder -> builder.field("name").query(keyword)))
.weight(10.0)
.build());
functionScoreList.add(new FunctionScore.Builder()
.filter(QueryBuilders.match(builder -> builder.field("subTitle").query(keyword)))
.weight(5.0)
.build());
functionScoreList.add(new FunctionScore.Builder()
.filter(QueryBuilders.match(builder -> builder.field("keywords").query(keyword)))
.weight(2.0)
.build());
FunctionScoreQuery.Builder functionScoreQueryBuilder = QueryBuilders.functionScore()
.functions(functionScoreList)
.scoreMode(FunctionScoreMode.Sum)
.minScore(2.0);
nativeQueryBuilder.withQuery(builder -> builder.functionScore(functionScoreQueryBuilder.build()));
}
// sort
if (sort == 1) {
// newest to oldest
nativeQueryBuilder.withSort(Sort.by(Sort.Order.desc("id")));
} else if (sort == 2) {
// sales high to low
nativeQueryBuilder.withSort(Sort.by(Sort.Order.desc("sale")));
} else if (sort == 3) {
// price low to high
nativeQueryBuilder.withSort(Sort.by(Sort.Order.asc("price")));
} else if (sort == 4) {
// price high to low
nativeQueryBuilder.withSort(Sort.by(Sort.Order.desc("price")));
}
// relevance
nativeQueryBuilder.withSort(Sort.by(Sort.Order.desc("_score")));
NativeQuery nativeQuery = nativeQueryBuilder.build();
LOGGER.info("DSL:{}", nativeQuery.getQuery().toString());
SearchHits<EsProduct> searchHits = elasticsearchTemplate.search(nativeQuery, EsProduct.class);
if (searchHits.getTotalHits() <= 0) {
return new PageImpl<>(ListUtil.empty(), pageable, 0);
}
List<EsProduct> searchProductList = searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList());
return new PageImpl<>(searchProductList, pageable, searchHits.getTotalHits());
}
}ES 7.17.3 remains compatible; ES 8.x also works, but Kibana, Logstash, and the analysis‑ik plugin must be the same major version.
Spring Security New Usage
After upgrading to Spring Boot 3, some dynamic‑permission classes are deprecated. Implement AuthorizationManager instead.
/**
* 动态鉴权管理器,用于判断是否有资源的访问权限
* Created by macro on 2023/11/3.
*/
public class DynamicAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
@Autowired
private DynamicSecurityMetadataSource securityDataSource;
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Override
public void verify(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
AuthorizationManager.super.verify(authentication, object);
}
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext requestAuthorizationContext) {
HttpServletRequest request = requestAuthorizationContext.getRequest();
String path = request.getRequestURI();
PathMatcher pathMatcher = new AntPathMatcher();
// whitelist paths
List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
for (String ignoreUrl : ignoreUrls) {
if (pathMatcher.match(ignoreUrl, path)) {
return new AuthorizationDecision(true);
}
}
// pre‑flight OPTIONS request
if (request.getMethod().equals(HttpMethod.OPTIONS.name())) {
return new AuthorizationDecision(true);
}
// permission check
List<ConfigAttribute> configAttributeList = securityDataSource.getConfigAttributesWithPath(path);
List<String> needAuthorities = configAttributeList.stream()
.map(ConfigAttribute::getAttribute)
.collect(Collectors.toList());
Authentication currentAuth = authentication.get();
if (currentAuth.isAuthenticated()) {
Collection<? extends GrantedAuthority> grantedAuthorities = currentAuth.getAuthorities();
List<? extends GrantedAuthority> hasAuth = grantedAuthorities.stream()
.filter(item -> needAuthorities.contains(item.getAuthority()))
.collect(Collectors.toList());
if (CollUtil.isNotEmpty(hasAuth)) {
return new AuthorizationDecision(true);
} else {
return new AuthorizationDecision(false);
}
} else {
return new AuthorizationDecision(false);
}
}
}Configure SecurityFilterChain with functional style:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired(required = false)
private DynamicAuthorizationManager dynamicAuthorizationManager;
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests(registry -> {
// whitelist URLs
for (String url : ignoreUrlsConfig.getUrls()) {
registry.requestMatchers(url).permitAll();
}
// allow OPTIONS for CORS pre‑flight
registry.requestMatchers(HttpMethod.OPTIONS).permitAll();
})
.authorizeHttpRequests(registry -> registry.anyRequest()
.access(dynamicAuthorizationManager == null ? AuthenticatedAuthorizationManager.authenticated() : dynamicAuthorizationManager))
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(configurer -> configurer
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint))
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
}Other Changes
Package names that start with javax must be changed to jakarta after the Jakarta EE transition.
Spring Boot 3.2 has a Parameter Name Retention issue; add -parameters to the Maven compiler plugin arguments:
<build>
<plugins>
<!-- Resolve SpringBoot 3.2 Parameter Name Retention issue -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>Alternatively, use @Qualifier to specify bean names, but this may prevent Swagger from inferring request‑parameter names.
Running and Deploying
Windows
Spring Boot 3 requires JDK 17. Ensure the project is compiled and run with JDK 17 on Windows; other operations remain the same as previous versions.
Linux / Docker
When building Docker images, use the openjdk:17 base image and adjust the docker‑maven‑plugin configuration in pom.xml. Pull the image beforehand:
docker pull openjdk:17Summary
All framework dependencies have been upgraded to the latest mainstream versions.
Swagger documentation migrated from SpringFox to SpringDoc.
Product search now uses the new Spring Data Elasticsearch approach.
Spring Security configuration updated to the new functional style.
The project must be built and run with JDK 17.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
