I believe I've uncovered a bug in spring framework with the forward:
notation. This is happening with XML based spring applications.
I found this while upgrading my existing spring framework application from Spring 3 to Spring 6. I have created a project to reproduce.
Starting with build.gradle
plugins {
id 'java'
id 'war' // Add war plugin for web application
}
group = 'com.avenuinsights'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
def springVersion = '6.2.7'
implementation "org.springframework:spring-webmvc:${springVersion}"
implementation "org.springframework:spring-context:${springVersion}"
implementation group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '6.1.0'
implementation group: 'jakarta.servlet.jsp', name: 'jakarta.servlet.jsp-api', version: '3.1.1'
implementation group: 'jakarta.servlet.jsp.jstl', name: 'jakarta.servlet.jsp.jstl-api', version: '3.0.2'
implementation group: 'org.glassfish.web', name: 'jakarta.servlet.jsp.jstl', version: '3.0.1'
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
}
test {
useJUnitPlatform()
}
The web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- Configure the Spring DispatcherServlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
and the spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- Enable Spring MVC features -->
<mvc:annotation-driven />
<!-- Configure controllers as beans -->
<bean id="firstController" class="org.example.forwardxml.controller.FirstController" />
<bean id="secondController" class="org.example.forwardxml.controller.SecondController" />
<bean id="thirdController" class="org.example.forwardxml.controller.ThirdController" />
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/first=firstController
/second=secondController
/third=thirdController
</value>
</property>
</bean>
<!-- Define the View Resolver -->
<bean class="org.example.forwardxml.resolver.TranslatingViewResolver"/>
<bean id="urlViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
</bean>
</beans>
FirstController.java
public class FirstController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("FirstController: Processing request...");
// Forward to the second controller
return new ModelAndView("second.one");
}
}
SecondController.java
public class SecondController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("SecondController: Processing request...");
// Forward to the third controller
return new ModelAndView("third.one");
}
}
and ThirdController.java
public class ThirdController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("ThirdController: Processing request...");
// Create a model and view to display "Hello, World!"
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("hello.view");
modelAndView.addObject("message", "Hello, World!");
return modelAndView;
}
}
and finally the TranslatingViewResolver.java
public class TranslatingViewResolver extends AbstractCachingViewResolver {
public static final String HELLO = "hello.view";
public static final String SECOND = "second.one";
public static final String THIRD = "third.one";
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
if (SECOND.equals(viewName)) {
// Forward to the second controller
return new InternalResourceView("forward:/second");
} else if (THIRD.equals(viewName)) {
// Forward to the third controller
return new InternalResourceView("forward:/third");
} else if (HELLO.equals(viewName)) {
// Forward to the hello JSP
return new JstlView("/WEB-INF/views/hello.jsp");
}
return null;
}
}
Now, I believe that what should happen is that I should go to
localhost:8080/forwardxml/first
And it should return the jsp saying "Hello, World!" What I'm getting is:
If I've made a mistake, please let me know, but this looks like a bug according to the documentation. If you recreate and think this is an issue, please comment as well.
The forward:
prefix is a magic prefix for the UrlBasedViewResolver
(and subclasses) which will then turn it into an InternalResourceView
with the prefix removed. The default for the InternalResourceView
is to forward anyway.
There is no bug related to this in Spring MVC, the problem is in your TranslatingViewResolver
. Instead of new InternalResourceView("forward:/second")
you should do new InternalResourceView("/second")
. As you include the forward:
prefix in the name of the view, Spring will then try to resolve something that listens for forward:/second
instead of `/second. It fails hence a 404.
This also has nothing to do with XML config or a bug in Spring, the same would happen with Java config and annotation based controllers etc.