Software Development

Automatic Refactoring With Spring Boot Migrator

Spring Boot Migrator is a tool to migrate applications to Spring Boot. In this article, we are going to see how it works and how you can use it. This may seem a narrow topic, but it is an interesting example of an application that can manipulate your code. The reason is that the tool deals with transforming code and applications at scale. So, we may learn something new by looking at how it works.

Spring Boot Migrator as Part of Legacy Modernization

In the larger scheme of things, this is the kind of tool you may use in a process of legacy modernization. Legacy modernization in itself is a large topic that could warrant its own book. Here, we just want to help you understand how the tool fits in modernizing an application. Even the best designed projects accumulate technical debt with time, this is simply because the world evolves and therefore best practices change.

For example, the best designed mainframe application is now outdated simply because mainframes are no longer the best platform on which to build applications. You cannot blame whoever designed the application for that, it is just that the world has changed.

So, you are at a point where you have an old codebase to deal with. In some cases you might need to migrate development language, for example passing from COBOL to Java. In some other cases, you might get away migrating from an old framework to a new one. You might even need to do both, in multiple steps. First, you migrate from a legacy language to a new one, then you change the applications themselves.

If you need to change a Java application to a Spring Boot application, there is a tool that can help you with that. This is the best case scenario, you will not always be so lucky for other legacy applications. In such cases you will have to invest all the time and effort to change your application.

What is Spring Boot?

Let’s start from the beginning: Spring is a Java framework. Spring Boot is a project/framework that gets you a complete web app based on Spring.

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.

It automatically configures Spring and wire together a production ready starting project with included a webserver and features like metrics, health checks, etc.

You use Spring if you want to create a tailored Spring application or integrate it with existing software. Instead you use Spring Boot if you want to create a new web application based on Spring following some established conventions.

You can even use the tool Spring Initializr to generate a Spring Boot application, either on the web or in a supported IDE.

Sprint Initializr on the web

Spring Boot Migrator

As the name suggests, Spring Boot Migrator is a tool to migrate existing applications to Spring Boot. Unfortunately, it is not made of magic, so it cannot migrate everything to Spring Boot, the initial project must respect some basic requirements:

  • must be a Maven project (but no Maven Reactor, only one pom.xml)
  • must follow Maven dir layout
  • recent build with mvn clean package and target dir still exists

The tool itself is a simple JAR application, so to run it you only need a system with a JVM version 11+ installed. So, the good news is that you do not need to follow complicated installation procedures just to use it.

Using it is also quite simple, the tool comes with a series of recipes for automatic refactoring. A recipe performs a sequence of operations on your code and configuration files.

You can see the current list of available recipes from the program itself, with the command list.

migrator:> list
 
Found these recipes:
 
🤖    = 'automated recipe'
💪 🤖 = 'partially automated recipe'
💪    = 'manual recipe'
 
 - initialize-spring-boot-migration [🤖]
    -> Initialize an application as Spring Boot application.
 - migrate-jndi-lookup [🤖]
    -> Migrate JNDI lookup using InitialContext to Spring Boot
 - migrate-jpa-to-spring-boot [🤖]
    -> Migrate JPA to Spring Boot
 - migrate-ejb-jar-deployment-descriptor [🤖]
    -> Add or overrides @Stateless annotation as defined in ejb deployment descriptor
 - migrate-weblogic-ejb-deployment-descriptor [🤖]
    -> Migrate weblogic-ejb-jar.xml deployment descriptor
 - mark-and-clean-remote-ejbs [🤖]
    -> Search @Stateless EJBs implementing a @Remote interface
 - migrate-stateless-ejb [🤖]
    -> Migration of stateless EJB to Spring components.
 - migrate-annotated-servlets [🤖]
    -> Allow Spring Boot to deploy servlets annotated with @WebServlet
 - migrate-jax-ws [🤖]
    -> Migrate Jax Web-Service implementation to Spring Boot bases Web-Service
 - migrate-jax-rs [🤖]
    -> Any class has import starting with javax.ws.rs
 - migrate-mule-to-boot [🤖]
    -> Migrate Mulesoft to Spring Boot
 - migrate-tx-to-spring-boot [🤖]
    -> Migration of @TransactionAttribute to @Transactionsl
 - spring-context-xml-import [🤖]
    -> Import Spring Framework xml bean configuration into Java configuration without converting them.
 - migrate-spring-xml-to-java-config [🤖]
    -> Migrate Spring Framework xml bean configuration to Java configuration.
 - sql-access-in-java-ee-apps [💪]
    -> SQL Access in Java EE Apps
 - migrate-jms [🤖]
    -> Convert JEE JMS app into Spring Boot JMS app
 - documentation-actions [🤖]
    -> Create Documentation for Actions
 - migrate-jsf-2.x-to-spring-boot [🤖]
    -> Use joinfaces to integrate JSF 2.x with Spring Boot.
 - bootifying-java-ee-jaxb [💪]
    -> Bootifying Java EE JAXB
 - cn-spring-cloud-config-server [🤖]
    -> Externalize properties to Spring Cloud Config Server
 - boot-2.4-2.5-upgrade-report [🤖]
    -> Create Upgrade Report for a Spring Boot 2.4 Application
 - boot-2.4-2.5-datasource-initializer [🤖]
    -> null
 - boot-2.4-2.5-spring-data-jpa [🤖]
    -> null
 - boot-2.4-2.5-dependency-version-update [🤖]
    -> Update Spring Boot dependencies from 2.4 to 2.5
 - boot-2.4-2.5-sql-init-properties [🤖]
    -> null
 - migrate-raml-to-spring-mvc [🤖]
    -> Create Spring Boot @RestController from .raml files.
 - migrate-boot-2.3-2.4 [🤖]
    -> Migrate from Spring Boot 2.3 to 2.4
 - upgrade-boot-1x-to-2x [🤖]
    -> Migrates Spring Boot 1.x to 2.x including best practices.
 
Run command '> apply recipe-name' to apply a recipe.

Notice that on Windows you might not see the emoji characters indicating the grade of automation of the recipe (e.g., 🤖) instead you might see a question mark.

You can use the command scan to see which recipes can be applied to your existing project and then apply them with the corresponding command. 

Spring Boot Migrator in Action

The Spring Boot Migrator github repository has a few example projects to show what the tool is capable of. If you try the scan command on the the Mulesoft example app, you get these results:

scanning 'spring-amqp-mule'
 
Checked preconditions for 'spring-amqp-mule'
[ok] Found pom.xml.
[ok] 'sbm.gitSupportEnabled' is 'false', Nothing will be committed.
[ok] Required Java version (11) was found.
[ok] Found required source dir 'src/main/java'.
 
 
Maven        100% │████████████████████████│ 3/3 (0:00:01 / 0:00:00)
Java [main]: 100% │████████████████████████│ 3/3 (0:00:00 / 0:00:00)
Xml          100% │████████████████████████│ 2/2 (0:00:00 / 0:00:00)
 
Applicable recipes:
 
🤖    = 'automated recipe'
💪 🤖 = 'partially automated recipe'
💪    = 'manual recipe'
 
 - initialize-spring-boot-migration [🤖]
    -> Initialize an application as Spring Boot application.
 - migrate-mule-to-boot [🤖]
    -> Migrate Mulesoft to Spring Boot
 - cn-spring-cloud-config-server [🤖]
    -> Externalize properties to Spring Cloud Config Server
 - boot-2.4-2.5-upgrade-report [🤖]
    -> Create Upgrade Report for a Spring Boot 2.4 Application
 - boot-2.4-2.5-sql-init-properties [🤖]
    -> null
 
Run command '> apply recipe-name' to apply a recipe.

This command will scan the project and provide a list of recipes it can apply to it. We choose to apply the one to migrate a Mule application to Spring Boot.

spring-amqp-mule:> apply migrate-mule-to-boot
Applying recipe 'migrate-mule-to-boot'
[..] Migrating Mulesoft to Spring Boot
   [..] Converting Mulesoft files
       [ok] Adding 3 dependencies
       [ok] Adding 1 methods
   [ok] Converting Mulesoft files
[ok] Migrating Mulesoft to Spring Boot
 
migrate-mule-to-boot successfully applied the following actions:
 (x)
 (x)
 (x) Migrating Mulesoft to Spring Boot
 (x)
 (x)

The tool gives a brief description of the changes it made. It can also take advantage of git and automatically make a commit after applying a recipe, which is a useful feature in case you want to review and possibly revert the code changes.

If we run git show on the last commit we can see that the tool changed dependencies (the pom.xml file), the code of the application (Foo.java) and added a required configuration file (application.properties). This is a good illustration of what it can do.

The Technology Behind the Tool

Now that you have a feel of how the tool works, we can see what technology powers it. It has a name: OpenRewrite. This is another open source project tailored at automatic code refactoring:

OpenRewrite project is a mass refactoring ecosystem for Java and other source code, designed to eliminate technical debt across an engineering organization. This project delivers scalable automated code maintenance, best practices, vulnerability patching, API migrations, dependency management, and more.

At its core, OpenRewrite parses the project files and then manipulates the AST representing the different files. A recipe is nothing more than a series of transformations applied to the AST of a file. You can clearly see that by looking at the Spring Boot Migrator recipe for Mule migration that we applied in the previous example.

@Configuration
public class MigrateMuleToBoot {
    @Autowired
    private JavaDSLAction2 javaDSLAction2;
    @Bean
    public Recipe muleRecipe() {
        return Recipe.builder()
                .name("migrate-mule-to-boot")
                .description("Migrate Mulesoft 3.9 to Spring Boot")
                .order(60)
                .description("Migrate Mulesoft to Spring Boot")
                .condition(new MuleConfigFileExist())
                .actions(List.of(
                        /*
                        * Add dependencies for spring integration
                        */
                        AddDependencies.builder()
                                .condition(
                                        NoDependencyExistMatchingRegex.builder()
                                            .dependencies(List.of(
                                                    "org.springframework.boot:spring-boot-starter-web:2.5.5",
                                                    "org.springframework.boot:spring-boot-starter-integration:2.5.5",
                                                    "org.springframework.integration:spring-integration-http",
                                                    "org.springframework.integration:spring-integration-amqp:2.5.5",
                                                    "org.springframework.integration:spring-integration-stream:2.5.5"
                                            )
                                        )
                                        .build()
                                )
                                .dependencies(
                                    List.of(
                                        Dependency.builder()
                                                .groupId("org.springframework.boot")
                                                .artifactId("spring-boot-starter-web")
                                                .version("2.5.5")
                                                .build(),
                                        Dependency.builder()
                                                .groupId("org.springframework.boot")
                                                .artifactId("spring-boot-starter-integration")
                                                .version("2.5.5")
                                                .build(),
                                        Dependency.builder()
                                                .groupId("org.springframework.integration")
                                                .artifactId("spring-integration-amqp")
                                                .version("5.4.4")
                                                .build(),
                                        Dependency.builder()
                                                .groupId("org.springframework.integration")
                                                .artifactId("spring-integration-stream")
                                                .version("5.4.4")
                                                .build(),
                                        Dependency.builder()
                                                .groupId("org.springframework.integration")
                                                .artifactId("spring-integration-http")
                                                .version("5.4.4")
                                                .build()
                                    )
                                )
                                .build(),

                        /*
                        * Annotate Spring Boot Application class with @EnableIntegration
                        */
                        AddTypeAnnotationToTypeAnnotatedWith.builder()
                                .annotatedWith("org.springframework.boot.autoconfigure.SpringBootApplication")
                                .annotation("org.springframework.integration.config.EnableIntegration")
                                .condition(
                                        HasNoTypeAnnotation.builder()
                                                .hasTypeAnnotation(
                                                   HasTypeAnnotation.builder()
                                                    .annotation("org.springframework.integration.config.EnableIntegration")
                                                    .build()
                                                ).build()
                                ).build(),


                        /*
                         * Add java class with Spring Integration DSL statements
                         */
                        javaDSLAction2,

//                        /*
//                        * Migrate Mulesoft XML to Spring Integration XML
//                        */
//                        MigrateMulesoftFile.builder().configuration(configuration).build(),

                        /*
                        * Remove Mule dependencies
                        */
                        RemoveDependenciesMatchingRegex.builder()
                                .condition(Condition.TRUE)
                                .dependenciesRegex(List.of("org\\.mule\\..*", "com\\.mulesoft\\..*"))
                                .build(),

                        /*
                        * Remove Mule plugins
                        */
                        RemovePluginsMatchingRegex.builder()
                                .condition(Condition.TRUE)
                                .pluginsRegex(List.of("org\\.mule\\..*", "com\\.mulesoft\\..*"))
                                .build()

                        /*
                        * TODO: How to find out if all elements were successfully migrated and Mule XML can be deleted?
                        */
                ))
                .build();
    }
}

You can also define a recipe using declarative syntax in a YAML file, but the principles remain the same.

OpenRewrite and Spring Boot Migrator add features, robustness, checks… a lot of value on top of this basic idea, but at its core you are parsing some code and transforming it.

It is kind of the static analysis and refactoring features of your IDE on steroids. Your IDE can do things like renaming methods and variables, generating some stub methods, etc. This tool uses the same basic idea, but on a much larger scale and with a bigger plan. The result is a lot of time saved.

In fact you can also use OpenRewrite to perform complex refactorings other than migrations. You can use it to build your custom linter, to check and apply some style to your code and solve specific problems you have. For example, if you need to migrate an application it is reasonable to expect that you may also need to change coding patterns. A project might be using outdated patterns or simply patterns that are not optimal with the new framework. You can define a custom recipe with OpenRewrite to deal with such issues.

Summary

Spring Boot Migrator is an interesting tool in its own right but also as an example of what you can build in the right environment and how much legacy modernization matters nowadays. 

Software built decades ago powers an important part of our businesses and personal lives. If you work in an organization with a lot of such code, you are going to spend most of your time and effort just on maintaining it. So, using a tool like Spring Boot Migrator or even developing something custom on top of OpenRewrite, can help you reduce costs or improve your life as a developer.

Another interesting point to make is that the tool makes sense because Spring Boot itself is an opinionated framework. That is to say, all Spring Boot applications are supposed to behave in a certain way. It would not make sense to migrate to a generic Spring application, because there is no such thing. 

This is a not-so-obvious benefit of having company policies and style rules. If all of your company software behaves in a certain way, you can apply shared patterns and use shared tools to maintain and upgrade them.

Published on Java Code Geeks with permission by Federico Tomassetti, partner at our JCG program. See the original article here: Automatic Refactoring With Spring Boot Migrator

Opinions expressed by Java Code Geeks contributors are their own.

Federico Tomassetti

Federico has a PhD in Polyglot Software Development. He is fascinated by all forms of software development with a focus on Model-Driven Development and Domain Specific Languages.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button