# Mastering the Transition: Navigating Java 17, Spring 6, and Spring Boot 3 Upgrades

This article was authored by [Vidhyadharan Deivamani](https://www.linkedin.com/in/vidhyadharan/)

  
He has over 15 years of experience with robust background in Java and front-end development, specializing in complex architectures and API management. He seamlessly integrated technologies like HUGO and Angular 6, developed multi-tenant web apps with Spring Boot, and orchestrated secure environments using Keycloak OAuth2/OpenID. His expertise extends to DevOps practices, CI/CD pipelines, K8S and ensuring code quality through code reviews and static code analysis tools. He also regular speaker and participant of Java User Group Chennai.

## [**Introduction**](https://www.linkedin.com/in/vidhyadharan/overlay/about-this-profile/)

[Naviga](https://www.linkedin.com/in/vidhyadharan/overlay/about-this-profile/)ting the Java 17, Spring 6, and Spring Boot 3 Upgrade Journey can be a challenging yet rewarding endeavor. As you embark on this journey, it’s crucial to understand the scope of the upgrade and the key considerations involved in each component’s migration. In this article, we’ll delve into essential aspects of upgrading, including migrating from Java EE to Jakarta EE in Spring 6, updating Hibernate configurations for Java 17 and Spring 6, upgrading API documentation from Swagger to OpenAPI, transitioning to Apache HttpClient 5, and migrating to Java 17 with Spring Boot 3 using OpenRewrite.

## **Migrating from Java EE to Jakarta EE in Spring 6**

When upgrading to Spring 6 and Spring Boot 3, compatibility with Java EE or Jakarta EE APIs is paramount. This entails updating imports and configurations to align with namespace changes. For instance, understanding the mapping between Java EE and Jakarta EE namespaces is crucial for a seamless migration:

| Java EE Namespace | Jakarta EE Namespace |
| --- | --- |
| javax.servlet | jakarta.servlet |
| javax.servlet.http | jakarta.servlet.http |
| javax.servlet.annotation | jakarta.servlet.annotation |
| javax.servlet.descriptor | jakarta.servlet.descriptor |
| javax.servlet.jsp | jakarta.servlet.jsp |
| javax.servlet.jsp.el | jakarta.servlet.jsp.el |
| javax.servlet.jsp.tagext | jakarta.servlet.jsp.tagext |
| javax.websocket | jakarta.websocket |
| javax.websocket.server | jakarta.websocket.server |
| javax.xml.\* | jakarta.xml.\* |
| javax.activation | jakarta.activation |
| javax.annotation | jakarta.annotation |
| javax.enterprise | jakarta.enterprise |
| javax.jms | jakarta.jms |
| javax.jws | jakarta.jws |
| javax.mail | jakarta.mail |
| javax.management | jakarta.management |
| javax.persistence | jakarta.persistence |
| javax.security.\* | jakarta.security.\* |
| javax.transaction | jakarta.transaction |
| javax.validation | jakarta.validation |
| javax.websocket | jakarta.websocket |
| javax.xml.\* | jakarta.xml.\* |

## **Hibernate Update in Java 17 and Spring 6**

Upgrading to Java 17 and Spring 6 necessitates considerations for updating Hibernate configurations. Key points to note include updating the Hibernate dialect and annotations for defining custom types. Ensuring compatibility with the latest Hibernate versions and adhering to evolving best practices is essential.

1. Hibernate Dialect Update: The `org.hibernate.dialect.MySQLDialect` is supported from Hibernate 5.3 onwards. Prior to Hibernate 5.3, it was recommended to use `org.hibernate.dialect.MySQL57Dialect` for MySQL 5.x and 8.x. However, with the release of Hibernate 5.3, a `org.hibernate.dialect.MySQL8Dialect` was introduced, and it is recommended to use `org.hibernate.dialect.MySQLDialect` since `Hibernate 6`, as `org.hibernate.dialect.MySQL8Dialect` is deprecated.
    
2. Type and TypeDef Annotations Update: In Hibernate 6, there are changes in the annotations used for defining custom types. For example, the usage of `@TypeDef` and `@Type` annotations has been updated.
    

```xml
<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-60</artifactId>
    <version><!-- specify version --></version>
</dependency>
```

Instead of:

```java
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;

@TypeDef(name = "json", typeClass = StringJsonUserType.class)
public class EntityName {

 @Type(type = "json")
 private propertyName
}
```

You should now use:

```java
import jakarta.persistence.Convert;
import org.hibernate.annotations.JdbcTypeCode;

@Convert(attributeName = "entityAttrName", converter = StringJsonUserType.class)
public class EntityName {

 @JdbcTypeCode(SqlTypes.JSON)
 private propertyName
}
```

## **API documentation upgrade swagger to OpenAPI**

Migrating from Swagger annotations to OpenAPI annotations involves updating your codebase to use the newer annotations provided by the `springdoc-openapi` library, as well as adjusting any configuration or usage accordingly. Here’s a list of some common changes you may need to make:

| Swagger Annotation | OpenAPI (springdoc-openapi) Annotation |
| --- | --- |
| `io.swagger.annotations.ApiIgnore` | `org.springdoc.core.annotations.Hidden` |
| `io.swagger.annotations.ApiModelProperty` | `io.swagger.v3.oas.annotations.media.Schema` |
| `io.swagger.annotations.ApiParam` | `io.swagger.v3.oas.annotations.Parameter` |
| `io.swagger.annotations.ApiOperation` | `io.swagger.v3.oas.annotations.Operation` |
| `io.swagger.annotations.ApiResponse` | `io.swagger.v3.oas.annotations.responses.ApiResponse` |
| `io.swagger.annotations.ApiModel` | `io.swagger.v3.oas.annotations.media.Schema` |
| `io.swagger.annotations.ApiImplicitParams` | `io.swagger.v3.oas.annotations.parameters.Parameters` |
| `io.swagger.annotations.ApiResponses` | `io.swagger.v3.oas.annotations.responses.ApiResponse` |

Swagger Annotation: @ApiIgnore

```java
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiIgnore;

@Api(tags = "Sample Controller")
public class SampleController {

    @ApiIgnore
    public void ignoredMethod() {
        // This method will be ignored in Swagger documentation
    }
}
```

OpenAPI (springdoc-openapi) Annotation: @[Hidden](@HiddenObjects)

```java
import org.springdoc.core.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "Sample Controller")
public class SampleController {

    @Hidden
    public void ignoredMethod() {
        // This method will be hidden in OpenAPI documentation
    }
}
```

Swagger Annotation: @ApiModelProperty

```java

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
public class SampleModel {

    @ApiModelProperty(value = "The ID of the entity", example = "1")
    private Long id;

    @ApiModelProperty(value = "The name of the entity")
    private String name;
}
```

OpenAPI (springdoc-openapi) Annotation: @[Schema Flor](@schemaflor)

```java

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

@Data
public class SampleModel {

    @Schema(description = "The ID of the entity", example = "1")
    private Long id;

    @Schema(description = "The name of the entity")
    private String name;
}
```

## **Upgrading to Apache HttpClient 5**

#### **Key Differences:**

* `Package Names`: HttpClient 5 uses `org.apache.hc.client5.http` and related packages, whereas HttpClient 4 uses `org.apache.http.`
    
* `Class Structure`: HttpClient 5 introduces new classes and restructures existing ones for configuration and connection management.
    
* `SSL Context Handling`: HttpClient 5 provides a different approach for SSL context handling using `SSLConnectionSocketFactoryBuilder`.
    
* `Connection Pooling Configuration`: Configuration related to connection pooling is done differently in `HttpClient 5` with `PoolingHttpClientConnectionManagerBuilder`.
    
* `Proxy Configuration`: HttpClient 5 proxy configuration involves changes in how `HttpHost` is used.
    

```java
import org.apache.http.HttpHost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.env.Environment;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.StringUtils;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

@Configuration
public class RestTemplateConfiguration {
  
    private final static Logger logger = LoggerFactory.getLogger(RestTemplateConfiguration.class);
    
    @Autowired
    private Environment environment;
    
    @Autowired
    private ApplicationProperties appconfig;
    
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public ClientHttpRequestFactory clientHttpRequestFactory() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        HttpComponentsClientHttpRequestFactory requestFactory = null;
        
        String proxyHost = environment.getProperty("proxy.host");
        String proxyPortStr = environment.getProperty("proxy.port");
        Integer proxyPort = null;
        
        if (!StringUtils.isEmpty(proxyPortStr)) {
            proxyPort = Integer.valueOf(proxyPortStr);
        }
        
        // Proxy
        if (!StringUtils.isEmpty(proxyHost) && proxyPort != null) {
            requestFactory = new HttpComponentsClientHttpRequestFactory(
                HttpClients.custom()
                    .setProxy(new HttpHost(proxyHost, proxyPort))
                    .build());
            logger.info("Using proxy {}:{}", proxyHost, proxyPort);
        } else {
            requestFactory = new HttpComponentsClientHttpRequestFactory();
        }
        
        requestFactory.setConnectTimeout(appconfig.getConfig().getConnectTimeout());
        requestFactory.setReadTimeout(appconfig.getConfig().getReadTimeout());

        // SSL
        TrustStrategy acceptingTrustStrategy = (chain, authType) -> true;
        SSLContext sslContext = SSLContexts.custom()
            .loadTrustMaterial(null, acceptingTrustStrategy)
            .build();
        
        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
        CloseableHttpClient httpClient = HttpClients.custom()
            .setSSLSocketFactory(csf)
            .build();
        
        requestFactory.setHttpClient(httpClient);

        return requestFactory;
    }
}
```

[view raw](https://gist.github.com/vidhya03/a41a85bc38e3f295321bde42bb57146b/raw/3841ecf0183dc357f690d2fb7b086628e32434b3/RestTemplateLegacy.java)[RestTemplateLegacy.java](https://gist.github.com/vidhya03/a41a85bc38e3f295321bde42bb57146b#file-resttemplatelegacy-java) hosted with ❤ by [GitHub](https://github.com/)

New updated migrated code

```java
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.StandardCookieSpec;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
import org.apache.hc.core5.pool.PoolReusePolicy;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.apache.http.HttpHost;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.env.Environment;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.StringUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

@Configuration
public class RestTemplateConfiguration {

    private final static Logger logger = LoggerFactory.getLogger(RestTemplateConfiguration.class);

    @Autowired
    private Environment environment;

    @Autowired
    private ApplicationProperties appconfig;

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public ClientHttpRequestFactory clientHttpRequestFactory() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException, IOException {
        CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(getConnectionManager())
            .setDefaultRequestConfig(RequestConfig.custom()
                .setCookieSpec(StandardCookieSpec.STRICT)
                .build())
            .build();

        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();

        String proxyHost = environment.getProperty("proxy.host");
        String proxyPortStr = environment.getProperty("proxy.port");
        Integer proxyPort = null;
        if (!StringUtils.isEmpty(proxyPortStr)) {
            proxyPort = Integer.valueOf(proxyPortStr);
        }

        // Proxy
        if (!StringUtils.isEmpty(proxyHost) && proxyPort != null) {
            requestFactory = new HttpComponentsClientHttpRequestFactory(
                HttpClients.custom()
                    .setProxy(new org.apache.hc.core5.http.HttpHost(proxyHost, proxyPort))
                    .build());
            logger.info("Using proxy {}:{}", proxyHost, proxyPort);
        } else {
            requestFactory = new HttpComponentsClientHttpRequestFactory();
        }
        return requestFactory;
    }

    public PoolingHttpClientConnectionManager getConnectionManager() throws IOException {
        TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;

        SSLContext sslContext = null;
        try {
            sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
        } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
            throw new IOException(e);
        }

        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
        PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
            .setSSLSocketFactory(SSLConnectionSocketFactoryBuilder.create()
                .setSslContext(org.apache.hc.core5.ssl.SSLContexts.createSystemDefault())
                .setTlsVersions(TLS.V_1_3)
                .build())
            .setSSLSocketFactory(csf)
            .setDefaultSocketConfig(SocketConfig.custom()
                .setSoTimeout(Timeout.ofMinutes(10))
                .build())
            .setPoolConcurrencyPolicy(PoolConcurrencyPolicy.STRICT)
            .setConnPoolPolicy(PoolReusePolicy.LIFO)
            .setDefaultConnectionConfig(ConnectionConfig.custom()
                .setSocketTimeout(Timeout.ofMilliseconds(appconfig.getConfig().getReadTimeout()))
                .setConnectTimeout(Timeout.ofMilliseconds(appconfig.getConfig().getConnectTimeout()))
                .setTimeToLive(TimeValue.ofMinutes(10))
                .build())
            .build();
        return connectionManager;
    }
}
```

[view raw](https://gist.github.com/vidhya03/a41a85bc38e3f295321bde42bb57146b/raw/3841ecf0183dc357f690d2fb7b086628e32434b3/RestTemplateHttp5Upgrade.java)[RestTemplateHttp5Upgrade.java](https://gist.github.com/vidhya03/a41a85bc38e3f295321bde42bb57146b#file-resttemplatehttp5upgrade-java) hosted with ❤ by [GitHub](https://github.com/)

#### **Additional Notes:**

* WebClient: Remember that WebClient is the recommended alternative to RestTemplate for new applications, as it provides both synchronous and asynchronous capabilities.
    
* RestTemplate: However, if you’re working with existing code, RestTemplate remains a viable choice.
    

The new webclient will be

```java
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;

@Configuration
public class WebFluxConfig implements WebFluxConfigurer {

    private static final Logger logger = LoggerFactory.getLogger(WebFluxConfig.class);

    @Autowired
    private RestTimeoutConfiguration timeoutConfiguration;

    @Bean
    public WebClient getWebClient() {
        HttpClient httpClient = HttpClient
            .create()
            .tcpConfiguration(client ->
                client
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeoutConfiguration.getConnectTimeout())
                    .doOnConnected(conn ->
                        conn
                            .addHandlerLast(new ReadTimeoutHandler(timeoutConfiguration.getReadTimeout()))
                            .addHandlerLast(new WriteTimeoutHandler(timeoutConfiguration.getWriteTimeout()))
                    )
            );

        ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient.wiretap(true));
        logger.info("WebClient initialized");
        return WebClient
            .builder()            
            .clientConnector(connector)
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .build();
    }
}
```

[view raw](https://gist.github.com/vidhya03/5d21de835b5fc3a11d4df9bc2b6f6fcf/raw/5dcc6d7b09a8b099f1c2de0334ca039b4ccebb32/WebFluxConfig.java)[WebFluxConfig.java](https://gist.github.com/vidhya03/5d21de835b5fc3a11d4df9bc2b6f6fcf#file-webfluxconfig-java) hosted with ❤ by [GitHub](https://github.com/)

## **Migrating to Java 17 with Spring Boot 3 using OpenRewrite**

#### **Introduction to OpenRewrite**

OpenRewrite is a powerful tool for automating code refactoring and transformation tasks in Java projects. It provides a flexible framework for writing custom rules to analyze and modify Java code programmatically. In this section, we’ll explore how to integrate OpenRewrite into both Maven and Gradle projects to facilitate the migration from Java 11 to Java 17, along with Spring Boot 3.

```java
// Before OpenRewrite
import javax.servlet.http.HttpServletRequest;
...
```

```java
// After OpenRewrite
import jakarta.servlet.http.HttpServletRequest;
...
```

#### **Integrating OpenRewrite into Maven Projects**

#### **Step 1: Add OpenRewrite Plugin to** `pom.xml`

To use OpenRewrite in a Maven project, first, add the OpenRewrite Maven plugin to your project’s `pom.xml`:

```xml
<project>
  <build>
    <plugins>
      <plugin>
        <groupId>org.openrewrite.maven</groupId>
        <artifactId>rewrite-maven-plugin</artifactId>
        <version>5.27.0</version>
        <configuration>
          <activeRecipes>
            <recipe>org.openrewrite.java.migrate.UpgradeToJava17</recipe>
          </activeRecipes>
        </configuration>
        <dependencies>
          <dependency>
            <groupId>org.openrewrite.recipe</groupId>
            <artifactId>rewrite-migrate-java</artifactId>
            <version>2.11.0</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
</project>
```

Run `mvn rewrite:run` to run the recipe. alternatively

```plaintext
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-migrate-java:RELEASE -Drewrite.activeRecipes=org.openrewrite.java.migrate.UpgradeToJava17
```

#### **Integrating OpenRewrite into Gradle Projects**

#### **Step 1: Add OpenRewrite Plugin to gradle**

* Add the following to your `build.gradle` file:
    
    ```plaintext
    plugins {
        id("org.openrewrite.rewrite") version("6.11.2")
    }
    
    rewrite {
        activeRecipe("org.openrewrite.java.migrate.UpgradeToJava17")
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        rewrite("org.openrewrite.recipe:rewrite-migrate-java:2.11.0")
    }
    ```
    

* Run `gradle rewriteRun` to run the recipe.
