How to avoid the "Circular view path" exception with Spring MVC test
Asked Answered
P

26

164

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?

Pattern answered 15/9, 2013 at 14:42 Comment(4)
Can you post the ViewResolver you use when it's failing?Biserrate
@SotiriosDelimanolis: I am not sure if any viewResolver is used by Spring MVC Test. documentationPattern
I was facing the same problem but the problem was i haven't added below dependency. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>Pah
use @RestController instead of @ControllerLander
B
77

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.

Biserrate answered 15/9, 2013 at 17:3 Comment(4)
@Pattern 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. This resource can be a Servlet. In this case it is because the path /preference maps to your DispatcherServlet.Biserrate
@Pattern 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.Biserrate
@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.Biserrate
@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.Biserrate
L
185

@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.

Larina answered 16/4, 2018 at 9:52 Comment(7)
@TodorTodorov It did for meAcculturize
@TodorTodorov and for me!Alboran
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.Alkanet
This worked for me - I had @ControllerAdvice instead of @RestControllerAdvice which caused this same problem. Thanks!Bloodless
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.Biserrate
@RestController @ControllerAdvice Works for meSommelier
Thanks man, that's the exact solution i was looking for.Speed
T
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) {
Tahoe answered 1/12, 2013 at 12:58 Comment(1)
They want to return HTML by resolving a view, not return a serialized version of a List<DomainObject>.Biserrate
B
77

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.

Biserrate answered 15/9, 2013 at 17:3 Comment(4)
@Pattern 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. This resource can be a Servlet. In this case it is because the path /preference maps to your DispatcherServlet.Biserrate
@Pattern 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.Biserrate
@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.Biserrate
@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.Biserrate
S
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
Sextain answered 13/2, 2014 at 13:24 Comment(4)
This is for testcases only. Not for controllers.Lollard
Was helping someone troubleshooting this issue in one of their new unit tests, this is exactly what we were looking for.Giustino
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?Roselane
This is the perfect answer for test and exactly what I was looking for!Linis
M
32

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.

Madancy answered 5/1, 2018 at 13:40 Comment(2)
For me, It wasn't necessary to change the starter-web, but I had the thymeleaf dependency with <scope>test</scope>. When I removed the "test" scope, it worked. Thanks for the clue!Indus
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 everythingKimikokimitri
L
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();
}
Lockard answered 6/1, 2015 at 11:45 Comment(4)
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. ThanksErastus
I had the same problem and this fixed it for me as well. Thanks a lot!Mullah
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. +1Palaeontography
Oldie but goldie! Saved my day. Thank you for this workaround +1Millepore
K
15

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>
Kirkpatrick answered 21/12, 2017 at 8:45 Comment(1)
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:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>Tacket
E
13

Adding / after /preference solved the problem for me:

@Test
public void circularViewPathIssue() throws Exception {
    mockMvc.perform(get("/preference/"))
           .andDo(print());
}
Enterotomy answered 19/3, 2019 at 18:31 Comment(1)
That worked for me. However, I don't really understand why it doesn't work without a "/" at the endTorchbearer
L
13

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

Lander answered 21/3, 2020 at 14:15 Comment(3)
this is not fix, now it will show your file name, instead showing templateConway
that depends on the actual problem. this error can occur because of many reasonsLander
@AshishKamble it happened to me when using Spring Boot with OAuth Custom login pageRamentum
P
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.

Priapism answered 17/7, 2019 at 9:51 Comment(0)
A
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.

Ardine answered 14/7, 2018 at 14:0 Comment(0)
L
4

Add the annotation @ResponseBody to your method return.

Latrice answered 9/10, 2019 at 7:20 Comment(1)
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.Widdershins
D
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

Decemvirate answered 26/11, 2019 at 19:41 Comment(2)
Simply renaming Freemarker files from .ftl to .ftlh solved the problem for me.Throb
Man... I owe you a beer. I lost my entire day because of this renaming thing.Empathy
D
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> 
Duala answered 4/5, 2016 at 8:2 Comment(0)
C
1

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

Cornia answered 4/12, 2017 at 17:33 Comment(0)
U
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>
Upu answered 12/1, 2023 at 23:20 Comment(1)
Thanks, it helps. I tried other options suggested here, nothing worked. Most of the time simple steps solve the issues.Journalist
M
1

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.

Muckraker answered 22/1, 2023 at 12:30 Comment(0)
G
1

Use thymeleaf dependency without version:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Gabrila answered 2/5, 2023 at 14:18 Comment(0)
B
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;
    }
}
Bathometer answered 12/5, 2015 at 12:58 Comment(1)
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.Debrief
H
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.

Horsecar answered 21/1, 2018 at 14:15 Comment(0)
I
0

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

Ironsmith answered 9/7, 2018 at 6:18 Comment(0)
F
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>
Flanna answered 17/2, 2020 at 11:43 Comment(0)
S
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");
            }
        };
Slotnick answered 8/4, 2021 at 13:42 Comment(0)
D
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
Definiendum answered 16/9, 2021 at 6:31 Comment(0)
D
0

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

I am using Intellij. May be some caching issue.

Decollate answered 14/8, 2023 at 12:48 Comment(0)
O
-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);
    }
}
Ordure answered 9/4, 2016 at 20:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.