To keep up with Spring 6, Spring Boot released 3.0.0 on November 24, 2022. The current version is 3.0.1 (2022-12-23). Spring 6 requires JDK 17+, and naturally Spring Boot 3 requires JDK 17+ to work. For those who have been clinging to JDK 8, upgrading to Spring Boot 3 is a big challenge.

What are the significant features that Spring Boot brings to the table?

  1. Relies on Spring 6, requires Java 17 minimum, Java 19 compatible
  2. support for generating GraalVM native images, replacing the experimental Spring Native project
  3. requires Java EE 9 and supports Jakarta EE 10 as a minimum
  4. dependency migration from Java EE to Jakarta EE API
  5. Upgrade to Tomcat 10

For guidance on upgrading from Spring Boot 2.x to Spring Boot 3, see the official Spring Boot 3.0 Migration Guide.

For Spring Boot 1 projects, you must first upgrade to Spring Boot 2. For early Spring Boot 2, the first step is to upgrade to Spring Boot 2.7.x. Then change the JDK to version 17 or later, compile, run, and change the code if you have problems.

The most significant API change is javax to jakarta.servlet, for example

  • javax.servlet -> jakarta.servlet
  • javax.annotations -> jakarta.annotations
  • javax.persistence -> jakarta.persistence
  • javax.transaction -> jakarta.transaction

I have a project with Spring Boot 2.7.6 + JDK 17, upgraded directly to Spring Boot 3.0.1. then tried to compile with mvn compile, due to changes in API package names such as javax.annotation.PostConstruct, javax.servlet.http.HttpServletRequest etc. will cause compilation failure, directly replace javax with jakarta, such as jakarta.annotation.PostConstruct, jakarta.servlet.http.HttpServletRequest will work.

Change javax to jakarta

Finally, it can be compiled by mvn compile, but it will throw an exception at runtime.

1
Parameter 2 of constructor in yanbin.blog.testweb.controllers.ManagementController required a bean of type 'yanbin.blog.testweb.service.CalcEngineFactory' that could not be found.

When I come to the CalcEngineFactory code, it is clear that there is an annotation @Named, why it is not registered as a Spring bean anymore? If I replace the annotation @Named with Spring’s @Component, there is no problem. The problem is solved, let’s find the reason, debug, to org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(String... basePackages) method.

Classes annotated with @Named are not considered SpringBean by the findCandidateComponents(basePackage) method, while those annotated with @Component are. Continue following the method scanCandidateComponents(basePackage), you can see the following code.

1
2
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {

The logic of isCandidateComponent(metadataReader) returns false for classes annotated with @Named and true for classes annotated with @Component.

The isCandidateComponent() method uses excludeFilters and includedFilters to determine true or false.

Focus on this includedFilters, which in Spring Boot 3 contains three AnnotationTypeFilters, which are as follows

  1. annotationType: interface org.springframework.stereotype.Component
  2. annotationType: interface jakarta.annotation.ManagedBean
  3. annotationType: interface jakarta.inject.Named

It is true that there is @Named, but it is not javax.inject.Named, which we used earlier, but jakarta.inject.Named. Since javax.inject.Named comes from the javax.inject:javax.inject dependency, it will compile fine after upgrading to Spring Boot 3, but will throw an exception at runtime.

This is why the @Component annotation solves the problem, but of course the sub-interfaces @Service, @Controller, etc. can also be registered as Spring beans with @Component.

This is why the @Component annotation solves the problem, but of course sub-interfaces that use @Component, such as @Service , @Controller and so on, can also be registered as Spring beans.

If you still insist on @Named, then remove the javax.inject:javax.inject dependency and replace it with the @jakarta.inject.Named annotation. After upgrading to Spring Boot 3, have in mind whether javax should be replaced with jakarta.

If you compare the code comments of the Spring 5 and 6 ClassPathBeanDefinitionScanner classes, it also illustrates the change from @javax.inject.Named to @jakarta.inject.Named.

If you still want Spring to treat classes annotated with @javax.inject.Named as Spring beans, you need to see if you can affect the value of the includeFilters: List<TypeFilter> variable in the org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider. The relevant code in the ComponentScanAnnotationParser class is as follows.

1
2
3
4
5
6
7
8
9
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
            componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
    ......................
 
    else {
        Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
        scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
    }

That uses the useDefaultFilters and includeFilters attributes of the @ComponentScan annotation, so we can add the following configuration to the Spring Boot Java configuration or startup class.

1
@ComponentScan(includeFilters = @ComponentScan.Filter(classes = javax.inject.Named.class))

This will add annotationType: interface javax.inject.Named to the default includeFilters.

Classes annotated with @javax.inject.Named will be treated as Spring beans as well. However, I don’t recommend this approach and try to use the Spring Boot 3 (Spring 6) compliant approach with @jakarta.inject.Named instead.

Finally, annotation classes other than the @Named annotation in javax.inject:javax.inject may also not work properly and require attention.

Annotations in javax.inject:javax.inject

Reference: https://yanbin.blog/upgrade-to-spring-boot-3-javax-inject-named-unvailable/