I just upgraded my app to Spring Boot 3.0.5 (and consequently Spring 6.0.7) and there seems to be something broken with spring-boot-starter-freemarker.

In my Freemarker template, I have this line that used to work before the upgrade:

<div>${RequestParameters.myParam!}</div>

After the upgrade, I get this error when loading the template:

freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:
==> RequestParameters  [in template "template.ftlh" at line 136, column 11]

----
Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----

----
FTL stack trace ("~" means nesting-related):
    - Failed at: ${RequestParameters.myParam!}  [in template "template.ftlh" at line 136, column 9]

It seems like RequestParameters is not available anymore.

I originally posted this issue in the spring-boot repo and @wilkinsona kindly pointed out that due to https://github.com/spring-projects/spring-framework/commit/d84ca2ba90d27a7c63d7b35a6259b5b9cf341118, there's no longer a RequestParameters entry in the model.

Are there plans to fix this?

Thanks!

Comment From: damir78

Hello @anabright , Thank you. I created an issue: #34637 but unfortunately it was closed. The implementation of "RequestParameters" is https://github.com/apache/freemarker/blob/v2.3.32/src/main/java/freemarker/ext/servlet/HttpRequestHashModel.java and uses javax. (for example javax.servlet) instead jakarta.** ( for example jakarta.servlet).

Comment From: Paul-Gerarts

yes, I upgraded to SpringBoot 3.0.2 as parent and Freemarker had a hash for RequestParameters which doesn't exist anymore.

From the Freemarker docs: "FreemarkerServlet also puts 3 hashes into the data-model, by which you can access the attributes of the 3 objects directly. The hash variables are: Request, Session, Application (corresponds to ServletContext). It also exposes another hash named RequestParameters that provides access to the parameters of the HTTP request."

Those RequestParameters could be accessed directly in your template, for example: <input name="myParam" type="hidden" value="${RequestParameters.myParam!}"/>

As I'm working in a legacy project from waaaay back, I was hoping to find a more elegant way rather than getting the parameter from the servletRequest and adding it as an attribute to my model.

Comment From: mrhcongc

I see that the issue is more than just not supporting the jakarta.* The code in the FreeMarkerView.buildTemplateModel has been changed and has removed all the model additions. I created my own implementations of a few classes to allow me to add the RequestParameters and Request back into the model. If fixed in a future release, I'll just remove my classes.

Comment From: startjava

use spring-boot 3.1.2

use url: http://localhost:8080/test20?selectedValue=d

template code: <#if listString?? && listString?size!=0> <#list listString as list> <#if RequestParameters['selectedValue']==list> ${list} </#if> <#if RequestParameters['selectedValue']!=list> ${list} </#if> </#list> </#if>

controller code: @RequestMapping("test20") public String test20(Model model,HttpServletRequest request,HttpServletResponse response)

{ List listString=new ArrayList(); listString.add("a"); listString.add("b"); listString.add("c"); listString.add("d"); listString.add("e"); request.setAttribute("listString",listString); return "test20"; }

run result : freemarker.core.InvalidReferenceException: The following has evaluated to null or missing: ==> RequestParameters [in template "test20.ftlh" at line 12, column 12]

Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??

FTL stack trace ("~" means nesting-related): - Failed at: #if RequestParameters.selectedValue =...

[in template "test20.ftlh" at line 12, column 7]

same question!


@anabright @mrhcongc @damir78 @Paul-Gerarts

Comment From: startjava

https://github.com/spring-projects/spring-boot/issues/34637

Comment From: startjava

@wilkinsona

Comment From: snicoll

@anabright unfortunately, there's nothing we can do here. Even the latest version of Freemarker does not use Jakarta as far as I can tell.

If you need to use the Servlet integration of Freemarker, only a change in freemarker can help here.

Comment From: doctore

I had this problem upgrading an old project from Spring 3 to Spring 6, by now we have no time to replace the technology used to render the views, so my solution was adding it as a new model attribute of FreeMarker.

Replacing:

<#if RequestParameters['authfail']??>
  ...
</#if>

By, in backend:

@ModelAttribute("rawRequestParameters") 
public String getRequestParameters(HttpServletRequest request) {
  return ofNullable(request)
            .map(HttpServletRequest::getQueryString)
            .orElse("");
}

and in frontend:

<#if rawRequestParameters?? && rawRequestParameters == "authfail">
  ...
</#if>

Comment From: anabright

@anabright unfortunately, there's nothing we can do here. Even the latest version of Freemarker does not use Jakarta as far as I can tell.

If you need to use the Servlet integration of Freemarker, only a change in freemarker can help here.

There's supposed to be a Freemarker release soon that supports jakarta. @snicoll maybe when it's out Spring can be updated to use the new version and add back the missing model attributes like RequestParameters?

Comment From: snicoll

@anabright it's not really actionable right now so please ping again when it's actually available and we'll have a look.

Comment From: FloTrEu

@snicoll According to https://github.com/apache/freemarker/pull/94#issuecomment-2167603161

You have to use freemarker.ext.jakarta.servlet package instead of freemarker.ext.servlet package, and freemarker.ext.jakarta.jsp instead of freemarker.ext.jsp, everywhere in your application. It's not enough to just update your FreeMarker dependency. We support both pre-Jakarata and Jakarta environments in the same artifact, so the name of the non-Jakarta classes did not change.

I've checked version 2.3.33 and it includes freemarker.ext.jakarta.servlet package. Could you please give it a try?

Comment From: bclozel

It looks like Freemarker 2.3.33 was released in May 2024. Spring Framework 6 was released in November 2022.

We could reintroduce this support in Spring Framework 6.2 at the earliest, so two entire minor versions into the 6th generation. Because we didn't get a lot of requests for this in the meantime, I'm reopening this enhancement request and adding it to our backlog. We'll prioritize this according to community demand.

Comment From: snicoll

@anabright and others interested by this, this should be available shortly in 6.2.0-SNAPSHOT and in the next milestone coming in August. Please give that a try and let us know if you experience a problem.

Comment From: anabright

@snicoll thanks for the nudge, unfortunately we migrated our freemarker templates to thymeleaf so we could upgrade our project to Spring Boot 3 and it's not in our plans to revert the changes.

Comment From: yusuhua

I tried freemarker 2.3.33, unfortunately the RequestParameters in the .ftl file still point to the freemarker.ext.servlet. path instead of the freemarker.ext.jakarta.servlet. path, how should I modify it?

Comment From: bclozel

@yusuhua I can't find any reference to "freemarker.ext.servlet" in our codebase. If you're requesting assistance for your application, please do so on StackOverflow. If you believe you've found a bug in Spring Framework, please create a new issue with a minimal application that reproduces the problem.

Comment From: anabright

If anyone still encounters this issue, the workaround is to have a custom FreeMarkerView and manually add RequestParameters to the model:

public class CustomFreeMarkerView extends FreeMarkerView {
  @Override
  protected SimpleHash buildTemplateModel(Map<String, Object> model, HttpServletRequest request,
      HttpServletResponse response) {
    final SimpleHash fmModel = super.buildTemplateModel(model, request, response);
    fmModel.put(FreemarkerServlet.KEY_REQUEST_PARAMETERS, new HttpRequestParametersHashModel(request));
    return fmModel;
  }
}

(Don't forget to register it as a bean)

In a similar vein, since the upgrade to Spring 6 and Freemarker 2.3.33, session attributes added like this request.getSession().setAttribute("hello", "world") are no longer accesible from a Freemarker template in this way ${hello}.

This is the workaround:

  @Bean
  public CustomFreeMarkerViewResolver viewResolver() {
    CustomFreeMarkerViewResolver viewResolver = new CustomFreeMarkerViewResolver();
    ...
    viewResolver.setExposeSessionAttributes(true);
    return viewResolver;
  }

Hope that helps.

Comment From: blacktoby

(Don't forget to register it as a bean)

Hey @anabright, Could you give an example of how to register the CustomFreeMarkerView? I'm a bit stuck with my spring boot configuration.

Thanks in advance.

Comment From: anabright

(Don't forget to register it as a bean)

Hey @anabright, Could you give an example of how to register the CustomFreeMarkerView? I'm a bit stuck with my spring boot configuration.

Thanks in advance.

Hello! What I did in the end was configure the required view class in the resolver, like this:

public class CustomFreeMarkerViewResolver extends FreeMarkerViewResolver {
  @Override
  protected Class<?> requiredViewClass() {
    return CustomFreeMarkerView.class;
  }
}

and then declare the resolver bean:

@Bean
  public CustomFreeMarkerViewResolver viewResolver() {
    CustomFreeMarkerViewResolver viewResolver = new CustomFreeMarkerViewResolver();
    viewResolver.setCache(true);
    ...
    return viewResolver;
  }

Hope that helps.