Skip to content

ViewDispatches treats a @RestController/@ResponseBody String return as a Spring view name #272

Description

@rahlk

Summary

ViewDispatches gates a Spring entrypoint's String-returning method into the view-name tier whenever the owning type (or method) is flagged with the spring entrypoint framework. That flag doesn't distinguish @RestController from @Controller, and doesn't check @ResponseBody on @Controller methods either — so a REST endpoint's return value (an HTTP response body) gets analyzed as if it names a view template.

Found while fixing the crash in #271 (a non-identifier target in this same code path threw an NPE); this is the "related semantic issue" called out but intentionally not fixed in that PR, since it changes detection scope rather than just the crash.

Where

SpringEntrypointFinder.isEntrypointClass matches on substring, so both annotations set the same spring framework tag:

if (annotation.getNameAsString().contains("RestController")
        || annotation.getNameAsString().contains("Controller")
        || ...

(src/main/java/com/ibm/cldk/javaee/spring/SpringEntrypointFinder.java)

ViewDispatches.collectTypes then uses that same undifferentiated tag to gate the return site:

boolean springType = type.getEntrypointFrameworks().contains("spring");
...
boolean viewNames = springType || callable.getEntrypointFrameworks().contains("spring");

(src/main/java/com/ibm/cldk/artifacts/ViewDispatches.java)

Reproducer

Robot Shop's shipping service is a concrete real-world example (Controller.java):

@RestController
public class Controller {
    @GetMapping("/health")
    public String health() {
        return "OK";
    }

    @GetMapping("/count")
    public String count() {
        long count = cityrepo.count();
        return String.valueOf(count);
    }
}

Both health() and count() are REST response bodies, not view names, but both currently create a view-name dispatch site:

The same gap applies to @Controller methods annotated @ResponseBody:

@Controller
class Controller {
    @ResponseBody
    @GetMapping("/count")
    public String count() {
        return String.valueOf(10);
    }
}

Expected

ViewDispatches should not create a view-name site for:

  • any method on a type annotated @RestController, and
  • any method (or its declaring type) annotated @ResponseBody.

Ordinary @Controller view-returning methods (return "home";, dataflow-traced locals, etc.) should keep working exactly as they do today.

Suggested regression tests

  1. @RestController + literal return (return "OK";) → no view-name site at all.
  2. @RestController + non-identifier return (return String.valueOf(n);) → no view-name site (currently ends up correctly unresolved post-fix(artifacts): a non-identifier view-dispatch target is not a dataflow variable, not a NullPointerException #271, but ideally isn't a site in the first place).
  3. @Controller + @ResponseBody + literal return → no view-name site.
  4. @Controller + literal return, no @ResponseBody → existing view-name behavior unchanged.
  5. @Controller + dataflow-traced local return (String page = "home"; return page;) → existing dataflow-based view resolution unchanged.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions