157

I have the following code in one of my controllers:

@Controller
@RequestMapping("/preference")
public class PreferenceController {

    @RequestMapping(method = RequestMethod.GET, produces = "text/html")
    public String preference() {
        return "preference";
    }
}

I am simply trying to test it using Spring MVC test as follows:

@ContextConfiguration
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class PreferenceControllerTest {

    @Autowired
    private WebApplicationContext ctx;

    private MockMvc mockMvc;
    @Before
    public void setup() {
        mockMvc = webAppContextSetup(ctx).build();
    }

    @Test
    public void circularViewPathIssue() throws Exception {
        mockMvc.perform(get("/preference"))
               .andDo(print());
    }
}

I am getting the following exception:

Circular view path [preference]: would dispatch back to the current handler URL [/preference] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)

What I find strange is that it works fine when I load the "full" context configuration that includes the template and view resolvers as shown below:

<bean class="org.thymeleaf.templateresolver.ServletContextTemplateResolver" id="webTemplateResolver">
    <property name="prefix" value="WEB-INF/web-templates/" />
    <property name="suffix" value=".html" />
    <property name="templateMode" value="HTML5" />
    <property name="characterEncoding" value="UTF-8" />
    <property name="order" value="2" />
    <property name="cacheable" value="false" />
</bean>

I am well aware that the prefix added by the template resolver ensures that there is not "circular view path" when the app uses this template resolver.

But then how I am supposed to test my app using Spring MVC test?

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
balteo
  • 23,602
  • 63
  • 219
  • 412
  • 1
    Can you post the `ViewResolver` you use when it's failing? – Sotirios Delimanolis Sep 15 '13 at 16:28
  • @SotiriosDelimanolis: I am not sure if any viewResolver is used by Spring MVC Test. [documentation](http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/testing.html#spring-mvc-test-framework) – balteo Sep 15 '13 at 16:53
  • 10
    I was facing the same problem but the problem was i haven't added below dependency. org.springframework.boot spring-boot-starter-thymeleaf – Aamir Dec 05 '16 at 16:54
  • 2
    use `@RestController` instead of `@Controller` – MozenRath Mar 21 '20 at 14:16

26 Answers26

162

@Controller@RestController

I had the same issue and I noticed that my controller was also annotated with @Controller. Replacing it with @RestController solved the issue. Here is the explanation from Spring Web MVC:

@RestController is a composed annotation that is itself meta-annotated with @Controller and @ResponseBody indicating a controller whose every method inherits the type-level @ResponseBody annotation and therefore writes directly to the response body vs view resolution and rendering with an HTML template.

Boris
  • 22,667
  • 16
  • 50
  • 71
  • 1
    @TodorTodorov It did for me – Igor Rodriguez May 20 '19 at 05:10
  • @TodorTodorov and for me! – Ran Jun 20 '19 at 09:04
  • 5
    Worked for me, too. I had a `@ControllerAdvice` with a `handleXyException` method in it, which returned my own object instead of a ResponseEntity. Adding `@RestController` on top of the `@ControllerAdvice` annotation worked and the issue is gone. – Igor Jul 19 '19 at 22:51
  • 1
    This worked for me - I had `@ControllerAdvice` instead of `@RestControllerAdvice` which caused this same problem. Thanks! – Brice Frisco Dec 27 '20 at 00:09
  • 1
    This has the same problem as Deepti's answer. The OP is trying to return a view name and render an HTML template with Thymeleaf. They're not trying to return the `String` `"preference"` as the content of the response. – Sotirios Delimanolis Feb 14 '21 at 19:16
  • ```@RestController @ControllerAdvice ``` Works for me – Ben May 22 '22 at 05:47
109

I solved this problem by using @ResponseBody like below:

@RequestMapping(value = "/resturl", method = RequestMethod.GET, produces = {"application/json"})
    @ResponseStatus(HttpStatus.OK)
    @Transactional(value = "jpaTransactionManager")
    public @ResponseBody List<DomainObject> findByResourceID(@PathParam("resourceID") String resourceID) {
Deepti Kohli
  • 1,859
  • 1
  • 14
  • 6
76

This has nothing to do with Spring MVC testing.

When you don't declare a ViewResolver, Spring registers a default InternalResourceViewResolver which creates instances of JstlView for rendering the View.

The JstlView class extends InternalResourceView which is

Wrapper for a JSP or other resource within the same web application. Exposes model objects as request attributes and forwards the request to the specified resource URL using a javax.servlet.RequestDispatcher.

A URL for this view is supposed to specify a resource within the web application, suitable for RequestDispatcher's forward or include method.

Emphasis mine. In other words, the view, before rendering, will try to get a RequestDispatcher to which to forward(). Before doing this it checks the following

if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
    throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
                        "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
                        "(Hint: This may be the result of an unspecified view, due to default view name generation.)");
}

where path is the view name, what you returned from the @Controller. In this example, that is preference. The variable uri holds the uri of the request being handled, which is /context/preference.

The code above realizes that if you were to forward to /context/preference, the same servlet (since the same handled the previous) would handle the request and you would go into an endless loop.


When you declare a ThymeleafViewResolver and a ServletContextTemplateResolver with a specific prefix and suffix, it builds the View differently, giving it a path like

WEB-INF/web-templates/preference.html

ThymeleafView instances locate the file relative to the ServletContext path by using a ServletContextResourceResolver

templateInputStream = resourceResolver.getResourceAsStream(templateProcessingParameters, resourceName);`

which eventually

return servletContext.getResourceAsStream(resourceName);

This gets a resource that is relative to the ServletContext path. It can then use the TemplateEngine to generate the HTML. There's no way an endless loop can happen here.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • 1
    @balteo When you use `ThymleafViewResolver` the `View` is resolved as a file relative to the `prefix` and `suffix` you provide. When you don't use that resolves, Spring uses a default `InternalResourceViewResolver` which finds resources with a [`RequestDispatcher`](http://docs.oracle.com/javaee/6/api/javax/servlet/RequestDispatcher.html). This resource can be a `Servlet`. In this case it is because the path `/preference` maps to your `DispatcherServlet`. – Sotirios Delimanolis Sep 15 '13 at 17:37
  • 2
    @balteo To test your app, provide a correct `ViewResolver`. Either the `ThymeleafViewResolver` as in your question, your own configured `InternalResourceViewResolver` or change the view name you are returning in your controller. – Sotirios Delimanolis Sep 15 '13 at 17:38
  • 3
    @ShirgillFarhanAnsari A `@RequestMapping` annotated handler method with a `String` return type (and no `@ResponseBody`) has its return value handled by a `ViewNameMethodReturnValueHandler` which interprets the String as a view name, and uses it to go through the process I explain in my answer. With `@ResponseBody`, Spring MVC will instead use `RequestResponseBodyMethodProcessor` which instead writes the String directly to the HTTP response, ie. no view resolution. – Sotirios Delimanolis Dec 17 '17 at 12:37
  • @valik Not `/` in the OP's example, their path would be `/context/preference`. But yes, the `DispatcherServlet` detects that the handler method would forward to that same path and that the `DispatcherServlet` would handle it the exact same way it just did, and therefore go into a loop. – Sotirios Delimanolis Mar 26 '18 at 16:33
45

This is how I solved this problem:

@Before
    public void setup() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/jsp/view/");
        viewResolver.setSuffix(".jsp");
 
        mockMvc = MockMvcBuilders.standaloneSetup(new HelpController())
                                 .setViewResolvers(viewResolver)
                                 .build();
    }

Also you can make bean for this in .xml file

<bean id = "viewResolver" class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/"/>
    <property name="suffix" value=".jsp"/>
</bean
Karan Kamboj
  • 121
  • 1
  • 5
Piotr Sagalara
  • 2,247
  • 3
  • 22
  • 25
  • 1
    This is for testcases only. Not for controllers. – cst1992 Jul 20 '16 at 06:18
  • 2
    Was helping someone troubleshooting this issue in one of their new unit tests, this is exactly what we were looking for. – Bradford2000 Jul 28 '16 at 20:42
  • I used this, but in spite of giving the wrong prefix and suffix for my resolver in the test, it worked. Can you provide reasoning behind this, why is this required? – dushyantashu Sep 11 '17 at 11:05
  • 1
    This is the perfect answer for test and exactly what I was looking for! – SDB Aug 24 '21 at 07:18
31

I am using Spring Boot to try and load a webpage, not test, and had this problem. My solution was a bit different than those above considering the slightly different circumstances. (although those answers helpled me understand.)

I simply had to change my Spring Boot starter dependency in Maven from:

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
</dependency>

to:

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Just changing the 'web' to 'thymeleaf' fixed the problem for me.

Old Schooled
  • 1,222
  • 11
  • 22
  • 1
    For me, It wasn't necessary to change the starter-web, but I had the thymeleaf dependency with test. When I removed the "test" scope, it worked. Thanks for the clue! – Georgina Diaz Mar 30 '20 at 21:01
  • 2
    I had this problem as well, tried this solution and had issues with missing files such as missing javax.validation.constraints. My solution was to include both the thymeleaf and web jars which resolved everything – Dave Nov 25 '20 at 21:04
16

Here's an easy fix if you don't actually care about rendering the view.

Create a subclass of InternalResourceViewResolver which doesn't check for circular view paths:

public class StandaloneMvcTestViewResolver extends InternalResourceViewResolver {

    public StandaloneMvcTestViewResolver() {
        super();
    }

    @Override
    protected AbstractUrlBasedView buildView(final String viewName) throws Exception {
        final InternalResourceView view = (InternalResourceView) super.buildView(viewName);
        // prevent checking for circular view paths
        view.setPreventDispatchLoop(false);
        return view;
    }
}

Then set up your test with it:

MockMvc mockMvc;

@Before
public void setUp() {
    final MyController controller = new MyController();

    mockMvc =
            MockMvcBuilders.standaloneSetup(controller)
                    .setViewResolvers(new StandaloneMvcTestViewResolver())
                    .build();
}
Dave Bower
  • 3,487
  • 26
  • 28
  • This fixed my problem. I just added a StandaloneMvcTestViewResolver class in the same directory of the tests and used it in the MockMvcBuilders as described above. Thanks – Matheus Araujo Feb 08 '17 at 20:44
  • I had the same problem and this fixed it for me as well. Thanks a lot! – Johan Feb 22 '17 at 21:26
  • This is a great solution that (1) doesn't need changing the controllers and (2) can be reused in all test classes with one simple import per class. +1 – Nander Speerstra Apr 18 '18 at 11:54
  • Oldie but goldie! Saved my day. Thank you for this workaround +1 – Raistlin Oct 01 '18 at 08:49
14

If you are using Spring Boot, then add thymeleaf dependency into your pom.xml:

    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring4</artifactId>
        <version>2.1.6.RELEASE</version>
    </dependency>
Sarvar Nishonboyev
  • 12,262
  • 10
  • 69
  • 70
  • 2
    Upvote. Missing Thymeleaf dependency was what caused this error in my project. However, ff you are using Spring Boot, then the dependency would look like this instead:` org.springframework.boot spring-boot-starter-thymeleaf ` – peterh Aug 25 '19 at 08:00
13

if you have not used a @RequestBody and are using only @Controller, simplest way to fix this is using @RestController instead of @Controller

MozenRath
  • 9,652
  • 13
  • 61
  • 104
11

Adding / after /preference solved the problem for me:

@Test
public void circularViewPathIssue() throws Exception {
    mockMvc.perform(get("/preference/"))
           .andDo(print());
}
double-beep
  • 5,031
  • 17
  • 33
  • 41
8

In my case, I was trying out Kotlin + Spring boot and I got into the Circular View Path issue. All the suggestions I got online could not help, until I tried the below:

Originally I had annotated my controller using @Controller

import org.springframework.stereotype.Controller

I then replaced @Controller with @RestController

import org.springframework.web.bind.annotation.RestController

And it worked.

johnmilimo
  • 81
  • 1
  • 4
4

I am using Spring Boot with Thymeleaf. This is what worked for me. There are similar answers with JSP but note that I am using HTML, not JSP, and these are in the folder src/main/resources/templates like in a standard Spring Boot project as explained here. This could also be your case.

@InjectMocks
private MyController myController;

@Before
public void setup()
{
    MockitoAnnotations.initMocks(this);

    this.mockMvc = MockMvcBuilders.standaloneSetup(myController)
                    .setViewResolvers(viewResolver())
                    .build();
}

private ViewResolver viewResolver()
{
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();

    viewResolver.setPrefix("classpath:templates/");
    viewResolver.setSuffix(".html");

    return viewResolver;
}

Hope this helps.

Pedro Lopez
  • 2,236
  • 2
  • 19
  • 27
4

Add the annotation @ResponseBody to your method return.

  • Please include an explanation of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. – Android Oct 09 '19 at 07:41
3

When running Spring Boot + Freemarker if the page appears:

Whitelabel Error Page This application has no explicit mapping for / error, so you are seeing this as a fallback.

In spring-boot-starter-parent 2.2.1.RELEASE version freemarker does not work:

  1. rename Freemarker files from .ftl to .ftlh
  2. Add to application.properties: spring.freemarker.expose-request-attributes = true

spring.freemarker.suffix = .ftl

Max
  • 31
  • 2
2

For Thymeleaf:

I just began using spring 4 and thymeleaf, when I encountered this error it was resolved by adding:

<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
  <property name="templateEngine" ref="templateEngine" />
  <property name="order" value="0" />
</bean> 
1

When using @Controller annotation, you need @RequestMapping and @ResponseBody annotations. Try again after adding annotation @ResponseBody

Paul Roub
  • 36,322
  • 27
  • 84
  • 93
1

If you still get the circular view path error after adding below dependencies to pom.xml, you can Right-click project -> Maven -> Reload Project

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
ibrahimkesici
  • 301
  • 3
  • 2
  • Thanks, it helps. I tried other options suggested here, nothing worked. Most of the time simple steps solve the issues. – shaneeqa Mar 05 '23 at 16:23
0

I use the annotation to configure spring web app, the problem solved by adding a InternalResourceViewResolver bean to the configuration. Hope it would be helpful.

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.example.springmvc" })
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Bean
    public InternalResourceViewResolver internalResourceViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/jsp/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}
alijandro
  • 11,627
  • 2
  • 58
  • 74
  • Thanks this works fine for me. My app broke after upgrading to spring boot 1.3.1 from 1.2.7 and was only this line which was failing registry.addViewController("/login").setViewName("login"); When registering that bean, the app worked again...at least the login went wll. – le0diaz Dec 24 '15 at 15:32
0

This is happening because Spring is removing "preference" and appending the "preference" again making the same path as the request Uri.

Happening like this : request Uri: "/preference"

remove "preference": "/"

append path: "/"+"preference"

end string: "/preference"

This is getting into a loop which the Spring notifies you by throwing exception.

Its best in your interest to give a different view name like "preferenceView" or anything you like.

xpioneer
  • 3,075
  • 2
  • 16
  • 18
0

try adding compile("org.springframework.boot:spring-boot-starter-thymeleaf") dependency to your gradle file.Thymeleaf helps mapping views.

0

In my case, I had this problem while trying to serve JSP pages using Spring boot application.

Here's what worked for me:

application.properties

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

pom.xml

To enable support for JSPs, we would need to add a dependency on tomcat-embed-jasper.

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>
Faouzi
  • 929
  • 2
  • 15
  • 23
0

In my case circular view path in spring boot 2 and jdk 11 was fixed by redirecting to index.html:

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            }
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                registry.addViewController("/").setViewName("redirect:/index.html");
            }
        };
sermyro
  • 21
  • 2
0

Add a view resovler in xml file

<bean id = "viewResolver" class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/"/>
    <property name="suffix" value=".jsp"/>
</bean
Karan Kamboj
  • 121
  • 1
  • 5
0

Replace @Controller with @RestController => cause if you use @Controller, you need to define the viewResolver that received data from Controller and displays it to user. On the other hand, with @RestController => you are using Rest API => for transfer JSON that does not need viewResolver.

tonydn
  • 25
  • 5
0

Use thymeleaf dependency without version:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
tricks. com
  • 45
  • 1
  • 6
0

I tried almost all solution mentioned. but only Restarting IDE worked for me.

I am using Intellij. May be some caching issue.

c.sankhala
  • 850
  • 13
  • 27
-2

Another simple approach:

package org.yourpackagename;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

      @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            return application.sources(PreferenceController.class);
        }


    public static void main(String[] args) {
        SpringApplication.run(PreferenceController.class, args);
    }
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
Toothless Seer
  • 798
  • 9
  • 13