Skip to content

Jax-RS to Spring MVC Recipe#903

Open
yasaswigadde wants to merge 9 commits intoopenrewrite:mainfrom
yasaswigadde:main
Open

Jax-RS to Spring MVC Recipe#903
yasaswigadde wants to merge 9 commits intoopenrewrite:mainfrom
yasaswigadde:main

Conversation

@yasaswigadde
Copy link

@yasaswigadde yasaswigadde commented Jan 15, 2026

What's changed?

Added a Recipe for Jax-RS to Spring MVC Migration

What's your motivation?

Needed to migrate a project from Jax-rs to Spring MVC. Tried finding the recipe on Moderne but couldn't. Noticed that it is a wanted recipe - #69. Hence decided to create one as it can also be used by others.

Have you considered any alternatives or workarounds?

Tried creating declarative recipe first, but couldn't achieve the required functionality.

Any additional context

Custom Recipes

Three Imperative Recipes are created to facilitate JAX-RS to Spring MVC migration:

1. JAX-RS Annotation Transformation Recipe
Converts JAX-RS REST annotation to Spring MVC equivalents

  • Transforms @path annotation on class to @RestController + @RequestMapping
  • Migrates HTTP method annotations (@get, @post, @put, @delete) to corresponding Spring annotations (@GetMapping, @PostMapping, @PutMapping, @DeleteMapping)
  • Transforms @path annotation on method to value parameter within annotations @GetMapping or @PostMapping etc.
  • Converts @consumes and @produces annotations to consumes and produces parameters within the annotations @RequestMapping or @GetMapping etc.
  • Migrates @QueryParam and @FormParam annotations on method parameters to @RequestParam. If the parameter also has the @DefaultValue annotation, it is converted to defaultValue parameter within annotation @RequestParam. Else the required parameter is set to false within annotation @RequestParam, since QueryParam and FormParam are optional in JAX-RS and RequestParam is required by default and will throw error if not provided.
  • Migrates @PathParam annotation on method parameters to @PathVariable.
  • Migrates @HeaderParam annotation on method parameters to @RequestHeader. If the parameter also has the @DefaultValue annotation, it is converted to defaultValue parameter within annotation @RequestHeader. Else the required parameter is set to false within annotation @RequestParam, since HeaderParam is optional in JAX-RS and RequestHeader is required by default and will throw error if not provided.
  • Removes @context annotation on method parameters as Spring injects context objects automatically via method parameters.
  • Adds @RequestPart annotation on method parameter if the parameter doesn't have any JAX-RS annotations and @consumes has only one value which contains the word multipart in it.
  • Adds @RequestBoody annotation on method parameter if the parameter doesn't have any JAX-RS annotations and is not a multipart consumer.

2. MediaType Transformation Recipe
Converts JAX-RS MediaType to Spring's MediaType

  • Transforms MediaType constant references for MediaType Strings (Eg. MediaType.APPLICATION_JSON to MediaType.APPLICATION_JSON_VALUE )
  • Transforms MediaType constant references for MediaType Objects (Eg. MediaType.APPLICATION_JSON_TYPE to MediaType.APPLICATION_JSON )
  • ChangeType: JAX-RS MediaType to Spring's MediaType

3. Response Object Conversion Recipe
Converts JAX-RS Response to Spring's ResponseEntity

  • Replaces .entity(...).build() with .body(...) passing the argument directly and removing .entity()
  • Replaces .serverError() method with .internalServerError()
  • ChangeType: JAX-RS Response.Status to Spring's HttpStatus
  • ChangeType: JAX-RS Response.ResponseBuilder to Spring's ResponseEntity.BodyBuilder
  • ChangeType: JAX-RS Response to Spring's ResponseEntity

Change Type Recipes Used

org.openrewrite.java.ChangeType Recipe is used in Declarative Recipe to migrate few JAX-RS components to respective Spring components:

  • JAX-RS Response to Spring's HttpServletRequest
  • JAX-RS HttpHeaders to Spring's HttpHeader
  • JAX-RS CacheControl to Spring's CacheControl
  • JAX-RS StreamingOutput to Spring's StreamingResponseBody

Known Limitations

  • JAX-RS controller classes without the @path annotation will not be annotated with @RestController. These classes need to be manually identified and updated.
  • JAX-RS QueryParam, FormParam, and HeaderParam are optional by default, while their Spring MVC counterparts are mandatory. The recipe adds required = false for these parameters when there is no default value. Parameter should be reviewed and updated if they should be mandatory.
  • In JAX-RS, @context can also be used on class variables to inject context. These needs to be handled manually.
  • The @RequestPart logic is not complete, it will add the annotation even if the parameter already contains @FormDataParam or @multipart which is redundant. @RequestPart might also need value or name manually based on the usage.
  • CXF MutiPartBody to Spring's MultiPartFile changes are not done.
  • Response to ResponseEntity method changes are done only with the method chain ends with .build()
  • Response.type() to ResponseEntity.contentType() change is not handled.
  • Replacing Response with ResponseEntity in variable declarations gives raw type warning.
  • Method changes are not handled for the components migrated via ChangeType.
  • @ControllerAdvice + @ExceptionHandler needs to be migrated to use ExceptionMapper Instead.
  • JAX-RS Variant is to be replaced with Spring's MediaType usage.
  • JAX-RS EntityTag is to be replaced with Spring header's IF_NONE_MATCH usage.
  • ServletContext injected via @context is to be replaced with HttpServletRequest.getServletContext() calls.
  • Few annotations are not handled - @ApplicationPath, @BeanParam, @ConstrainedTo, @CookieParam, @encoded, @Head, @HttpMethod, @MatrixParam, @namebinding, @options, @patch
  • None of the exceptions are handled
  • Other components in JAX-RS library are to be migrated manually.

Checklist

  • I've added unit tests to cover both positive and negative cases
  • I've read and applied the recipe conventions and best practices
  • I've used the IntelliJ IDEA auto-formatter on affected files

LICENSE
THIS SOFTWARE IS CONTRIBUTED SUBJECT TO THE TERMS OF THE MODERNE SOURCE AVAILABLE LICENSE. YOU MAY OBTAIN A COPY OF THE LICENSE AT https://github.com/openrewrite/rewrite-spring/blob/main/LICENSE/moderne-source-available-license.md.
THIS SOFTWARE IS LICENSED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND ANY WARRANTY OF NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THIS SOFTWARE MAY BE REDISTRIBUTED TO OTHERS ONLY BY EFFECTIVELY USING THIS OR ANOTHER EQUIVALENT DISCLAIMER IN ADDITION TO ANY OTHER REQUIRED LICENSE TERMS.

Copy link
Member

@timtebeek timtebeek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this started @yasaswigadde ; let us know if you need any help.

@yasaswigadde
Copy link
Author

yasaswigadde commented Jan 17, 2026

Thanks for getting this started @yasaswigadde ; let us know if you need any help.

Thanks @timtebeek, appreciate the support!

@yasaswigadde
Copy link
Author

Hi @timtebeek I am trying to the tests in local, but I'm getting -
Unable to find classpath resource dependencies beginning with: 'javax.ws.rs-api-2'
I tried running createTypeTable task, but the issue persists.

And also, I am getting this during build -
Caused by: org.gradle.api.GradleException: Recipe exists in environment but is not listed in CSV (4):

  • org.openrewrite.java.spring.mvc.JaxrsToSpringmvcAnnotations
  • org.openrewrite.java.spring.mvc.JaxrsToSpringmvcMediaType
  • org.openrewrite.java.spring.mvc.JaxrsToSpringmvcResponseEntity
  • org.openrewrite.java.spring.mvc.MigrateJaxRsToSpringMvc

Can you let me know if I'm missing anything?

@timtebeek
Copy link
Member

Sorry to hear about your troubles; for the CSV issue it should print a suggestion to run ./gradlew recipeCsvGenerate; did you spot that in the log output?

For the type tables there's only an entry in the test type tables:
https://github.com/yasaswigadde/rewrite-spring/blob/636c8946b75ba430b04fcf1934f4b68957ee03b0/build.gradle.kts#L108

If you need that in src/main then it's best to change testParserClasspath to parserClasspath and rerun ./gradlew createTypeTable.

Hope that helps you on your way there!

@yasaswigadde
Copy link
Author

yasaswigadde commented Feb 3, 2026

Thanks! I am able to run the test cases after changing them to parserClasspath.
And yes, I do see this suggestion in build output in local. I was looking at pipeline output earlier so missed it, I understand that I need to commit recipes.csv - is that correct? And do I also need to commit the changes made on classpath.tsv.gz?

@yasaswigadde yasaswigadde marked this pull request as ready for review February 3, 2026 14:24
Comment on lines +32 to +43
@Override
public String getDisplayName() {
return "Migrate jax-rs annotations to spring MVC annotations";
}

@Override
public String getDescription() {
return "Replaces all jax-rs annotations with Spring MVC annotations.";
}

@Override
public Set<String> getTags() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@Override
public String getDisplayName() {
return "Migrate jax-rs annotations to spring MVC annotations";
}
@Override
public String getDescription() {
return "Replaces all jax-rs annotations with Spring MVC annotations.";
}
@Override
public Set<String> getTags() {
String displayName = "Migrate jax-rs annotations to spring MVC annotations";
String description = "Replaces all jax-rs annotations with Spring MVC annotations.";
Set<String> tags = new HashSet<>(Arrays.asList("Java", "Spring"));

Comment on lines +32 to +44
import java.util.Set;

@Value
@EqualsAndHashCode(callSuper = false)
public class JaxrsToSpringmvcMediaType extends Recipe {

@Override
public String getDisplayName() {
return "Migrate jax-rs MediaType to spring MVC MediaType";
}

@Override
public String getDescription() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import java.util.Set;
@Value
@EqualsAndHashCode(callSuper = false)
public class JaxrsToSpringmvcMediaType extends Recipe {
@Override
public String getDisplayName() {
return "Migrate jax-rs MediaType to spring MVC MediaType";
}
@Override
public String getDescription() {
String displayName = "Migrate jax-rs MediaType to spring MVC MediaType";
String description = "Replaces all jax-rs MediaType with Spring MVC MediaType.";
Set<String> tags = new HashSet<>(Arrays.asList("Java", "Spring"));

import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;

import java.util.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

Comment on lines +37 to +48
@Override
public String getDisplayName() {
return "Migrate jax-rs Response to spring MVC ResponseEntity";
}

@Override
public String getDescription() {
return "Replaces all jax-rs Response with Spring MVC ResponseEntity.";
}

@Override
public Set<String> getTags() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@Override
public String getDisplayName() {
return "Migrate jax-rs Response to spring MVC ResponseEntity";
}
@Override
public String getDescription() {
return "Replaces all jax-rs Response with Spring MVC ResponseEntity.";
}
@Override
public Set<String> getTags() {
String displayName = "Migrate jax-rs Response to spring MVC ResponseEntity";
String description = "Replaces all jax-rs Response with Spring MVC ResponseEntity.";
Set<String> tags = new HashSet<>(Arrays.asList("Java", "Spring"));

void jaxrsToSpringmvcAnnotationsTest1() {
rewriteRun(
java(
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""
"""

.build()
.activateRecipes("org.openrewrite.java.spring.mvc.MigrateJaxRsToSpringMvc"))
.parser(JavaParser.fromJavaVersion()
.classpathFromResources(new InMemoryExecutionContext(), "jakarta.ws.rs-api-4", "javax.ws.rs-api-2"));;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.classpathFromResources(new InMemoryExecutionContext(), "jakarta.ws.rs-api-4", "javax.ws.rs-api-2"));;
.classpathFromResources(new InMemoryExecutionContext(), "jakarta.ws.rs-api-4", "javax.ws.rs-api-2"));

void jaxrsToSpringmvcJavaxTest() {
rewriteRun(
java(
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""
"""

}
}
""",
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""
"""

void jaxrsToSpringmvcJakartaTest() {
rewriteRun(
java(
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""
"""

}
}
""",
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""
"""

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants