Merge pull request #89 from nolaviz/nolaviz-patch1

Add a simple mechanism for runtime debugging of templates.
diff --git a/.asf.yaml b/.asf.yaml
new file mode 100644
index 0000000..bd1b33a
--- /dev/null
+++ b/.asf.yaml
@@ -0,0 +1,33 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+github:
+  description: "Apache Freemarker"
+  homepage: https://freemarker.apache.org/
+  dependabot_alerts: true
+  features:
+    wiki: false
+    issues: false
+    projects: true
+  enabled_merge_buttons:
+    squash:  false
+    merge:   true
+    rebase:  false
+  autolink_jira:
+    - FREEMARKER
+
+notifications:
+  jira_options: link label
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..a40850e
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,74 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: CI
+
+permissions:
+  contents: read
+
+on:
+  workflow_dispatch: { }
+  push:
+    branches: [ '2.3-gae' ]
+  pull_request:
+    branches: [ '2.3-gae' ]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Set up JDK 8
+        uses: actions/setup-java@v3
+        with:
+          java-version: 8
+          distribution: temurin
+
+      - name: Set up JDK 16
+        uses: actions/setup-java@v3
+        with:
+          java-version: 16
+          distribution: zulu
+
+      - name: Prepare build.properties
+        shell: bash
+        run: >-
+          echo "boot.classpath.j2se1.8=${JAVA_HOME_8_X64}/jre/lib/rt.jar" >> build.properties;
+          echo "mvnCommand=$(which mvn)" >> build.properties;
+          echo "gpgCommand=$(which gpg)" >> build.properties;
+
+      - name: Create a gpg key for signing
+        shell: bash
+        run: >-
+          gpg --quick-gen-key --batch --passphrase '' github-build@apache.invalid;
+          echo "batch" >> "/home/runner/.gnupg/gpg.conf";
+          echo "pinentry-mode=loopback" >> "/home/runner/.gnupg/gpg.conf";
+
+      - name: Prepare ant with ivy
+        shell: bash
+        run: ant download-ivy
+
+      - name: Build with Ant and ivy
+        shell: bash
+        run: ant ci -Dci=true
+
+      - name: RAT check
+        shell: bash
+        run: ant rat
diff --git a/.gitignore b/.gitignore
index 6a1796c..2ab1d7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,3 +55,5 @@
 
 **/adhoctest/
 AdhocTest*.*
+
+junit*.properties
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 21bd099..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-# 
-#   http://www.apache.org/licenses/LICENSE-2.0
-# 
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-os: linux
-dist: focal
-
-arch:
-  - amd64
-  - arm64
-
-addons:
-  apt:
-    packages:
-      - openjdk-8-jdk
-      - ant-optional
-
-cache:
-  directories:
-    - $HOME/.ivy-freemarker/cache
-
-before_install:
-  - lscpu
-  - export JAVA_HOME="/usr/lib/jvm/java-8-openjdk-${TRAVIS_CPU_ARCH}/"
-  - export PATH="$JAVA_HOME/bin:$PATH"
-  - java -version
-  - ant -version
-
-install:
-  - ant download-ivy  
-
-script: 
-  - ant ci  
\ No newline at end of file
diff --git a/README.md b/README.md
index e5f87ac..8cfa8c8 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 Apache FreeMarker {version}
 ===========================
 
-[![Build Status](https://travis-ci.org/apache/freemarker.svg?branch=2.3-gae)](https://travis-ci.org/apache/freemarker)
+![Build status](https://github.com/apache/freemarker/actions/workflows/ci.yml/badge.svg)
 
 For the latest version or to report bugs visit:
 https://freemarker.apache.org/
@@ -83,7 +83,7 @@
 included in OpenJDK anymore. It's not needed on Oracle Java 9,
 or if FreeMarker is configured to use Jaxen for XPath.
 
-The minimum required Java version is currently Java SE 7. (The presence
+The minimum required Java version is currently Java SE 8. (The presence
 of a later version may be detected on runtime and utilized by
 FreeMarker.)
 
@@ -106,7 +106,7 @@
 the source code repository. See repository locations here:
 https://freemarker.apache.org/sourcecode.html
 
-You need JDK 8 (not JDK 9!), Apache Ant (tested with 1.9.6) and Ivy (tested
+You need JDK 16, Apache Ant (tested with 1.10.6) and Ivy (integrated into
 with 2.5.0) to be installed. To install Ivy (but be sure it's not already
 installed), issue `ant download-ivy`; it will copy Ivy under `~/.ant/lib`.
 (Alternatively, you can copy `ivy-<version>.jar` into the Ant home `lib`
@@ -114,7 +114,7 @@
 
 It's recommended to copy `build.properties.sample` into `build.properties`,
 and edit its content to fit your system. (Although basic jar building should
-succeeds without the build.properties file too.)
+succeed without the build.properties file too.)
 
 To build `freemarker.jar`, just issue `ant` in the project root directory, and
 it should download all dependencies automatically and build `freemarker.jar`. 
@@ -165,7 +165,7 @@
     Number of imports required for .*: 99
     Number of static imports needed for .*: 1
   - Java -> Installed JRE-s:
-    Ensure that you have JDK 8 installed, and that it was added to Eclipse.
+    Ensure that you have JDK 16 installed, and that it was added to Eclipse.
     Note that it's not JRE, but JDK.
   - Java -> Compiler -> Javadoc:
     "Malformed Javadoc comments": Error
@@ -188,25 +188,22 @@
         src/test/resources
     - On the "Libraries" tab:
       - Delete everyhing from there, except the "JRE System Library [...]"
-      - Edit "JRE System Library [...]" to "Execution Environment" "JavaSE 1.8"
+      - Edit "JRE System Library [...]" to "Execution Environment" "JavaSE 16"
       - Add all jar-s that are directly under the "ide-dependencies" directory
         (use the "Add JARs..." and select all those files).
    - Press "Finish"
 - Eclipse will indicate many errors at this point; it's expected, read on.
 - Project -> Properties -> Java Compiler
-  - Set "Compiler Compliance Level" to "1.7" (you will have to uncheck
-    "Use compliance from execution environment" for that)
   - In Errors/Warnings, check in "Enable project specific settings", then set
     "Forbidden reference (access rules)" from "Error" to "Warning".
 - You will still have errors on these java files (because different java
   files depend on different versions of the same library, and Eclipse can't
   handle that). Exclude those java files from the Build Path (in the Package
-  Explorer, right click on the problematic file -> "Build Path" -> "Exclude"):
+  Explorer, right-click on the problematic file -> "Build Path" -> "Exclude"):
     _Jython20*.java,
     _Jython22*.java,
     _FreeMarkerPageContext2.java,
     FreeMarkerJspFactory2.java,
-    Java8*.java
   Also, close these files if they are open. Now you shouldn't have any errors.
 - At Project -> Properties -> Java Code Style -> Formatter, check in "Enable
   project specific settings", and then select "FreeMarker" as active profile.
@@ -233,7 +230,7 @@
 
 - "New" -> "Project". In order as the IntelliJ will prompt you:
 
-  - Select "Java" on the left side, and "1.8" for SDK on the right side. Press "Next".
+  - Select "Java" on the left side, and "16" for SDK on the right side. Press "Next".
   
   - Template selection: Don't chose anything, "Next"
   
@@ -264,16 +261,16 @@
 
     - Test Resource Folders:  
       src/test/resources
-      
-  - Still inside the "Sources" tab, change the "Language level" to "7". (Yes, we use Java 8 SDK with
-    language level 7 in the IDE, due to the tricks FreeMarker uses to support different Java versions.)
-    
+
+  - Still inside the "Sources" tab, change the "Language level" to "8". (Yes, we use Java 16 SDK with
+    language level 8 in the IDE, due to the tricks FreeMarker uses to support different Java versions.)
+    Then, in "File" -> "Settings" -> "Editor" -> "Inspections", uncheck "Uses of API which isn't available at the 
+    configured language level".
+
   - Switch over to the "Dependencies" tab (still inside "Project Structure" / "Modules"), and add
     all the jar-s inside the `ide-dependencies` directory as dependency. (How: Click the "+" icon
     at the right edge, select "JARs or directory", navigate to `ide-dependencies` directory, expand
-    it, then range-select all the jars in it. Thus you add all of them at once.) After all jar-s were added,
-    find  dom4j-*.jar in the table, and move it to the bottom of the table (otherwise it shadows some
-    Jaxen classes with a too old version).
+    it, then range-select all the jars in it. Thus, you add all of them at once.)
 
 - "File" -> "Settings" -> "Build, Execution, Deployment" -> "Compiler" -> "Excludes":
   Add source files that match these (you simply find them manually, and add their absolute path):  
@@ -281,7 +278,6 @@
     _Jython22*.java,  
     _FreeMarkerPageContext2.java,  
     FreeMarkerJspFactory2.java,  
-    Java8*.java  
 
 - You may do "Build" / "Build project" (Ctrl+F9) to see if everything compiles now.
     
diff --git a/build.properties.sample b/build.properties.sample
index d5e31cc..dd796c3 100644
--- a/build.properties.sample
+++ b/build.properties.sample
@@ -16,8 +16,6 @@
 # under the License.
 
 # Copy this file to "build.properties" before editing!
-# These propeties should point to the rt.jar-s of the respective J2SE versions:
-boot.classpath.j2se1.7=c:/Program Files/Java/jre7/lib/rt.jar
 boot.classpath.j2se1.8=C:/Program Files/Java/jdk1.8.0_66/jre/lib/rt.jar
 mvnCommand=C:/Program Files (x86)/maven3/bin/mvn.bat
 gpgCommand=C:/Program Files (x86)/GNU/GnuPG/pub/gpg.exe
\ No newline at end of file
diff --git a/build.xml b/build.xml
index 85dc05d..c16e571 100644
--- a/build.xml
+++ b/build.xml
@@ -19,12 +19,13 @@
 -->
 
 <project basedir="." default="jar" name="freemarker"
-  xmlns:ivy="antlib:org.apache.ivy.ant"
-  xmlns:javacc="http://javacc.dev.java.net/"
-  xmlns:docgen="http://freemarker.org/docgen"
-  xmlns:bnd="http://www.aqute.biz/bnd"
-  xmlns:rat="antlib:org.apache.rat.anttasks"
-  xmlns:u="http://freemarker.org/util"
+         xmlns:ivy="antlib:org.apache.ivy.ant"
+         xmlns:javacc="http://javacc.dev.java.net/"
+         xmlns:docgen="http://freemarker.org/docgen"
+         xmlns:bnd="http://www.aqute.biz/bnd"
+         xmlns:rat="antlib:org.apache.rat.anttasks"
+         xmlns:u="http://freemarker.org/util"
+         xmlns:unless="ant:unless"
 >
 
   <!-- ================================================================== -->
@@ -43,30 +44,21 @@
   <property name="server.ivy.repo.root" value="${basedir}/build/dummy-server-ivy-repo" />
 
   <property file="build.properties"/>
-  <condition property="has.explicit.boot.classpath.j2se1.7">
-    <isset property="boot.classpath.j2se1.7"/>
-  </condition>
   <condition property="has.explicit.boot.classpath.j2se1.8">
     <isset property="boot.classpath.j2se1.8"/>
   </condition>
   <condition property="has.all.explicit.boot.classpaths">
     <and>
-      <isset property="has.explicit.boot.classpath.j2se1.7"/>
       <isset property="has.explicit.boot.classpath.j2se1.8"/>
     </and>
   </condition>
   <available property="atLeastJDK8" classname="java.util.function.Predicate"/>
 
-  <!-- When boot.classpath.j2se* is missing, these will be the defaults: -->
+  <!-- When boot.classpath.j* is missing, these will be the defaults: -->
   <!-- Note: Target "dist" doesn't allow using these. -->
-  <property name="boot.classpath.j2se1.7" value="${sun.boot.class.path}" />
   <property name="boot.classpath.j2se1.8" value="${sun.boot.class.path}" />
 
   <!-- For checking the correctness of the boot.classpath.j2se* -->
-  <available classpath="${boot.classpath.j2se1.7}"
-    classname="java.nio.file.Path" ignoresystemclasses="true"
-    property="boot.classpath.j2se1.7.correct"
-  />
   <available classpath="${boot.classpath.j2se1.8}"
     classname="java.time.Instant" ignoresystemclasses="true"
     property="boot.classpath.j2se1.8.correct"
@@ -155,11 +147,6 @@
     <delete dir="build/javacc-home.tmp" />
 
     <replace
-      file="${_javaccOutputDir}/FMParser.java"
-      token="private final LookaheadSuccess"
-      value="private static final LookaheadSuccess"
-    />
-    <replace
       file="${_javaccOutputDir}/FMParserConstants.java"
       token="public interface FMParserConstants"
       value="interface FMParserConstants"
@@ -197,12 +184,6 @@
   </target>
 
   <target name="compile" depends="javacc">
-    <fail unless="boot.classpath.j2se1.7.correct"><!--
-      -->The "boot.classpath.j2se1.7" property value (${boot.classpath.j2se1.7}) <!--
-      -->seems to be an incorrect boot classpath. Please fix it in <!--
-      -->the &lt;projectDir>/build.properties file, or wherever you <!--
-      -->set it.<!--
-    --></fail>
     <fail unless="boot.classpath.j2se1.8.correct"><!--
       -->The "boot.classpath.j2se1.8" property value (${boot.classpath.j2se1.8}) <!--
       -->seems to be an incorrect boot classpath. Please fix it in <!--
@@ -211,8 +192,8 @@
     --></fail>
     <echo level="info"><!--
       -->Using boot classpaths: <!--
-      -->Java 7: ${boot.classpath.j2se1.7};<!--
       -->Java 8: ${boot.classpath.j2se1.8}<!--
+      -->Java 16: Current JDK classpath<!--
     --></echo>
 
     <!-- Comment out @SuppressFBWarnings, as it causes compilation warnings in dependent Gradle projects -->
@@ -239,10 +220,10 @@
     <!-- Note: the "build.base" conf doesn't include optional FreeMarker dependencies. -->
     <ivy:cachepath conf="build.base" pathid="ivy.dep" />
     <javac destdir="build/classes" deprecation="off"
-      debug="on" optimize="off" target="1.7" source="1.7" encoding="utf-8"
+      debug="on" optimize="off" target="1.8" source="1.8" encoding="utf-8"
       includeantruntime="false"
       classpathref="ivy.dep"
-      bootclasspath="${boot.classpath.j2se1.7}"
+      bootclasspath="${boot.classpath.j2se1.8}"
       excludes="
         freemarker/core/_Java?*Impl.java,
         freemarker/ext/jsp/**,
@@ -261,25 +242,18 @@
 
     <ivy:cachepath conf="build.base" pathid="ivy.dep" />
     <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
-      debug="on" optimize="off" target="1.8" source="1.8" encoding="utf-8"
-      includeantruntime="false"
-      classpathref="ivy.dep"
-      bootclasspath="${boot.classpath.j2se1.8}"
-      includes="freemarker/core/_Java8Impl.java"
-    />
-
-    <rmic
-      base="build/classes" includes="freemarker/debug/impl/Rmi*Impl.class"
-      classpathref="ivy.dep"
-      verify="yes" stubversion="1.2"
+           debug="on" optimize="off" release="16" encoding="utf-8"
+           includeantruntime="false"
+           classpathref="ivy.dep"
+           includes="freemarker/core/_Java16Impl.java"
     />
 
     <ivy:cachepath conf="build.jsp2.0" pathid="ivy.dep.jsp2.0" />
     <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
-      debug="on" optimize="off" target="1.7" source="1.7" encoding="utf-8"
+      debug="on" optimize="off" target="1.8" source="1.8" encoding="utf-8"
       includeantruntime="false"
       classpathref="ivy.dep.jsp2.0"
-      bootclasspath="${boot.classpath.j2se1.7}"
+      bootclasspath="${boot.classpath.j2se1.8}"
       includes="
         freemarker/ext/jsp/**,
         freemarker/ext/servlet/**,
@@ -294,10 +268,10 @@
 
     <ivy:cachepath conf="build.jsp2.1" pathid="ivy.dep.jsp2.1" />
     <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
-      debug="on" optimize="off" target="1.7" source="1.7" encoding="utf-8"
+      debug="on" optimize="off" target="1.8" source="1.8" encoding="utf-8"
       includeantruntime="false"
       classpathref="ivy.dep.jsp2.1"
-      bootclasspath="${boot.classpath.j2se1.7}"
+      bootclasspath="${boot.classpath.j2se1.8}"
       includes="
         freemarker/ext/jsp/_FreeMarkerPageContext21.java,
         freemarker/ext/jsp/FreeMarkerJspFactory21.java,
@@ -306,10 +280,10 @@
 
     <ivy:cachepath conf="build.jython2.0" pathid="ivy.dep.jython2.0" />
     <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
-      debug="on" optimize="off" target="1.7" source="1.7" encoding="utf-8"
+      debug="on" optimize="off" target="1.8" source="1.8" encoding="utf-8"
       includeantruntime="false"
       classpathref="ivy.dep.jython2.0"
-      bootclasspath="${boot.classpath.j2se1.7}"
+      bootclasspath="${boot.classpath.j2se1.8}"
       includes="
         freemarker/ext/ant/**,
         freemarker/template/utility/JythonRuntime.java,
@@ -321,29 +295,24 @@
 
     <ivy:cachepath conf="build.jython2.2" pathid="ivy.dep.jython2.2" />
     <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
-      debug="on" optimize="off" target="1.7" source="1.7" encoding="utf-8"
+      debug="on" optimize="off" target="1.8" source="1.8" encoding="utf-8"
       includeantruntime="false"
       classpathref="ivy.dep.jython2.2"
-      bootclasspath="${boot.classpath.j2se1.7}"
+      bootclasspath="${boot.classpath.j2se1.8}"
       includes="
         freemarker/ext/jython/_Jython22VersionAdapter.java"
     />
 
     <ivy:cachepath conf="build.jython2.5" pathid="ivy.dep.jython2.5" />
     <javac srcdir="build/src-main-java-filtered" destdir="build/classes" deprecation="off"
-      debug="on" optimize="off" target="1.7" source="1.7" encoding="utf-8"
+      debug="on" optimize="off" target="1.8" source="1.8" encoding="utf-8"
       includeantruntime="false"
       classpathref="ivy.dep.jython2.5"
-      bootclasspath="${boot.classpath.j2se1.7}"
+      bootclasspath="${boot.classpath.j2se1.8}"
       includes="
         freemarker/ext/jython/_Jython25VersionAdapter.java"
     />
 
-    <rmic base="build/classes" classpathref="ivy.dep"
-      includes="build/src-main-java-filtered/freemarker/debug/impl/Rmi*Impl.class"
-      verify="yes" stubversion="1.2"
-    />
-
     <copy toDir="build/classes">
       <fileset dir="src/main/resources"
         excludes="
@@ -367,11 +336,10 @@
 
     <ivy:cachepath conf="build.test" pathid="ivy.dep.build.test" />
     <javac srcdir="src/test/java" destdir="build/test-classes" deprecation="off"
-      debug="on" optimize="off" target="1.8" source="1.8" encoding="utf-8"
+      debug="on" optimize="off" release="16" encoding="utf-8"
       includeantruntime="false"
       classpath="build/classes"
       classpathref="ivy.dep.build.test"
-      bootclasspath="${boot.classpath.j2se1.8}"
     />
     <copy toDir="build/test-classes">
       <fileset dir="src/test/resources"
@@ -438,7 +406,7 @@
   <!-- Generate docs                                                     -->
   <!-- ================================================================= -->
 
-  <target name="_rawJavadoc" depends="compile">
+  <target name="javadoc" depends="compile" description="Build the JavaDocs">
     <mkdir dir="build/api" />
     <delete includeEmptyDirs="yes">
       <fileset dir="build/api" includes="**/*" />
@@ -491,81 +459,11 @@
       encoding="UTF-8"
       locale="en_US"
     >
-      <link href="http://docs.oracle.com/javase/8/docs/api/"/>
+      <link href="https://docs.oracle.com/en/java/javase/16/docs/api/"/>
     </javadoc>
     <delete dir="build/javadoc-sources" />
   </target>
 
-  <target name="javadoc" depends="_rawJavadoc, _fixJDK8JavadocCSS" description="Build the JavaDocs" />
-
-  <target name="_fixJDK8JavadocCSS" depends="_rawJavadoc" if="atLeastJDK8">
-    <property name="file" value="build/api/stylesheet.css" />
-
-    <available file="${file}" property="stylesheet.available"/>
-    <fail unless="stylesheet.available">CSS file not found: ${file}</fail>
-    <echo>Fixing JDK 8 CSS in ${file}</echo>
-
-    <!-- Tell that it's modified: -->
-    <replaceregexp
-        file="${file}" flags="gs" encoding="utf-8"
-        match="/\* (Javadoc style sheet) \*/" replace="/\* \1 - JDK 8 usability fix regexp substitutions applied \*/"
-    />
-
-    <!-- Remove broken link: -->
-    <replaceregexp
-        file="${file}" flags="gs" encoding="utf-8"
-        match="@import url\('resources/fonts/dejavu.css'\);\s*" replace=""
-    />
-
-    <!-- Font family fixes: -->
-    <replaceregexp
-        file="${file}" flags="gsi" encoding="utf-8"
-        match="['&quot;]DejaVu Sans['&quot;]" replace="Arial"
-    />
-    <replaceregexp
-        file="${file}" flags="gsi" encoding="utf-8"
-        match="['&quot;]DejaVu Sans Mono['&quot;]" replace="'Courier New'"
-    />
-    <replaceregexp
-        file="${file}" flags="gsi" encoding="utf-8"
-        match="['&quot;]DejaVu Serif['&quot;]" replace="Arial"
-    />
-    <replaceregexp
-        file="${file}" flags="gsi" encoding="utf-8"
-        match="(?&lt;=[\s,:])serif\b" replace="sans-serif"
-    />
-    <replaceregexp
-        file="${file}" flags="gsi" encoding="utf-8"
-        match="(?&lt;=[\s,:])Georgia,\s*" replace=""
-    />
-    <replaceregexp
-        file="${file}" flags="gsi" encoding="utf-8"
-        match="['&quot;]Times New Roman['&quot;],\s*" replace=""
-    />
-    <replaceregexp
-        file="${file}" flags="gsi" encoding="utf-8"
-        match="(?&lt;=[\s,:])Times,\s*" replace=""
-    />
-    <replaceregexp
-        file="${file}" flags="gsi" encoding="utf-8"
-        match="(?&lt;=[\s,:])Arial\s*,\s*Arial\b" replace="Arial"
-    />
-
-    <!-- "Parameters:", "Returns:", "Throws:", "Since:", "See also:" etc. fixes: -->
-    <property name="ddSelectorStart" value="(?:\.contentContainer\s+\.(?:details|description)|\.serializedFormContainer)\s+dl\s+dd\b.*?\{[^\}]*\b" />
-    <property name="ddPropertyEnd" value="\b.+?;" />
-    <!-- - Put back description (dd) indentation: -->
-    <replaceregexp
-        file="${file}" flags="gs" encoding="utf-8"
-        match="(${ddSelectorStart})margin${ddPropertyEnd}" replace="\1margin: 5px 0 10px 20px;"
-    />
-    <!-- - No monospace font for the description (dd) part: -->
-    <replaceregexp
-        file="${file}" flags="gs" encoding="utf-8"
-        match="(${ddSelectorStart})font-family${ddPropertyEnd}" replace="\1"
-    />
-  </target>
-
   <!-- ====================== -->
   <!-- Manual                 -->
   <!-- ====================== -->
@@ -753,11 +651,12 @@
       <input
          validargs="y,n"
          addproperty="signatureGood"
+         unless:set="ci"
       >Is the above signer the intended one for Apache releases?</input>
-      <condition property="signatureGood.y">
+      <condition property="signatureGood.y" unless:set="ci">
         <equals arg1="y" arg2="${signatureGood}"/>
       </condition>
-      <fail unless="signatureGood.y" message="Task aborted by user." />
+      <fail unless:set="ci" unless="signatureGood.y" message="Task aborted by user." />
 
       <echo>Creating checksum file for "${archive.gzip}"...</echo>
       <checksum file="${archive.gzip}" fileext=".sha512" algorithm="SHA-512" forceOverwrite="yes" />
@@ -957,7 +856,7 @@
   </target>
 
   <target name="ci"
-  	depends="ci-setup, clean, jar, test, javadoc"
+  	depends="ci-setup, clean, _dist"
   	description="CI should invoke this task"
   />
 
diff --git a/ivy.xml b/ivy.xml
index dad9ff2..cc573d2 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -21,7 +21,8 @@
   AFTER CHANGING THIS FILE don't forget to issue: ant update-deps
 -->
 <!DOCTYPE ivy-module [
-    <!ENTITY jetty.version "7.6.21.v20160908">
+    <!ENTITY jetty.version "9.4.53.v20231009">
+    <!ENTITY standardTaglibs.version "1.2.5">
     <!ENTITY slf4j.version "1.6.1">
     <!ENTITY spring.version "2.5.6.SEC03">
 ]>
@@ -143,9 +144,12 @@
 
     <dependency org="org.eclipse.jetty" name="jetty-server" rev="&jetty.version;" conf="test->default" />
     <dependency org="org.eclipse.jetty" name="jetty-webapp" rev="&jetty.version;" conf="test->default" />
-    <dependency org="org.eclipse.jetty" name="jetty-jsp" rev="&jetty.version;" conf="test->default" />
     <dependency org="org.eclipse.jetty" name="jetty-util" rev="&jetty.version;" conf="test->default" />
-    
+    <dependency org="org.eclipse.jetty" name="apache-jsp" rev="&jetty.version;" conf="test->default" />
+    <!-- JSP JSTL (not included in Jetty): -->
+    <dependency org="org.apache.taglibs" name="taglibs-standard-impl" rev="&standardTaglibs.version;" conf="test->default" />
+    <dependency org="org.apache.taglibs" name="taglibs-standard-spec" rev="&standardTaglibs.version;" conf="test->default" />
+
     <dependency org="displaytag" name="displaytag" rev="1.2" conf="test->default">
       <exclude org="com.lowagie" name="itext" />
       <exclude org="org.slf4j" name="slf4j-log4j12" />
@@ -168,7 +172,7 @@
     
     <!-- parser -->
     
-    <dependency org="net.java.dev.javacc" name="javacc" rev="6.1.2" conf="parser->default" />
+    <dependency org="net.java.dev.javacc" name="javacc" rev="7.0.12" conf="parser->default" />
     
     <!-- bnd -->
     
diff --git a/osgi.bnd b/osgi.bnd
index 3cf11c1..491eb84 100644
--- a/osgi.bnd
+++ b/osgi.bnd
@@ -49,10 +49,10 @@
 # This is needed for "a.class.from.another.Bundle"?new() to work.
 DynamicImport-Package: *
 
-# The required minimum is 1.7, but we utilize versions up to 1.8 if available.
-# See also: http://wiki.eclipse.org/Execution_Environments, "Compiling
-# against more than is required"
-Bundle-RequiredExecutionEnvironment: JavaSE-1.8, JavaSE-1.7
+# List whole version range between minimum up to where we use new features
+# if available. See also: http://wiki.eclipse.org/Execution_Environments,
+# "Compiling against more than is required"
+Bundle-RequiredExecutionEnvironment: JavaSE-16, JavaSE-15, JavaSE-14, JavaSE-13, JavaSE-12, JavaSE-11, JavaSE-10, JavaSE-9, JavaSE-1.8
 
 # Non-OSGi meta:
 Main-Class: freemarker.core.CommandLine
diff --git a/src/main/java/freemarker/cache/StringTemplateLoader.java b/src/main/java/freemarker/cache/StringTemplateLoader.java
index dd1610a..28c1bff 100644
--- a/src/main/java/freemarker/cache/StringTemplateLoader.java
+++ b/src/main/java/freemarker/cache/StringTemplateLoader.java
@@ -56,7 +56,7 @@
  *   cfg.setTemplateLoader(stringLoader);
  * </pre>
  * <p>After that you should be able to use the templates as usual. Often you will
- * want to combine a <tt>StringTemplateLoader</tt> with another loader. You can
+ * want to combine a {@code StringTemplateLoader} with another loader. You can
  * do so using a {@link freemarker.cache.MultiTemplateLoader}.
  */
 public class StringTemplateLoader implements TemplateLoader {
@@ -66,7 +66,7 @@
     /**
      * Puts a template into the loader. A call to this method is identical to 
      * the call to the three-arg {@link #putTemplate(String, String, long)} 
-     * passing <tt>System.currentTimeMillis()</tt> as the third argument.
+     * passing {@code System.currentTimeMillis()} as the third argument.
      * 
      * <p>Note that this method is not thread safe! Don't call it after FreeMarker has started using this template
      * loader.
@@ -95,7 +95,7 @@
      * @param name the name of the template.
      * @param templateContent the source code of the template.
      * @param lastModified the time of last modification of the template in 
-     * terms of <tt>System.currentTimeMillis()</tt>
+     * terms of {@code System.currentTimeMillis()}
      */
     public void putTemplate(String name, String templateContent, long lastModified) {
         templates.put(name, new StringTemplateSource(name, templateContent, lastModified));
diff --git a/src/main/java/freemarker/cache/TemplateLoader.java b/src/main/java/freemarker/cache/TemplateLoader.java
index 9b837dc..1d3f796 100644
--- a/src/main/java/freemarker/cache/TemplateLoader.java
+++ b/src/main/java/freemarker/cache/TemplateLoader.java
@@ -65,7 +65,7 @@
      *            loader-defined root location (often referred as the "template root directory"), and will never start
      *            with a slash, nor will they contain a path component consisting of either a single or a double dot --
      *            these are all resolved by the template cache before passing the name to the loader. As a side effect,
-     *            paths that trivially reach outside template root directory, such as <tt>../my.ftl</tt>, will be
+     *            paths that trivially reach outside template root directory, such as {@code ../my.ftl}, will be
      *            rejected by the template cache, so they never reach the template loader. Note again, that if the path
      *            uses backslash as path separator instead of slash as (the template loader should not accept that), the
      *            normalization will not properly happen, as FreeMarker (the cache) recognizes only the slashes as
diff --git a/src/main/java/freemarker/core/AddConcatExpression.java b/src/main/java/freemarker/core/AddConcatExpression.java
index f02be12..d2c5745 100644
--- a/src/main/java/freemarker/core/AddConcatExpression.java
+++ b/src/main/java/freemarker/core/AddConcatExpression.java
@@ -19,13 +19,17 @@
 
 package freemarker.core;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import freemarker.template.SimpleNumber;
 import freemarker.template.SimpleScalar;
 import freemarker.template.SimpleSequence;
 import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateCollectionModelEx;
 import freemarker.template.TemplateException;
 import freemarker.template.TemplateHashModel;
 import freemarker.template.TemplateHashModelEx;
@@ -191,9 +195,10 @@
         return ParameterRole.forBinaryOperatorOperand(idx);
     }
 
-    private static final class ConcatenatedSequence
+    // Non-private for unit testing
+    static final class ConcatenatedSequence
     implements
-        TemplateSequenceModel {
+        TemplateSequenceModel, TemplateCollectionModelEx {
         private final TemplateSequenceModel left;
         private final TemplateSequenceModel right;
 
@@ -203,16 +208,254 @@
         }
 
         @Override
-        public int size()
-        throws TemplateModelException {
-            return left.size() + right.size();
+        public int size() throws TemplateModelException {
+            int totalSize = 0;
+
+            ConcatenatedSequence[] concSeqsWithRightPending = new ConcatenatedSequence[2];
+            int concSeqsWithRightPendingLength = 0;
+            ConcatenatedSequence concSeqInFocus = this;
+
+            while (true) {
+                TemplateSequenceModel left;
+                while ((left = concSeqInFocus.left) instanceof ConcatenatedSequence) {
+                    if (concSeqsWithRightPendingLength == concSeqsWithRightPending.length) {
+                        concSeqsWithRightPending = Arrays.copyOf(concSeqsWithRightPending, concSeqsWithRightPendingLength * 2);
+                    }
+                    concSeqsWithRightPending[concSeqsWithRightPendingLength++] = concSeqInFocus;
+                    concSeqInFocus = (ConcatenatedSequence) left;
+                }
+                totalSize += left.size();
+
+                while (true) {
+                    TemplateSequenceModel right = concSeqInFocus.right;
+                    if (right instanceof ConcatenatedSequence) {
+                        concSeqInFocus = (ConcatenatedSequence) right;
+                        break; // To jump at the left-descending loop
+                    }
+                    totalSize += right.size();
+
+                    if (concSeqsWithRightPendingLength == 0) {
+                        return totalSize;
+                    }
+
+                    concSeqsWithRightPendingLength--;
+                    concSeqInFocus = concSeqsWithRightPending[concSeqsWithRightPendingLength];
+                }
+            }
         }
 
         @Override
-        public TemplateModel get(int i)
-        throws TemplateModelException {
-            int ls = left.size();
-            return i < ls ? left.get(i) : right.get(i - ls);
+        public boolean isEmpty() throws TemplateModelException {
+            ConcatenatedSequence[] concSeqsWithRightPending = new ConcatenatedSequence[2];
+            int concSeqsWithRightPendingLength = 0;
+            ConcatenatedSequence concSeqInFocus = this;
+
+            while (true) {
+                TemplateSequenceModel left;
+                while ((left = concSeqInFocus.left) instanceof ConcatenatedSequence) {
+                    if (concSeqsWithRightPendingLength == concSeqsWithRightPending.length) {
+                        concSeqsWithRightPending = Arrays.copyOf(concSeqsWithRightPending, concSeqsWithRightPendingLength * 2);
+                    }
+                    concSeqsWithRightPending[concSeqsWithRightPendingLength++] = concSeqInFocus;
+                    concSeqInFocus = (ConcatenatedSequence) left;
+                }
+                if (!isEmpty(left)) {
+                    return false;
+                }
+
+                while (true) {
+                    TemplateSequenceModel right = concSeqInFocus.right;
+                    if (right instanceof ConcatenatedSequence) {
+                        concSeqInFocus = (ConcatenatedSequence) right;
+                        break; // To jump at the left-descending loop
+                    }
+                    if (!isEmpty(right)) {
+                        return false;
+                    }
+
+                    if (concSeqsWithRightPendingLength == 0) {
+                        return true;
+                    }
+
+                    concSeqsWithRightPendingLength--;
+                    concSeqInFocus = concSeqsWithRightPending[concSeqsWithRightPendingLength];
+                }
+            }
+        }
+
+        private static boolean isEmpty(TemplateSequenceModel seq) throws TemplateModelException {
+            return seq instanceof TemplateCollectionModelEx ? ((TemplateCollectionModelEx) seq).isEmpty()
+                    : seq.size() == 0;
+        }
+
+        @Override
+        public TemplateModel get(int index) throws TemplateModelException {
+            if (index < 0) {
+                return null;
+            }
+
+            int totalSize = 0;
+
+            ConcatenatedSequence[] concSeqsWithRightPending = new ConcatenatedSequence[2];
+            int concSeqsWithRightPendingLength = 0;
+            ConcatenatedSequence concSeqInFocus = this;
+
+            while (true) {
+                TemplateSequenceModel left;
+                while ((left = concSeqInFocus.left) instanceof ConcatenatedSequence) {
+                    if (concSeqsWithRightPendingLength == concSeqsWithRightPending.length) {
+                        concSeqsWithRightPending = Arrays.copyOf(concSeqsWithRightPending, concSeqsWithRightPendingLength * 2);
+                    }
+                    concSeqsWithRightPending[concSeqsWithRightPendingLength++] = concSeqInFocus;
+                    concSeqInFocus = (ConcatenatedSequence) left;
+                }
+                {
+                    int segmentSize = left.size();
+                    totalSize += segmentSize;
+                    if (totalSize > index) {
+                        return left.get(index - (totalSize - segmentSize));
+                    }
+                }
+
+                while (true) {
+                    TemplateSequenceModel right = concSeqInFocus.right;
+                    if (right instanceof ConcatenatedSequence) {
+                        concSeqInFocus = (ConcatenatedSequence) right;
+                        break; // To jump at the left-descending loop
+                    }
+                    {
+                        int segmentSize = right.size();
+                        totalSize += segmentSize;
+                        if (totalSize > index) {
+                            return right.get(index - (totalSize - segmentSize));
+                        }
+                    }
+
+                    if (concSeqsWithRightPendingLength == 0) {
+                        return null;
+                    }
+
+                    concSeqsWithRightPendingLength--;
+                    concSeqInFocus = concSeqsWithRightPending[concSeqsWithRightPendingLength];
+                }
+            }
+        }
+
+        @Override
+        public TemplateModelIterator iterator() throws TemplateModelException {
+            return new ConcatenatedSequenceIterator(this);
+        }
+
+    }
+
+    private static class ConcatenatedSequenceIterator implements TemplateModelIterator {
+        /** The path from the root down to the parent of {@link #currentSegment} */
+        private final List<ConcatenatedSequence> concSeqsWithRightPending = new ArrayList<>();
+        private ConcatenatedSequence concSeqWithLeftDescentPending;
+
+        private TemplateSequenceModel currentSegment;
+        private int currentSegmentNextIndex;
+        private TemplateModelIterator currentSegmentIterator;
+
+        private boolean hasPrefetchedResult;
+        private TemplateModel prefetchedNext;
+        private boolean prefetchedHasNext;
+
+        public ConcatenatedSequenceIterator(ConcatenatedSequence concatSeq) throws TemplateModelException {
+            // Descent down to the first nested sequence, and memorize the path down there
+            concSeqWithLeftDescentPending = concatSeq;
+        }
+
+        @Override
+        public TemplateModel next() throws TemplateModelException {
+            ensureHasPrefetchedResult();
+
+            if (!prefetchedHasNext) {
+                throw new TemplateModelException("The collection has no more elements.");
+            }
+
+            TemplateModel result = prefetchedNext;
+            hasPrefetchedResult = false; // To consume prefetched element
+            prefetchedNext = null; // To not prevent GC
+            return result;
+        }
+
+        @Override
+        public boolean hasNext() throws TemplateModelException {
+            ensureHasPrefetchedResult();
+            return prefetchedHasNext;
+        }
+
+        private void ensureHasPrefetchedResult() throws TemplateModelException {
+            if (hasPrefetchedResult) {
+                return;
+            }
+
+            while (true) {
+                // Try to fetch the next value from the current segment:
+                if (currentSegmentIterator != null) {
+                    boolean hasNext = currentSegmentIterator.hasNext();
+                    if (hasNext) {
+                        prefetchedNext = currentSegmentIterator.next();
+                        prefetchedHasNext = true;
+                        hasPrefetchedResult = true;
+                        return;
+                    } else {
+                        currentSegmentIterator = null;
+                        // Falls through
+                    }
+                } else if (currentSegment != null) {
+                    int size = currentSegment.size();
+                    if (currentSegmentNextIndex < size) {
+                        prefetchedNext = currentSegment.get(currentSegmentNextIndex++);
+                        prefetchedHasNext = true;
+                        hasPrefetchedResult = true;
+                        return;
+                    } else {
+                        currentSegment = null;
+                        // Falls through
+                    }
+                } else if (concSeqWithLeftDescentPending != null) { // Nothing to fetch from, has to descend left first
+                    ConcatenatedSequence leftDescentCurrentConcSeq = concSeqWithLeftDescentPending;
+                    concSeqWithLeftDescentPending = null;
+                    concSeqsWithRightPending.add(leftDescentCurrentConcSeq);
+
+                    TemplateSequenceModel leftSeq;
+                    while ((leftSeq = leftDescentCurrentConcSeq.left) instanceof ConcatenatedSequence) {
+                        leftDescentCurrentConcSeq = (ConcatenatedSequence) leftSeq;
+                        concSeqsWithRightPending.add(leftDescentCurrentConcSeq);
+                    }
+                    setCurrentSegment(leftSeq);
+                    continue; // Jump to fetching from current segment
+                }
+
+                // If we reach this, then the current segment was fully consumed, so we have to switch to the next segment.
+
+                if (concSeqsWithRightPending.isEmpty()) {
+                    prefetchedNext = null;
+                    prefetchedHasNext = false;
+                    hasPrefetchedResult = true;
+                    return;
+                }
+
+                TemplateSequenceModel right = concSeqsWithRightPending.remove(concSeqsWithRightPending.size() - 1).right;
+                if (right instanceof ConcatenatedSequence) {
+                    concSeqWithLeftDescentPending = (ConcatenatedSequence) right;
+                } else {
+                    setCurrentSegment(right);
+                }
+            }
+        }
+
+        private void setCurrentSegment(TemplateSequenceModel currentSegment) throws TemplateModelException {
+            if (currentSegment instanceof TemplateCollectionModel) {
+                this.currentSegmentIterator = ((TemplateCollectionModel) currentSegment).iterator();
+                this.currentSegment = null;
+            } else {
+                this.currentSegment = currentSegment;
+                this.currentSegmentNextIndex = 0;
+                this.currentSegmentIterator = null;
+            }
         }
     }
 
diff --git a/src/main/java/freemarker/core/_Java8.java b/src/main/java/freemarker/core/BuiltInBannedWhenForcedAutoEscaping.java
similarity index 73%
copy from src/main/java/freemarker/core/_Java8.java
copy to src/main/java/freemarker/core/BuiltInBannedWhenForcedAutoEscaping.java
index e1a3954..c877fa8 100644
--- a/src/main/java/freemarker/core/_Java8.java
+++ b/src/main/java/freemarker/core/BuiltInBannedWhenForcedAutoEscaping.java
@@ -18,17 +18,8 @@
  */
 package freemarker.core;
 
-import java.lang.reflect.Method;
-
 /**
- * Used internally only, might change without notice!
- * Used for accessing functionality that's only present in Java 8 or later.
+ * A built-in whose usage is banned when auto-escaping is in the "forced" state.
+ * This is just a marker; the actual checking is in {@code FTL.jj}. 
  */
-public interface _Java8 {
-
-    /**
-     * Returns if it's a Java 8 "default method".
-     */
-    boolean isDefaultMethod(Method method);
-    
-}
+interface BuiltInBannedWhenForcedAutoEscaping {}
diff --git a/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java b/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java
index b4b9fe8..7397924 100644
--- a/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java
+++ b/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java
@@ -23,7 +23,7 @@
 
 class BuiltInsForOutputFormatRelated {
 
-    static class no_escBI extends AbstractConverterBI {
+    static class no_escBI extends AbstractConverterBI implements BuiltInBannedWhenForcedAutoEscaping {
 
         @Override
         protected TemplateModel calculateResult(String lho, MarkupOutputFormat outputFormat, Environment env)
diff --git a/src/main/java/freemarker/core/BuiltInsForSequences.java b/src/main/java/freemarker/core/BuiltInsForSequences.java
index 3d2b9fc..4daf8ef 100644
--- a/src/main/java/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/freemarker/core/BuiltInsForSequences.java
@@ -688,7 +688,7 @@
         }
 
         /**
-         * Sorts a sequence for the <tt>sort</tt> and <tt>sort_by</tt>
+         * Sorts a sequence for the {@code sort} and {@code sort_by}
          * built-ins.
          * 
          * @param seq the sequence to sort.
diff --git a/src/main/java/freemarker/core/CommandLine.java b/src/main/java/freemarker/core/CommandLine.java
index ab94d2e..2107a3c 100644
--- a/src/main/java/freemarker/core/CommandLine.java
+++ b/src/main/java/freemarker/core/CommandLine.java
@@ -24,7 +24,7 @@
 import freemarker.template.utility.DateUtil;
 
 /**
- * FreeMarker command-line utility, the Main-Class of <tt>freemarker.jar</tt>.
+ * FreeMarker command-line utility, the Main-Class of {@code freemarker.jar}.
  * Currently it just prints the version number.
  * 
  * @deprecated Will be removed (main method in a library, often classified as CWE-489 "Leftover Debug Code").
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index 9da3113..1882025 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -608,40 +608,40 @@
      *   "expr" evaluates to null:
      *   <ul>
      *     <li>
-     *       in <tt>&lt;assign varname=expr&gt;</tt> directive, 
-     *       or in <tt>${expr}</tt> directive,
-     *       or in <tt>otherexpr == expr</tt>,
-     *       or in <tt>otherexpr != expr</tt>, 
-     *       or in <tt>hash[expr]</tt>,
-     *       or in <tt>expr[keyOrIndex]</tt> (since 2.3.20),
-     *       or in <tt>expr.key</tt> (since 2.3.20),
+     *       in <code>&lt;assign varname=expr&gt;</code> directive, 
+     *       or in <code>${expr}</code> directive,
+     *       or in {@code otherexpr == expr},
+     *       or in {@code otherexpr != expr}, 
+     *       or in {@code hash[expr]},
+     *       or in {@code expr[keyOrIndex]} (since 2.3.20),
+     *       or in {@code expr.key} (since 2.3.20),
      *       then it's treated as empty string.
      *     </li>
-     *     <li>as argument of <tt>&lt;list expr as item&gt;</tt> or 
-     *       <tt>&lt;foreach item in expr&gt;</tt>, the loop body is not executed
+     *     <li>as argument of <code>&lt;list expr as item&gt;</code> or 
+     *       <code>&lt;foreach item in expr&gt;</code>, the loop body is not executed
      *       (as if it were a 0-length list)
      *     </li>
-     *     <li>as argument of <tt>&lt;if&gt;</tt> directive, or on other places where a
+     *     <li>as argument of <code>&lt;if&gt;</code> directive, or on other places where a
      *       boolean expression is expected, it's treated as false
      *     </li>
      *   </ul>
      * </li>
-     * <li>Non-boolean models are accepted in <tt>&lt;if&gt;</tt> directive,
+     * <li>Non-boolean models are accepted in <code>&lt;if&gt;</code> directive,
      *   or as operands of logical operators. "Empty" models (zero-length string,
      * empty sequence or hash) are evaluated as false, all others are evaluated as
      * true.</li>
      * <li>When boolean value is treated as a string (i.e. output in 
-     *   <tt>${...}</tt> directive, or concatenated with other string), true 
+     *   <code>${...}</code> directive, or concatenated with other string), true 
      * values are converted to string "true", false values are converted to 
-     * empty string. Except, if the value of the setting is <tt>2</tt>, it will be
-     * formatted according the <tt>boolean_format</tt> setting, just like in
+     * empty string. Except, if the value of the setting is {@code 2}, it will be
+     * formatted according the {@code boolean_format} setting, just like in
      * 2.3.20 and later.
      * </li>
-     * <li>Scalar models supplied to <tt>&lt;list&gt;</tt> and 
-     *   <tt>&lt;foreach&gt;</tt> are treated as a one-element list consisting
+     * <li>Scalar models supplied to <code>&lt;list&gt;</code> and 
+     *   <code>&lt;foreach&gt;</code> are treated as a one-element list consisting
      *   of the passed model.
      * </li>
-     * <li>Paths parameter of <tt>&lt;include&gt;</tt> will be interpreted as
+     * <li>Paths parameter of <code>&lt;include&gt;</code> will be interpreted as
      * absolute path.
      * </li>
      * </ul>
@@ -883,7 +883,7 @@
      *   </li>
      * </ul>
      *
-     * <p>Defaults to <tt>"number"</tt>.
+     * <p>Defaults to {@code "number"}.
      */
     public void setNumberFormat(String numberFormat) {
         NullArgumentException.check("numberFormat", numberFormat);
@@ -1606,7 +1606,7 @@
      * 
      * <p>Using {@code false} is needed for example when a Web page is composed
      * from several boxes (like portlets, GUI panels, etc.) that aren't inserted
-     * with <tt>#include</tt> (or with similar directives) into a master
+     * with {@code #include} (or with similar directives) into a master
      * FreeMarker template, rather they are all processed with a separate
      * {@link Template#process(Object, Writer)} call. In a such scenario the
      * automatic flushes would commit the HTTP response after each box, hence
@@ -2316,8 +2316,8 @@
      *             with {@code "allowed_classes:"} (or {@code "allowedClasses:"}) and/or
      *             {@code "trusted_templates:"} (or {@code "trustedTemplates:"}). Examples of valid values:
      *             
-     *             <table style="width: auto; border-collapse: collapse" border="1"
-     *                  summary="trusted_template value examples">
+     *             <table style="width: auto; border-collapse: collapse" border="1">
+ *                   <caption style="display: none">trusted_templates value examples</caption>
      *               <tr>
      *                 <th>Setting value
      *                 <th>Meaning
@@ -2394,7 +2394,8 @@
      *       <br>String value: {@code "enable_if_default"} or {@code "enableIfDefault"} for
      *       {@link Configuration#ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY},
      *       {@code "enable_if_supported"} or {@code "enableIfSupported"} for
-     *       {@link Configuration#ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY}
+     *       {@link Configuration#ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY},
+     *       {@code "force"} for {@link Configuration#FORCE_AUTO_ESCAPING_POLICY}, or
      *       {@code "disable"} for {@link Configuration#DISABLE_AUTO_ESCAPING_POLICY}.
      *       
      *   <li><p>{@code "default_encoding"}:
@@ -2440,7 +2441,8 @@
      *       maximum strong and soft sizes specified with the setting value. Examples
      *       of valid setting values:
      *       
-     *       <table style="width: auto; border-collapse: collapse" border="1" summary="cache_storage value examples">
+     *       <table style="width: auto; border-collapse: collapse" border="1">
+     *         <caption style="display: none">cache_storage value examples</caption>
      *         <tr><th>Setting value<th>max. strong size<th>max. soft size
      *         <tr><td>{@code "strong:50, soft:500"}<td>50<td>500
      *         <tr><td>{@code "strong:100, soft"}<td>100<td>{@code Integer.MAX_VALUE}
@@ -2450,7 +2452,7 @@
      *         <tr><td>{@code "soft"}<td>0<td>{@code Integer.MAX_VALUE}
      *       </table>
      *       
-     *       <p>The value is not case sensitive. The order of <tt>soft</tt> and <tt>strong</tt>
+     *       <p>The value is not case sensitive. The order of {@code soft} and {@code strong}
      *       entries is not significant.
      *       
      *   <li><p>{@code "template_update_delay"}:
@@ -2526,42 +2528,42 @@
      *       See {@link Configuration#setTabSize(int)}.
      * </ul>
      * 
-     * <p><a name="fm_obe"></a>Regarding <em>object builder expressions</em> (used by the setting values where it was
+     * <p id="fm_obe">Regarding <em>object builder expressions</em> (used by the setting values where it was
      * indicated):
      * <ul>
      *   <li><p>Before FreeMarker 2.3.21 it had to be a fully qualified class name, and nothing else.</li>
      *   <li><p>Since 2.3.21, the generic syntax is:
-     *       <tt><i>className</i>(<i>constrArg1</i>, <i>constrArg2</i>, ... <i>constrArgN</i>,
+     *       <code><i>className</i>(<i>constrArg1</i>, <i>constrArg2</i>, ... <i>constrArgN</i>,
      *       <i>propName1</i>=<i>propValue1</i>, <i>propName2</i>=<i>propValue2</i>, ...
-     *       <i>propNameN</i>=<i>propValueN</i>)</tt>,
+     *       <i>propNameN</i>=<i>propValueN</i>)</code>,
      *       where
-     *       <tt><i>className</i></tt> is the fully qualified class name of the instance to create (except if we have
-     *       builder class or <tt>INSTANCE</tt> field around, but see that later),
-     *       <tt><i>constrArg</i></tt>-s are the values of constructor arguments,
-     *       and <tt><i>propName</i>=<i>propValue</i></tt>-s set JavaBean properties (like <tt>x=1</tt> means
-     *       <tt>setX(1)</tt>) on the created instance. You can have any number of constructor arguments and property
+     *       <code><i>className</i></code> is the fully qualified class name of the instance to create (except if we have
+     *       builder class or {@code INSTANCE} field around, but see that later),
+     *       <code><i>constrArg</i></code>-s are the values of constructor arguments,
+     *       and <code><i>propName</i>=<i>propValue</i></code>-s set JavaBean properties (like {@code x=1} means
+     *       {@code setX(1)}) on the created instance. You can have any number of constructor arguments and property
      *       setters, including 0. Constructor arguments must precede any property setters.   
      *   </li>
      *   <li>
-     *     Example: <tt>com.example.MyObjectWrapper(1, 2, exposeFields=true, cacheSize=5000)</tt> is nearly
+     *     Example: {@code com.example.MyObjectWrapper(1, 2, exposeFields=true, cacheSize=5000)} is nearly
      *     equivalent with this Java code:
-     *     <tt>obj = new com.example.MyObjectWrapper(1, 2); obj.setExposeFields(true); obj.setCacheSize(5000);</tt>
+     *     {@code obj = new com.example.MyObjectWrapper(1, 2); obj.setExposeFields(true); obj.setCacheSize(5000);}
      *   </li>
      *   <li>
-     *      <p>If you have no constructor arguments and property setters, and the <tt><i>className</i></tt> class has
+     *      <p>If you have no constructor arguments and property setters, and the <code><i>className</i></code> class has
      *      a public static {@code INSTANCE} field, the value of that filed will be the value of the expression, and
      *      the constructor won't be called. Note that if you use the backward compatible
      *      syntax, where these's no parenthesis after the class name, then it will not look for {@code INSTANCE}.
      *   </li>
      *   <li>
-     *      <p>If there exists a class named <tt><i>className</i>Builder</tt>, then that class will be instantiated
+     *      <p>If there exists a class named <code><i>className</i>Builder</code>, then that class will be instantiated
      *      instead with the given constructor arguments, and the JavaBean properties of that builder instance will be
-     *      set. After that, the public <tt>build()</tt> method of the instance will be called, whose return value
-     *      will be the value of the whole expression. (The builder class and the <tt>build()</tt> method is simply
+     *      set. After that, the public {@code build()} method of the instance will be called, whose return value
+     *      will be the value of the whole expression. (The builder class and the {@code build()} method is simply
      *      found by name, there's no special interface to implement.) Note that if you use the backward compatible
      *      syntax, where these's no parenthesis after the class name, then it will not look for builder class. Note
-     *      that if you have a builder class, you don't actually need a <tt><i>className</i></tt> class (since 2.3.24);
-     *      after all, <tt><i>className</i>Builder.build()</tt> can return any kind of object. 
+     *      that if you have a builder class, you don't actually need a <code><i>className</i></code> class (since 2.3.24);
+     *      after all, <code><i>className</i>Builder.build()</code> can return any kind of object. 
      *   </li>
      *   <li>
      *      <p>Currently, the values of arguments and properties can only be one of these:
@@ -2575,8 +2577,8 @@
      *        {@code BigDecimal} and "bi" for {@code BigInteger}.</li>
      *        <li>A boolean literal: {@code true} or {@code false}
      *        <li>The null literal: {@code null}
-     *        <li>A string literal with FTL syntax, except that  it can't contain <tt>${...}</tt>-s and
-     *            <tt>#{...}</tt>-s. Examples: {@code "Line 1\nLine 2"} or {@code r"C:\temp"}.
+     *        <li>A string literal with FTL syntax, except that  it can't contain <code>${...}</code>-s and
+     *            <code>#{...}</code>-s. Examples: {@code "Line 1\nLine 2"} or {@code r"C:\temp"}.
      *        <li>A list literal (since 2.3.24) with FTL-like syntax, for example {@code [ 'foo', 2, true ]}.
      *            If the parameter is expected to be array, the list will be automatically converted to array.
      *            The list items can be any kind of expression, like even object builder expressions.
@@ -2909,7 +2911,7 @@
     
     /**
      * Returns the textual representation of a setting.
-     * @param key the setting key. Can be any of standard <tt>XXX_KEY</tt>
+     * @param key the setting key. Can be any of standard {@code XXX_KEY}
      * constants, or a custom key.
      *
      * @deprecated It's not possible in general to convert setting values to string,
@@ -3149,7 +3151,7 @@
      * @param name the name of the custom attribute
      *
      * @return the value of the custom attribute. Note that if the custom attribute
-     * was created with <tt>&lt;#ftl&nbsp;attributes={...}&gt;</tt>, then this value is already
+     * was created with <code>&lt;#ftl&nbsp;attributes={...}&gt;</code>, then this value is already
      * unwrapped (i.e. it's a <code>String</code>, or a <code>List</code>, or a
      * <code>Map</code>, ...etc., not a FreeMarker specific class).
      */
diff --git a/src/main/java/freemarker/core/CustomAttribute.java b/src/main/java/freemarker/core/CustomAttribute.java
index df4e933..24b6cd4 100644
--- a/src/main/java/freemarker/core/CustomAttribute.java
+++ b/src/main/java/freemarker/core/CustomAttribute.java
@@ -66,7 +66,7 @@
     
     /**
      * Creates a new custom attribute with the specified scope
-     * @param scope one of <tt>SCOPE_</tt> constants. 
+     * @param scope one of {@code SCOPE_} constants. 
      */
     public CustomAttribute(int scope) {
         if (scope != SCOPE_ENVIRONMENT && 
diff --git a/src/main/java/freemarker/core/DirectiveCallPlace.java b/src/main/java/freemarker/core/DirectiveCallPlace.java
index ad6400f..5c3d6f3 100644
--- a/src/main/java/freemarker/core/DirectiveCallPlace.java
+++ b/src/main/java/freemarker/core/DirectiveCallPlace.java
@@ -126,7 +126,7 @@
     /**
      * Tells if the nested content (the body) can be safely cached, as it only depends on the template content (not on
      * variable values and such) and has no side-effects (other than writing to the output). Examples of cases that give
-     * {@code false}: {@code <@foo>Name: } <tt>${name}</tt>{@code</@foo>},
+     * {@code false}: {@code <@foo>Name: } <code>${name}</code>{@code</@foo>},
      * {@code <@foo>Name: <#if showIt>Joe</#if></@foo>}. Examples of cases that give {@code true}:
      * {@code <@foo>Name: Joe</@foo>}, {@code <@foo />}. Note that we get {@code true} for no nested content, because
      * that's equivalent with 0-length nested content in FTL.
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index 7eb68d8..6cbfe28 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -82,17 +82,17 @@
 
 /**
  * Object that represents the runtime environment during template processing. For every invocation of a
- * <tt>Template.process()</tt> method, a new instance of this object is created, and then discarded when
- * <tt>process()</tt> returns. This object stores the set of temporary variables created by the template, the value of
+ * {@code Template.process()} method, a new instance of this object is created, and then discarded when
+ * {@code process()} returns. This object stores the set of temporary variables created by the template, the value of
  * settings set by the template, the reference to the data model root, etc. Everything that is needed to fulfill the
  * template processing job.
  *
  * <p>
- * Data models that need to access the <tt>Environment</tt> object that represents the template processing on the
+ * Data models that need to access the {@code Environment} object that represents the template processing on the
  * current thread can use the {@link #getCurrentEnvironment()} method.
  *
  * <p>
- * If you need to modify or read this object before or after the <tt>process</tt> call, use
+ * If you need to modify or read this object before or after the {@code process} call, use
  * {@link Template#createProcessingEnvironment(Object rootMap, Writer out, ObjectWrapper wrapper)}
  */
 public final class Environment extends Configurable {
@@ -204,6 +204,14 @@
         threadEnv.set(env);
     }
 
+    /**
+     * Creates an environment with the given main (top-level) template that it intends to {@linkplain #process()} later;
+     * typically, it's better to use {@link Template#createProcessingEnvironment(Object, Writer)} instead of this.
+     *
+     * @param template Not {@code null}
+     * @param rootDataModel Not {@code null}
+     * @param out Not {@code null}
+     */
     public Environment(Template template, final TemplateHashModel rootDataModel, Writer out) {
         super(template);
         configuration = template.getConfiguration();
@@ -583,9 +591,9 @@
     }
 
     /**
-     * Tells if we are inside an <tt>#attempt</tt> block (but before <tt>#recover</tt>). This can be useful for
+     * Tells if we are inside an {@code #attempt} block (but before {@code #recover}). This can be useful for
      * {@link TemplateExceptionHandler}-s, as then they may don't want to print the error to the output, as
-     * <tt>#attempt</tt> will roll it back anyway.
+     * {@code #attempt} will roll it back anyway.
      * 
      * @since 2.3.20
      */
@@ -1333,7 +1341,7 @@
     }
 
     /*
-     * Note that altough it's not allowed to set this setting with the <tt>setting</tt> directive, it still must be
+     * Note that altough it's not allowed to set this setting with the {@code setting} directive, it still must be
      * allowed to set it from Java code while the template executes, since some frameworks allow templates to actually
      * change the output encoding on-the-fly.
      */
@@ -1667,9 +1675,9 @@
     }
 
     /**
-     * Returns the {@link NumberFormat} used for the <tt>c</tt> built-in, except, if
+     * Returns the {@link NumberFormat} used for the {@code c} built-in, except, if
      * {@linkplain Configuration#setIncompatibleImprovements(Version) Incompatible Improvements} is less than 2.3.31,
-     * this will wrongly give the format that the <tt>c</tt> built-in used before Incompatible Improvements 2.3.21.
+     * this will wrongly give the format that the {@code c} built-in used before Incompatible Improvements 2.3.21.
      * See more at {@link Configuration#Configuration(Version)}.
      *
      * @deprecated Use {@link #getCTemplateNumberFormat()} instead. This method can't return the format used when
@@ -2918,7 +2926,7 @@
     }
 
     /**
-     * sets TemplateNodeModel as the current visitor node. <tt>.current_node</tt>
+     * sets TemplateNodeModel as the current visitor node. {@code .current_node}
      */
     public void setCurrentVisitorNode(TemplateNodeModel node) {
         currentVisitorNode = node;
diff --git a/src/main/java/freemarker/core/EvalUtil.java b/src/main/java/freemarker/core/EvalUtil.java
index 1b469c9..7bb9547 100644
--- a/src/main/java/freemarker/core/EvalUtil.java
+++ b/src/main/java/freemarker/core/EvalUtil.java
@@ -20,6 +20,7 @@
 package freemarker.core;
 
 import java.lang.reflect.InvocationTargetException;
+import java.text.Normalizer;
 import java.util.Date;
 
 import freemarker.ext.beans.BeanModel;
@@ -277,8 +278,12 @@
             }
             String leftString = EvalUtil.modelToString((TemplateScalarModel) leftValue, leftExp, env);
             String rightString = EvalUtil.modelToString((TemplateScalarModel) rightValue, rightExp, env);
-            // FIXME NBC: Don't use the Collator here. That's locale-specific, but ==/!= should not be.
-            cmpResult = env.getCollator().compare(leftString, rightString);
+            if (env.getConfiguration().getIncompatibleImprovements().intValue() <= _VersionInts.V_2_3_32) {
+                cmpResult = env.getCollator().compare(leftString, rightString);
+            } else {
+                cmpResult = Normalizer.normalize(leftString, Normalizer.Form.NFKC)
+                         .compareTo(Normalizer.normalize(rightString, Normalizer.Form.NFKC));
+            }
         } else if (leftValue instanceof TemplateBooleanModel && rightValue instanceof TemplateBooleanModel) {
             if (operator != CMP_OP_EQUALS && operator != CMP_OP_NOT_EQUALS) {
                 throw new _MiscTemplateException(defaultBlamed, env,
diff --git a/src/main/java/freemarker/core/Include.java b/src/main/java/freemarker/core/Include.java
index f9acb2b..f95ae6c 100644
--- a/src/main/java/freemarker/core/Include.java
+++ b/src/main/java/freemarker/core/Include.java
@@ -41,7 +41,7 @@
     private final Boolean ignoreMissingExpPrecalcedValue;
 
     /**
-     * @param template the template that this <tt>#include</tt> is a part of.
+     * @param template the template that this {@code #include} is a part of.
      * @param includedTemplatePathExp the path of the template to be included.
      * @param encodingExp the encoding to be used or null, if it's the default.
      * @param parseExp whether the template should be parsed (or is raw text)
diff --git a/src/main/java/freemarker/core/Interpret.java b/src/main/java/freemarker/core/Interpret.java
index 4e6694e..3b7aab9 100644
--- a/src/main/java/freemarker/core/Interpret.java
+++ b/src/main/java/freemarker/core/Interpret.java
@@ -40,7 +40,7 @@
  * transform model that evaluates the template in place.
  * The template inherits the configuration and environment of the executing
  * template. By default, its name will be equal to 
- * <tt>executingTemplate.getName() + "$anonymous_interpreted"</tt>. You can
+ * {@code executingTemplate.getName() + "$anonymous_interpreted"}. You can
  * specify another parameter to the method call in which case the
  * template name suffix is the specified id instead of "anonymous_interpreted".
  */
@@ -58,8 +58,8 @@
      * template a name.
      * 
      * @return a {@link TemplateTransformModel} that when executed inside
-     * a <tt>&lt;transform></tt> block will process the generated template
-     * just as if it had been <tt>&lt;transform></tt>-ed at that point.
+     * a <code>&lt;transform></code> block will process the generated template
+     * just as if it had been <code>&lt;transform></code>-ed at that point.
      */
     @Override
     protected TemplateModel calculateResult(Environment env) throws TemplateException {
diff --git a/src/main/java/freemarker/core/LibraryLoad.java b/src/main/java/freemarker/core/LibraryLoad.java
index 64f659c..00c590a 100644
--- a/src/main/java/freemarker/core/LibraryLoad.java
+++ b/src/main/java/freemarker/core/LibraryLoad.java
@@ -38,7 +38,7 @@
     private String targetNsVarName;
 
     /**
-     * @param template the template that this <tt>Include</tt> is a part of.
+     * @param template the template that this {@code Include} is a part of.
      * @param templateName the name of the template to be included.
      * @param targetNsVarName the name of the  variable to assign this library's namespace to
      */
diff --git a/src/main/java/freemarker/core/MethodCall.java b/src/main/java/freemarker/core/MethodCall.java
index ab441aa..180438c 100644
--- a/src/main/java/freemarker/core/MethodCall.java
+++ b/src/main/java/freemarker/core/MethodCall.java
@@ -34,7 +34,7 @@
 
 /**
  * A unary operator that calls a TemplateMethodModel.  It associates with the
- * <tt>Identifier</tt> or <tt>Dot</tt> to its left.
+ * {@code Identifier} or {@code Dot} to its left.
  */
 final class MethodCall extends Expression {
 
diff --git a/src/main/java/freemarker/core/MixedContent.java b/src/main/java/freemarker/core/MixedContent.java
index f5b6747..30529d2 100644
--- a/src/main/java/freemarker/core/MixedContent.java
+++ b/src/main/java/freemarker/core/MixedContent.java
@@ -24,7 +24,7 @@
 import freemarker.template.TemplateException;
 
 /**
- * Encapsulates an array of <tt>TemplateElement</tt> objects. 
+ * Encapsulates an array of {@code TemplateElement} objects. 
  */
 final class MixedContent extends TemplateElement {
 
@@ -54,7 +54,7 @@
     }
 
     /**
-     * Processes the contents of the internal <tt>TemplateElement</tt> list,
+     * Processes the contents of the internal {@code TemplateElement} list,
      * and outputs the resulting text.
      */
     @Override
diff --git a/src/main/java/freemarker/core/NewBI.java b/src/main/java/freemarker/core/NewBI.java
index fa76168..45e478f 100644
--- a/src/main/java/freemarker/core/NewBI.java
+++ b/src/main/java/freemarker/core/NewBI.java
@@ -32,7 +32,7 @@
 
 /**
  * A built-in that allows us to instantiate an instance of a java class.
- * Usage is something like: <tt>&lt;#assign foobar = "foo.bar.MyClass"?new()></tt>;
+ * Usage is something like: <code>&lt;#assign foobar = "foo.bar.MyClass"?new()></code>;
  */
 class NewBI extends BuiltIn {
     
diff --git a/src/main/java/freemarker/core/NumberLiteral.java b/src/main/java/freemarker/core/NumberLiteral.java
index 2be737f..c380ac6 100644
--- a/src/main/java/freemarker/core/NumberLiteral.java
+++ b/src/main/java/freemarker/core/NumberLiteral.java
@@ -25,7 +25,7 @@
 import freemarker.template.TemplateNumberModel;
 
 /**
- * A simple implementation of the <tt>TemplateNumberModel</tt>
+ * A simple implementation of the {@code TemplateNumberModel}
  * interface. Note that this class is immutable.
  */
 final class NumberLiteral extends Expression implements TemplateNumberModel {
diff --git a/src/main/java/freemarker/core/StopException.java b/src/main/java/freemarker/core/StopException.java
index 7c3e508..dbabb27 100644
--- a/src/main/java/freemarker/core/StopException.java
+++ b/src/main/java/freemarker/core/StopException.java
@@ -25,7 +25,7 @@
 import freemarker.template.TemplateException;
 
 /**
- * This exception is thrown when a <tt>#stop</tt> directive is encountered. 
+ * This exception is thrown when a {@code #stop} directive is encountered. 
  */
 public class StopException extends TemplateException {
     
diff --git a/src/main/java/freemarker/core/StringLiteral.java b/src/main/java/freemarker/core/StringLiteral.java
index 87507ec..d11f361 100644
--- a/src/main/java/freemarker/core/StringLiteral.java
+++ b/src/main/java/freemarker/core/StringLiteral.java
@@ -139,7 +139,7 @@
     }
     
     /**
-     * Tells if this is something like <tt>"${foo}"</tt>, which is usually a user mistake.
+     * Tells if this is something like <code>"${foo}"</code>, which is usually a user mistake.
      */
     boolean isSingleInterpolationLiteral() {
         return dynamicValue != null && dynamicValue.size() == 1
diff --git a/src/main/java/freemarker/core/TemplateObject.java b/src/main/java/freemarker/core/TemplateObject.java
index 40f6015..e535d08 100644
--- a/src/main/java/freemarker/core/TemplateObject.java
+++ b/src/main/java/freemarker/core/TemplateObject.java
@@ -199,7 +199,7 @@
     
     /**
      * A very sort single-line string that describes what kind of AST node this is, without describing any 
-     * embedded expression or child element. Examples: {@code "#if"}, {@code "+"}, <tt>"${...}</tt>. These values should
+     * embedded expression or child element. Examples: {@code "#if"}, {@code "+"}, <code>"${...}</code>. These values should
      * be suitable as tree node labels in a tree view. Yet, they should be consistent and complete enough so that an AST
      * that is equivalent with the original could be reconstructed from the tree view. Thus, for literal values that are
      * leaf nodes the symbols should be the canonical form of value.
diff --git a/src/main/java/freemarker/core/_Java8.java b/src/main/java/freemarker/core/_Java16.java
similarity index 87%
rename from src/main/java/freemarker/core/_Java8.java
rename to src/main/java/freemarker/core/_Java16.java
index e1a3954..84af6cd 100644
--- a/src/main/java/freemarker/core/_Java8.java
+++ b/src/main/java/freemarker/core/_Java16.java
@@ -19,16 +19,16 @@
 package freemarker.core;
 
 import java.lang.reflect.Method;
+import java.util.Set;
 
 /**
  * Used internally only, might change without notice!
  * Used for accessing functionality that's only present in Java 8 or later.
  */
-public interface _Java8 {
+public interface _Java16 {
 
-    /**
-     * Returns if it's a Java 8 "default method".
-     */
-    boolean isDefaultMethod(Method method);
-    
+    boolean isRecord(Class<?> cl);
+
+    Set<Method> getComponentAccessors(Class<?> recordClass);
+
 }
diff --git a/src/main/java/freemarker/core/_Java16Impl.java b/src/main/java/freemarker/core/_Java16Impl.java
new file mode 100644
index 0000000..ba1ba87
--- /dev/null
+++ b/src/main/java/freemarker/core/_Java16Impl.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package freemarker.core;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.RecordComponent;
+import java.util.IdentityHashMap;
+import java.util.Set;
+
+/**
+ * Used internally only, might change without notice!
+ * Used for accessing functionality that's only present in Java 16 or later.
+ */
+// Compile this against Java 16
+@SuppressWarnings("Since15") // For IntelliJ inspection
+public class _Java16Impl implements _Java16 {
+
+    public static final _Java16 INSTANCE = new _Java16Impl();
+
+    private _Java16Impl() {
+        // Not meant to be instantiated
+    }
+
+    @Override
+    public boolean isRecord(Class<?> cl) {
+        return cl.isRecord();
+    }
+
+    @Override
+    public Set<Method> getComponentAccessors(Class<?> recordType)  {
+        RecordComponent[] recordComponents = recordType.getRecordComponents();
+        if (recordComponents == null) {
+            throw new IllegalArgumentException("Argument must be a record type");
+        }
+        IdentityHashMap<Method, Void> methods = new IdentityHashMap<>(recordComponents.length);
+        for (RecordComponent recordComponent : recordComponents) {
+            methods.put(recordComponent.getAccessor(), null);
+        }
+        return methods.keySet();
+    }
+}
diff --git a/src/main/java/freemarker/core/_Java8Impl.java b/src/main/java/freemarker/core/_Java8Impl.java
deleted file mode 100644
index e4d14c0..0000000
--- a/src/main/java/freemarker/core/_Java8Impl.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package freemarker.core;
-
-import java.lang.reflect.Method;
-
-/**
- * Used internally only, might change without notice!
- * Used for accessing functionality that's only present in Java 8 or later.
- */
-// Compile this against Java 8
-@SuppressWarnings("Since15") // For IntelliJ inspection
-public class _Java8Impl implements _Java8 {
-    
-    public static final _Java8 INSTANCE = new _Java8Impl();
-
-    private _Java8Impl() {
-        // Not meant to be instantiated
-    }    
-
-    @Override
-    public boolean isDefaultMethod(Method method) {
-        return method.isDefault();
-    }
-
-}
diff --git a/src/main/java/freemarker/core/_JavaVersions.java b/src/main/java/freemarker/core/_JavaVersions.java
index 5670c36..9530e59 100644
--- a/src/main/java/freemarker/core/_JavaVersions.java
+++ b/src/main/java/freemarker/core/_JavaVersions.java
@@ -31,49 +31,50 @@
         // Not meant to be instantiated
     }
 
-    private static final boolean IS_AT_LEAST_8;
+    private static final boolean IS_AT_LEAST_16 = isAtLeast(16, "java.net.UnixDomainSocketAddress");
+
+    /**
+     * {@code null} if Java 8 is not available, otherwise the object through with the Java 8 operations are available.
+     */
+    static public final _Java16 JAVA_16;
     static {
+        _Java16 java16;
+        if (IS_AT_LEAST_16) {
+            try {
+                java16 = (_Java16) Class.forName("freemarker.core._Java16Impl").getField("INSTANCE").get(null);
+            } catch (Exception e) {
+                try {
+                    Logger.getLogger("freemarker.runtime").error("Failed to access Java 16 functionality", e);
+                } catch (Exception e2) {
+                    // Suppressed
+                }
+                java16 = null;
+            }
+        } else {
+            java16 = null;
+        }
+        JAVA_16 = java16;
+    }
+
+    private static boolean isAtLeast(int minimumMinorVersion, String proofClassPresence) {
         boolean result = false;
         String vStr = SecurityUtilities.getSystemProperty("java.version", null);
         if (vStr != null) {
             try {
                 Version v = new Version(vStr);
-                result = v.getMajor() == 1 && v.getMinor() >= 8 || v.getMajor() > 1;
+                result = v.getMajor() == 1 && v.getMinor() >= minimumMinorVersion || v.getMajor() > 1;
             } catch (Exception e) {
                 // Ignore
             }
         } else {
             try {
-                Class.forName("java.time.Instant");
+                Class.forName(proofClassPresence);
                 result = true;
             } catch (Exception e) {
                 // Ignore
             }
         }
-        IS_AT_LEAST_8 = result;
+        return result;
     }
-    
-    /**
-     * {@code null} if Java 8 is not available, otherwise the object through with the Java 8 operations are available.
-     */
-    static public final _Java8 JAVA_8;
-    static {
-        _Java8 java8;
-        if (IS_AT_LEAST_8) {
-            try {
-                java8 = (_Java8) Class.forName("freemarker.core._Java8Impl").getField("INSTANCE").get(null);
-            } catch (Exception e) {
-                try {
-                    Logger.getLogger("freemarker.runtime").error("Failed to access Java 8 functionality", e);
-                } catch (Exception e2) {
-                    // Suppressed
-                }
-                java8 = null;
-            }
-        } else {
-            java8 = null;
-        }
-        JAVA_8 = java8;
-    }
-    
+
 }
diff --git a/src/main/java/freemarker/debug/DebugModel.java b/src/main/java/freemarker/debug/DebugModel.java
index e906fdf..c86b256 100644
--- a/src/main/java/freemarker/debug/DebugModel.java
+++ b/src/main/java/freemarker/debug/DebugModel.java
@@ -33,7 +33,7 @@
  * almost all of FreeMarker template models with identical method signatures.
  * For purposes of optimizing network traffic there are bulk retrieval methods
  * for sequences and hashes, as well as a {@link #getModelTypes()} method that
- * returns a bit mask of various <tt>TYPE_xxx</tt> constants flagging which
+ * returns a bit mask of various {@code TYPE_xxx} constants flagging which
  * template models are implemented by the mirrored object.
  */
 public interface DebugModel extends Remote {
diff --git a/src/main/java/freemarker/debug/DebuggerClient.java b/src/main/java/freemarker/debug/DebuggerClient.java
index 86c5954..eecf17f 100644
--- a/src/main/java/freemarker/debug/DebuggerClient.java
+++ b/src/main/java/freemarker/debug/DebuggerClient.java
@@ -44,8 +44,8 @@
     /**
      * Connects to the FreeMarker debugger service running on a specific host
      * and port. The Java VM to which the connection is made must have defined
-     * the system property <tt>freemarker.debug.password</tt> in order to enable
-     * the debugger service. Additionally, the <tt>freemarker.debug.port</tt>
+     * the system property {@code freemarker.debug.password} in order to enable
+     * the debugger service. Additionally, the {@code freemarker.debug.port}
      * system property can be set to specify the port where the debugger service
      * is listening. When not specified, it defaults to 
      * {@link Debugger#DEFAULT_PORT}.
diff --git a/src/main/java/freemarker/ext/ant/FreemarkerXmlTask.java b/src/main/java/freemarker/ext/ant/FreemarkerXmlTask.java
index 46e9955..0e60322 100644
--- a/src/main/java/freemarker/ext/ant/FreemarkerXmlTask.java
+++ b/src/main/java/freemarker/ext/ant/FreemarkerXmlTask.java
@@ -62,114 +62,115 @@
  * destination directory.</p>
  * <p>It makes the following variables available to the template in the data model:</p>
  * <ul>
- * <li><tt>document</tt>: <em>Deprecated!</em> The DOM tree of the currently processed XML file wrapped
+ * <li>{@code document}: <em>Deprecated!</em> The DOM tree of the currently processed XML file wrapped
       with the legacy {@link freemarker.ext.xml.NodeListModel}.
-      For new projects you should use the <tt>.node</tt> instead, which initially
+      For new projects you should use the {@code .node} instead, which initially
       contains the DOM Document wrapped with {@link freemarker.ext.dom.NodeModel}.</li>
- * <li><tt>properties</tt>: a {@link freemarker.template.SimpleHash} containing
+ * <li>{@code properties}: a {@link freemarker.template.SimpleHash} containing
  * properties of the project that executes the task</li>
- * <li><tt>userProperties</tt>: a {@link freemarker.template.SimpleHash} containing
+ * <li>{@code userProperties}: a {@link freemarker.template.SimpleHash} containing
  * user properties of the project that executes the task</li>
- * <li><tt>project</tt>: the DOM tree of the XML file specified by the
- * <tt>projectfile</tt>. It will not be available if you didn't specify the
- * <tt>projectfile</tt> attribute.</li>
+ * <li>{@code project}: the DOM tree of the XML file specified by the
+ * {@code projectfile}. It will not be available if you didn't specify the
+ * {@code projectfile} attribute.</li>
  * <li>further custom models can be instantiated and made available to the 
- * templates using the <tt>models</tt> attribute.</li>
+ * templates using the {@code models} attribute.</li>
  * </ul>
  * <p>It supports the following attributes:</p>
- * <table style="width: auto; border-collapse: collapse" border="1" summary="FreeMarker XML ant task attributes">
+ * <table style="width: auto; border-collapse: collapse" border="1">
+ *   <caption style="display: none">FreeMarker XML ant task attributes</caption>
  *   <tr>
- *     <th valign="top" align="left">Attribute</th>
- *     <th valign="top" align="left">Description</th>
+ *     <th valign="top">Attribute</th>
+ *     <th valign="top">Description</th>
  *     <th valign="top">Required</th>
  *   </tr>
  *   <tr>
  *     <td valign="top">basedir</td>
  *     <td valign="top">location of the XML files. Defaults to the project's
  *       basedir.</td>
- *     <td align="center" valign="top">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">destdir</td>
  *     <td valign="top">location to store the generated files.</td>
- *     <td align="center" valign="top">Yes</td>
+ *     <td valign="top">Yes</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">includes</td>
  *     <td valign="top">comma-separated list of patterns of files that must be
  *       included; all files are included when omitted.</td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">includesfile</td>
  *     <td valign="top">the name of a file that contains
  *       include patterns.</td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">excludes</td>
  *     <td valign="top">comma-separated list of patterns of files that must be
  *       excluded; no files (except default excludes) are excluded when omitted.</td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">excludesfile</td>
  *     <td valign="top">the name of a file that contains
  *       exclude patterns.</td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">defaultexcludes</td>
  *     <td valign="top">indicates whether default excludes should be used
  *       (<code>yes</code> | <code>no</code>); default excludes are used when omitted.</td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">extension</td>
  *     <td valign="top">extension of generated files. Defaults to .html.</td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">template</td>
  *     <td valign="top">name of the FreeMarker template file that will be
  *       applied by default to XML files</td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">templateDir</td>
  *     <td valign="top">location of the FreeMarker template(s) to be used, defaults
  *                       to the project's baseDir</td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">projectfile</td>
  *     <td valign="top">path to the project file. The poject file must be an XML file.
  *       If omitted, it will not be available to templates </td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">incremental</td>
  *     <td valign="top">indicates whether all files should be regenerated (no), or
  *       only those that are older than the XML file, the template file, or the
  *       project file (yes). Defaults to yes. </td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">encoding</td>
  *     <td valign="top">The encoding of the output files. Defaults to platform
  *       default encoding.</td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">templateEncoding</td>
  *     <td valign="top">The encoding of the template files. Defaults to platform
  *       default encoding.</td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">validation</td>
  *     <td valign="top">Whether to validate the XML input. Defaults to off.</td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">models</td>
@@ -178,16 +179,17 @@
  *      available to templates. If name is omitted, the unqualified class name
  *      is used as the name. Every class that is specified must implement the
  *      TemplateModel interface and have a no-args constructor.</td>
- *     <td valign="top" align="center">No</td>
+ *     <td valign="top">No</td>
  *   </tr>
  * </table>
  * 
  * <p>It supports the following nesed elements:</p>
  * 
- * <table style="width: auto; border-collapse: collapse" border="1" summary="FreeMarker XML ant task nested elements">
+ * <table style="width: auto; border-collapse: collapse" border="1">
+ *   <caption style="display: none">FreeMarker XML ant task nested elements</caption>
  *   <tr>
- *     <th valign="top" align="left">Element</th>
- *     <th valign="top" align="left">Description</th>
+ *     <th valign="top">Element</th>
+ *     <th valign="top">Description</th>
  *     <th valign="top">Required</th>
  *   </tr>
  *   <tr>
@@ -196,35 +198,35 @@
  *      This element executes Jython script before the processing of each XML
  *      files, that you can use to modify the data model.
  *      You either enter the Jython script directly nested into this
- *      element, or specify a Jython script file with the <tt>file</tt>
+ *      element, or specify a Jython script file with the {@code file}
  *      attribute.
  *      The following variables are added to the Jython runtime's local
  *      namespace before the script is invoked:
  *      <ul>
- *        <li><tt>model</tt>: The data model as <code>java.util.HashMap</code>.
+ *        <li>{@code model}: The data model as <code>java.util.HashMap</code>.
  *           You can read and modify the data model with this variable.
- *        <li><tt>doc</tt>: The XML document as <code>org.w3c.dom.Document</code>.
- *        <li><tt>project</tt>: The project document (if used) as
+ *        <li>{@code doc}: The XML document as <code>org.w3c.dom.Document</code>.
+ *        <li>{@code project}: The project document (if used) as
  *           <code>org.w3c.dom.Document</code>.
  *      </ul>
  *      <i>If this element is used, Jython classes (tried with Jython 2.1)
  *      must be available.</i>
  *    </td>
- *    <td valign="top" align="center">No</td>
+ *    <td valign="top">No</td>
  *   </tr>
  *   <tr>
  *     <td valign="top">prepareEnvironment</td>
  *     <td valign="top">This element executes Jython script before the processing
  *      of each XML files, that you can use to modify the freemarker environment
  *      ({@link freemarker.core.Environment}). The script is executed after the
- *      <tt>prepareModel</tt> element. The accessible Jython variables are the
- *      same as with the <tt>prepareModel</tt> element, except that there is no
- *      <tt>model</tt> variable, but there is <tt>env</tt> variable, which is
+ *      {@code prepareModel} element. The accessible Jython variables are the
+ *      same as with the {@code prepareModel} element, except that there is no
+ *      {@code model} variable, but there is {@code env} variable, which is
  *      the FreeMarker environment ({@link freemarker.core.Environment}).
  *      <i>If this element is used, Jython classes (tried with Jython 2.1)
  *      must be available.</i>
  *    </td>
- *    <td valign="top" align="center">No</td>
+ *    <td valign="top">No</td>
  *   </tr>
  * </table>
  * @deprecated <a href="http://fmpp.sourceforge.net">FMPP</a> is a more complete solution.
@@ -299,7 +301,7 @@
     }
 
     /**
-     * Set the base directory. Defaults to <tt>.</tt>
+     * Set the base directory. Defaults to {@code .}
      */
     public void setBasedir(File dir) {
         baseDir = dir;
@@ -315,7 +317,7 @@
     }
 
     /**
-     * Set the output file extension. <tt>.html</tt> by default.
+     * Set the output file extension. {@code .html} by default.
      */
     public void setExtension(String extension) {
         this.extension = extension;
diff --git a/src/main/java/freemarker/ext/beans/ArgumentTypes.java b/src/main/java/freemarker/ext/beans/ArgumentTypes.java
index 2bba006..6b28d9b 100644
--- a/src/main/java/freemarker/ext/beans/ArgumentTypes.java
+++ b/src/main/java/freemarker/ext/beans/ArgumentTypes.java
@@ -474,7 +474,7 @@
      * parameter types represented by this ArgumentTypes object, also tells
      * how difficult that conversion is.
      * 
-     * @return One of the <tt>CONVERSION_DIFFICULTY_...</tt> constants.
+     * @return One of the {@code CONVERSION_DIFFICULTY_...} constants.
      */
     private int isApplicable(ReflectionCallableMemberDescriptor memberDesc, boolean varArg) {
         final Class<?>[] paramTypes = memberDesc.getParamTypes(); 
@@ -527,7 +527,7 @@
      * parameter type should be convertible; possibly a primitive type
      * @param actual the argument type; not a primitive type, maybe {@link Null}.
      * 
-     * @return One of the <tt>CONVERSION_DIFFICULTY_...</tt> constants.
+     * @return One of the {@code CONVERSION_DIFFICULTY_...} constants.
      */
     private int isMethodInvocationConvertible(final Class<?> formal, final Class<?> actual) {
         // Check for identity or widening reference conversion
diff --git a/src/main/java/freemarker/ext/beans/ArrayModel.java b/src/main/java/freemarker/ext/beans/ArrayModel.java
index 22ea149..2ba5665 100644
--- a/src/main/java/freemarker/ext/beans/ArrayModel.java
+++ b/src/main/java/freemarker/ext/beans/ArrayModel.java
@@ -31,7 +31,7 @@
 
 /**
  * <p>A class that will wrap an arbitrary array into {@link TemplateCollectionModel}
- * and {@link TemplateSequenceModel} interfaces. It supports element retrieval through the <tt>array[index]</tt>
+ * and {@link TemplateSequenceModel} interfaces. It supports element retrieval through the {@code array[index]}
  * syntax and can be iterated as a list.
  */
 public class ArrayModel
diff --git a/src/main/java/freemarker/ext/beans/BeanModel.java b/src/main/java/freemarker/ext/beans/BeanModel.java
index e7d0baa..eacf7b2 100644
--- a/src/main/java/freemarker/ext/beans/BeanModel.java
+++ b/src/main/java/freemarker/ext/beans/BeanModel.java
@@ -54,8 +54,8 @@
  * A class that will wrap an arbitrary object into {@link freemarker.template.TemplateHashModel}
  * interface allowing calls to arbitrary property getters and invocation of
  * accessible methods on the object from a template using the
- * <tt>object.foo</tt> to access properties and <tt>object.bar(arg1, arg2)</tt> to
- * invoke methods on it. You can also use the <tt>object.foo[index]</tt> syntax to
+ * {@code object.foo} to access properties and {@code object.bar(arg1, arg2)} to
+ * invoke methods on it. You can also use the {@code object.foo[index]} syntax to
  * access indexed properties. It uses Beans {@link java.beans.Introspector}
  * to dynamically discover the properties and methods. 
  */
@@ -131,12 +131,12 @@
      * they reload a web application) and flushes the cache. If no method or
      * property matching the key is found, the framework will try to invoke
      * methods with signature
-     * <tt>non-void-return-type get(java.lang.String)</tt>,
-     * then <tt>non-void-return-type get(java.lang.Object)</tt>, or 
+     * {@code non-void-return-type get(java.lang.String)},
+     * then {@code non-void-return-type get(java.lang.Object)}, or 
      * alternatively (if the wrapped object is a resource bundle) 
-     * <tt>Object getObject(java.lang.String)</tt>.
+     * {@code Object getObject(java.lang.String)}.
      * @throws TemplateModelException if there was no property nor method nor
-     * a generic <tt>get</tt> method to invoke.
+     * a generic {@code get} method to invoke.
      */
     @Override
     public TemplateModel get(String key)
@@ -364,7 +364,7 @@
     /**
      * Helper method to support TemplateHashModelEx. Returns the Set of
      * Strings which are available via the TemplateHashModel
-     * interface. Subclasses that override <tt>invokeGenericGet</tt> to
+     * interface. Subclasses that override {@code invokeGenericGet} to
      * provide additional hash keys should also override this method.
      */
     protected Set/*<Object>*/ keySet() {
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index 47eb5c7..be2455e 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -98,8 +98,8 @@
     
     /**
      * At this level of exposure, all methods and properties of the
-     * wrapped objects are exposed to the template, and the {@link MemberAccessPolicy}
-     * will be ignored.
+     * wrapped objects are exposed to the template, and even the {@link MemberAccessPolicy}
+     * is ignored.
      */
     public static final int EXPOSE_ALL = 0;
     
@@ -113,6 +113,9 @@
      * java.lang.Thread and java.lang.ThreadGroup methods that can change its 
      * state, as well as the usual suspects in java.lang.System and
      * java.lang.Runtime.
+     *
+     * <p>Note that the {@link MemberAccessPolicy} will further restrict what's visible. That mechanism was introduced
+     * much later than "exposure levels", and it's the primary place to look at if you are concerned with safety.
      */
     public static final int EXPOSE_SAFE = 1;
     
@@ -120,6 +123,8 @@
      * At this level of exposure, only property getters are exposed.
      * Additionally, property getters that map to unsafe methods are not
      * exposed (i.e. Class.classLoader and Thread.contextClassLoader).
+     *
+     * <p>Note that the {@link MemberAccessPolicy} will further restrict what's visible.
      */
     public static final int EXPOSE_PROPERTIES_ONLY = 2;
 
@@ -128,7 +133,7 @@
      * Only map items, resource bundle items, and objects retrieved through
      * the generic get method (on objects of classes that have a generic get
      * method) can be retrieved through the hash interface. You might want to 
-     * call {@link #setMethodsShadowItems(boolean)} with <tt>false</tt> value to
+     * call {@link #setMethodsShadowItems(boolean)} with {@code false} value to
      * speed up map item retrieval.
      */
     public static final int EXPOSE_NOTHING = 3;
@@ -444,19 +449,19 @@
      * Specifies if an attempt to read a bean property that doesn't exist in the
      * wrapped object should throw an {@link InvalidPropertyException}.
      * 
-     * <p>If this property is <tt>false</tt> (the default) then an attempt to read
+     * <p>If this property is {@code false} (the default) then an attempt to read
      * a missing bean property is the same as reading an existing bean property whose
-     * value is <tt>null</tt>. The template can't tell the difference, and thus always
-     * can use <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins
+     * value is {@code null}. The template can't tell the difference, and thus always
+     * can use {@code ?default('something')} and {@code ?exists} and similar built-ins
      * to handle the situation.
      *
-     * <p>If this property is <tt>true</tt> then an attempt to read a bean propertly in
-     * the template (like <tt>myBean.aProperty</tt>) that doesn't exist in the bean
-     * object (as opposed to just holding <tt>null</tt> value) will cause
+     * <p>If this property is {@code true} then an attempt to read a bean propertly in
+     * the template (like {@code myBean.aProperty}) that doesn't exist in the bean
+     * object (as opposed to just holding {@code null} value) will cause
      * {@link InvalidPropertyException}, which can't be suppressed in the template
-     * (not even with <tt>myBean.noSuchProperty?default('something')</tt>). This way
-     * <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins can be used to
-     * handle existing properties whose value is <tt>null</tt>, without the risk of
+     * (not even with {@code myBean.noSuchProperty?default('something')}). This way
+     * {@code ?default('something')} and {@code ?exists} and similar built-ins can be used to
+     * handle existing properties whose value is {@code null}, without the risk of
      * hiding typos in the property names. Typos will always cause error. But mind you, it
      * goes against the basic approach of FreeMarker, so use this feature only if you really
      * know what you are doing.
@@ -481,7 +486,7 @@
     }
 
     /**
-     * By default returns <tt>this</tt>.
+     * By default returns {@code this}.
      * @see #setOuterIdentity(ObjectWrapper)
      */
     public ObjectWrapper getOuterIdentity() {
@@ -564,6 +569,8 @@
      * Sets the method exposure level. By default, set to <code>EXPOSE_SAFE</code>.
      * @param exposureLevel can be any of the <code>EXPOSE_xxx</code>
      * constants.
+     * Note that {@link #setMemberAccessPolicy(MemberAccessPolicy)} further restricts what's visible, unless this is
+     * set to {@link #EXPOSE_ALL}.
      */
     public void setExposureLevel(int exposureLevel) {
         checkModifiable();
@@ -797,8 +804,8 @@
     
     /**
      * Sets the default date type to use for date models that result from
-     * a plain <tt>java.util.Date</tt> instead of <tt>java.sql.Date</tt> or
-     * <tt>java.sql.Time</tt> or <tt>java.sql.Timestamp</tt>. Default value is 
+     * a plain {@code java.util.Date} instead of {@code java.sql.Date} or
+     * {@code java.sql.Time} or {@code java.sql.Timestamp}. Default value is 
      * {@link TemplateDateModel#UNKNOWN}.
      * @param defaultDateType the new default date type.
      */
diff --git a/src/main/java/freemarker/ext/beans/ClassIntrospector.java b/src/main/java/freemarker/ext/beans/ClassIntrospector.java
index 98580fc..bf86781 100644
--- a/src/main/java/freemarker/ext/beans/ClassIntrospector.java
+++ b/src/main/java/freemarker/ext/beans/ClassIntrospector.java
@@ -48,7 +48,6 @@
 import java.util.concurrent.ConcurrentHashMap;
 
 import freemarker.core.BugException;
-import freemarker.core._JavaVersions;
 import freemarker.ext.beans.BeansWrapper.MethodAppearanceDecision;
 import freemarker.ext.beans.BeansWrapper.MethodAppearanceDecisionInput;
 import freemarker.ext.util.ModelCache;
@@ -222,7 +221,7 @@
      * 
      * @return A {@link Map} where each key is a property/method/field name (or a special {@link Object} key like
      *         {@link #CONSTRUCTORS_KEY}), each value is a {@link FastPropertyDescriptor} or {@link Method} or
-     *         {@link OverloadedMethods} or {@link Field} (but better check the source code...).
+     *         {@link OverloadedMethods} or {@link Field} (but, you better check the source code).
      */
     Map<Object, Object> get(Class<?> clazz) {
         {
@@ -248,7 +247,7 @@
                     introspData = cache.get(clazz);
                 } catch (InterruptedException e) {
                     throw new RuntimeException(
-                            "Class inrospection data lookup aborded: " + e);
+                            "Class introspection data lookup aborted: " + e);
                 }
             }
             if (introspData != null) return introspData;
@@ -388,7 +387,7 @@
                             ((OverloadedMethods) previous).addMethod(method);
                         } else if (decision.getMethodShadowsProperty()
                                 || !(previous instanceof FastPropertyDescriptor)) {
-                            // Simple method (this far)
+                            // Simple method (so far)
                             introspData.put(methodKey, method);
                             Class<?>[] replaced = getArgTypesByMethod(introspData).put(method,
                                     method.getParameterTypes());
@@ -413,7 +412,7 @@
         List<PropertyDescriptor> introspectorPDs = introspectorPDsArray != null ? Arrays.asList(introspectorPDsArray)
                 : Collections.<PropertyDescriptor>emptyList();
         
-        if (!treatDefaultMethodsAsBeanMembers || _JavaVersions.JAVA_8 == null) {
+        if (!treatDefaultMethodsAsBeanMembers) {
             // java.beans.Introspector was good enough then.
             return introspectorPDs;
         }
@@ -434,7 +433,7 @@
         // (Note that java.beans.Introspector discovers non-accessible public methods, and to emulate that behavior
         // here, we don't utilize the accessibleMethods Map, which we might already have at this point.)
         for (Method method : clazz.getMethods()) {
-            if (_JavaVersions.JAVA_8.isDefaultMethod(method) && method.getReturnType() != void.class
+            if (method.isDefault() && method.getReturnType() != void.class
                     && !method.isBridge()) {
                 Class<?>[] paramTypes = method.getParameterTypes();
                 if (paramTypes.length == 0
@@ -607,14 +606,14 @@
         List<MethodDescriptor> introspectionMDs = introspectorMDArray != null && introspectorMDArray.length != 0
                 ? Arrays.asList(introspectorMDArray) : Collections.<MethodDescriptor>emptyList();
 
-        if (!treatDefaultMethodsAsBeanMembers || _JavaVersions.JAVA_8 == null) {
+        if (!treatDefaultMethodsAsBeanMembers) {
             // java.beans.Introspector was good enough then.
             return introspectionMDs;
         }
 
         Map<String, List<Method>> defaultMethodsToAddByName = null;
         for (Method method : clazz.getMethods()) {
-            if (_JavaVersions.JAVA_8.isDefaultMethod(method) && !method.isBridge()) {
+            if (method.isDefault() && !method.isBridge()) {
                 if (defaultMethodsToAddByName == null) {
                     defaultMethodsToAddByName = new HashMap<>();
                 }
diff --git a/src/main/java/freemarker/ext/beans/CollectionModel.java b/src/main/java/freemarker/ext/beans/CollectionModel.java
index c86d84d..2b46991 100644
--- a/src/main/java/freemarker/ext/beans/CollectionModel.java
+++ b/src/main/java/freemarker/ext/beans/CollectionModel.java
@@ -33,7 +33,7 @@
 /**
  * <p>A special case of {@link BeanModel} that can wrap Java collections
  * and that implements the {@link TemplateCollectionModel} in order to be usable 
- * in a <tt>&lt;#list&gt;</tt> block.</p>
+ * in a <code>&lt;#list&gt;</code> block.</p>
  */
 public class CollectionModel
 extends
diff --git a/src/main/java/freemarker/ext/beans/DefaultMemberAccessPolicy.java b/src/main/java/freemarker/ext/beans/DefaultMemberAccessPolicy.java
index 5f0b26c..f5bdabd 100644
--- a/src/main/java/freemarker/ext/beans/DefaultMemberAccessPolicy.java
+++ b/src/main/java/freemarker/ext/beans/DefaultMemberAccessPolicy.java
@@ -36,7 +36,7 @@
 import freemarker.template._TemplateAPI;
 
 /**
- * Member access policy, used  to implement default behavior that's mostly compatible with pre-2.3.30 versions, but is
+ * Member access policy to implement the default behavior that's mostly compatible with pre-2.3.30 versions, but is
  * somewhat safer; it still can't provide safety in practice, if you allow untrusted users to edit templates! Use
  * {@link WhitelistMemberAccessPolicy} if you need stricter control.
  *
diff --git a/src/main/java/freemarker/ext/beans/EnumerationModel.java b/src/main/java/freemarker/ext/beans/EnumerationModel.java
index 75888bb..4d464d2 100644
--- a/src/main/java/freemarker/ext/beans/EnumerationModel.java
+++ b/src/main/java/freemarker/ext/beans/EnumerationModel.java
@@ -58,7 +58,7 @@
     }
 
     /**
-     * This allows the enumeration to be used in a <tt>&lt;#list&gt;</tt> block.
+     * This allows the enumeration to be used in a <code>&lt;#list&gt;</code> block.
      * @return "this"
      */
     @Override
diff --git a/src/main/java/freemarker/ext/beans/IteratorModel.java b/src/main/java/freemarker/ext/beans/IteratorModel.java
index e7aec98..9d28162 100644
--- a/src/main/java/freemarker/ext/beans/IteratorModel.java
+++ b/src/main/java/freemarker/ext/beans/IteratorModel.java
@@ -33,7 +33,7 @@
  * </p>
  * <p>It differs from the {@link freemarker.template.SimpleCollection} in that 
  * it inherits from {@link BeanModel}, and therefore you can call methods on 
- * it directly, even to the effect of calling <tt>iterator.remove()</tt> in 
+ * it directly, even to the effect of calling {@code iterator.remove()} in 
  * the template.</p> <p>Using the model as a collection model is NOT 
  * thread-safe, as iterators are inherently not thread-safe.
  * Further, you can iterate over it only once. Attempts to call the
@@ -62,7 +62,7 @@
     }
 
     /**
-     * This allows the iterator to be used in a <tt>&lt;#list&gt;</tt> block.
+     * This allows the iterator to be used in a <code>&lt;#list&gt;</code> block.
      * @return "this"
      */
     @Override
diff --git a/src/main/java/freemarker/ext/beans/MapModel.java b/src/main/java/freemarker/ext/beans/MapModel.java
index 7bb8105..53bb66e 100644
--- a/src/main/java/freemarker/ext/beans/MapModel.java
+++ b/src/main/java/freemarker/ext/beans/MapModel.java
@@ -32,14 +32,14 @@
 /**
  * <p>A special case of {@link BeanModel} that adds implementation
  * for {@link TemplateMethodModelEx} on map objects that is a shortcut for the
- * <tt>Map.get()</tt> method. Note that if the passed argument itself is a
+ * {@code Map.get()} method. Note that if the passed argument itself is a
  * reflection-wrapper model, then the map lookup will be performed using the
- * wrapped object as the key. Note that you can call <tt>get()</tt> using the
- * <tt>map.key</tt> syntax inherited from {@link BeanModel} as well, 
+ * wrapped object as the key. Note that you can call {@code get()} using the
+ * {@code map.key} syntax inherited from {@link BeanModel} as well, 
  * however in that case the key is always a string.</p>
  * <p>The class itself does not implement the {@link freemarker.template.TemplateCollectionModel}.
- * You can, however use <tt>map.entrySet()</tt>, <tt>map.keySet()</tt>, or
- * <tt>map.values()</tt> to obtain {@link freemarker.template.TemplateCollectionModel} instances for 
+ * You can, however use {@code map.entrySet()}, {@code map.keySet()}, or
+ * {@code map.values()} to obtain {@link freemarker.template.TemplateCollectionModel} instances for 
  * various aspects of the map.</p>
  */
 public class MapModel
@@ -69,7 +69,7 @@
     }
 
     /**
-     * The first argument is used as a key to call the map's <tt>get</tt> method.
+     * The first argument is used as a key to call the map's {@code get} method.
      */
     @Override
     public Object exec(List arguments)
diff --git a/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java b/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
index 2ca568d..6d7abdb 100644
--- a/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
+++ b/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
@@ -58,6 +58,10 @@
  * {@link Object#equals(Object)} implementation if possible.
  *
  * @since 2.3.30
+ *
+ * @see DefaultMemberAccessPolicy
+ * @see WhitelistMemberAccessPolicy
+ * @see LegacyDefaultMemberAccessPolicy
  */
 public interface MemberAccessPolicy {
     /**
diff --git a/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java b/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java
index 8dd134a..80731e0 100644
--- a/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java
+++ b/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java
@@ -38,36 +38,36 @@
      * Implement this to tweak certain aspects of how methods appear in the
      * data-model. {@link BeansWrapper} will pass in all Java methods here that
      * it intends to expose in the data-model as methods (so you can do
-     * <tt>obj.foo()</tt> in the template).
+     * {@code obj.foo()} in the template).
      * With this method you can do the following tweaks:
      * <ul>
      *   <li>Hide a method that would be otherwise shown by calling
      *     {@link MethodAppearanceDecision#setExposeMethodAs(String)}
-     *     with <tt>null</tt> parameter. Note that you can't un-hide methods
+     *     with {@code null} parameter. Note that you can't un-hide methods
      *     that are not public or are considered to by unsafe
      *     (like {@link Object#wait()}) because
      *     {@link #process} is not called for those.</li>
      *   <li>Show the method with a different name in the data-model than its
      *     real name by calling
      *     {@link MethodAppearanceDecision#setExposeMethodAs(String)}
-     *     with non-<tt>null</tt> parameter.
+     *     with non-{@code null} parameter.
      *   <li>Create a fake JavaBean property for this method by calling
      *     {@link MethodAppearanceDecision#setExposeAsProperty(PropertyDescriptor)}.
-     *     For example, if you have <tt>int size()</tt> in a class, but you
-     *     want it to be accessed from the templates as <tt>obj.size</tt>,
-     *     rather than as <tt>obj.size()</tt>, you can do that with this
+     *     For example, if you have {@code int size()} in a class, but you
+     *     want it to be accessed from the templates as {@code obj.size},
+     *     rather than as {@code obj.size()}, you can do that with this
      *     (but remember calling
      *     {@link MethodAppearanceDecision#setMethodShadowsProperty(boolean)
      *     setMethodShadowsProperty(false)} as well, if the method name is exactly
      *     the same as the property name).
      *     The default is {@code null}, which means that no fake property is
      *     created for the method. You need not and shouldn't set this
-     *     to non-<tt>null</tt> for the getter methods of real JavaBean
+     *     to non-{@code null} for the getter methods of real JavaBean
      *     properties, as those are automatically shown as properties anyway.
      *     The property name in the {@link PropertyDescriptor} can be anything,
      *     but the method (or methods) in it must belong to the class that
-     *     is given as the <tt>clazz</tt> parameter or it must be inherited from
-     *     that class, or else whatever errors can occur later.
+     *     is given as the {@code clazz} parameter or it must be inherited from
+     *     that class, otherwise the behavior is undefined, and errors can occur later.
      *     {@link IndexedPropertyDescriptor}-s are supported.
      *     If a real JavaBean property of the same name exists, or a fake property
      *     of the same name was already assigned earlier, it won't be
@@ -76,9 +76,9 @@
      *   <li>Prevent the method to hide a JavaBean property (fake or real) of
      *     the same name by calling
      *     {@link MethodAppearanceDecision#setMethodShadowsProperty(boolean)}
-     *     with <tt>false</tt>. The default is <tt>true</tt>, so if you have
+     *     with {@code false}. The default is {@code true}, so if you have
      *     both a property and a method called "foo", then in the template
-     *     <tt>myObject.foo</tt> will return the method itself instead
+     *     {@code myObject.foo} will return the method itself instead
      *     of the property value, which is often undesirable.
      * </ul>
      * 
diff --git a/src/main/java/freemarker/ext/beans/OverloadedNumberUtil.java b/src/main/java/freemarker/ext/beans/OverloadedNumberUtil.java
index 243a935..da650b0 100644
--- a/src/main/java/freemarker/ext/beans/OverloadedNumberUtil.java
+++ b/src/main/java/freemarker/ext/beans/OverloadedNumberUtil.java
@@ -84,7 +84,7 @@
      * type and the coerced type, all encoded into the class of the value, which is used as the overloaded method lookup
      * cache key.
      *  
-     * <p>See also: <tt>src\main\misc\overloadedNumberRules\prices.ods</tt>.
+     * <p>See also: {@code src\main\misc\overloadedNumberRules\prices.ods}.
      * 
      * @param num the number to coerce
      * @param typeFlags the type flags of the target parameter position; see {@link TypeFlags}
diff --git a/src/main/java/freemarker/ext/beans/ResourceBundleModel.java b/src/main/java/freemarker/ext/beans/ResourceBundleModel.java
index 4b2b0fb..3bff44f 100644
--- a/src/main/java/freemarker/ext/beans/ResourceBundleModel.java
+++ b/src/main/java/freemarker/ext/beans/ResourceBundleModel.java
@@ -45,10 +45,10 @@
  *
  * <p>Typical usages:</p>
  * <ul>
- * <li><tt>bundle.resourceKey</tt> will retrieve the object from resource bundle
- * with key <tt>resourceKey</tt></li>
- * <li><tt>bundle("patternKey", arg1, arg2, arg3)</tt> will retrieve the string
- * from resource bundle with key <tt>patternKey</tt>, and will use it as a pattern
+ * <li>{@code bundle.resourceKey} will retrieve the object from resource bundle
+ * with key {@code resourceKey}</li>
+ * <li>{@code bundle("patternKey", arg1, arg2, arg3)} will retrieve the string
+ * from resource bundle with key {@code patternKey}, and will use it as a pattern
  * for MessageFormat with arguments arg1, arg2 and arg3</li>
  * </ul>
  */
diff --git a/src/main/java/freemarker/ext/beans/SimpleMapModel.java b/src/main/java/freemarker/ext/beans/SimpleMapModel.java
index 03690f1..16c6b45 100644
--- a/src/main/java/freemarker/ext/beans/SimpleMapModel.java
+++ b/src/main/java/freemarker/ext/beans/SimpleMapModel.java
@@ -39,7 +39,7 @@
 import freemarker.template.utility.RichObjectWrapper;
 
 /**
- * Model used by {@link BeansWrapper} when <tt>simpleMapWrapper</tt>
+ * Model used by {@link BeansWrapper} when {@code simpleMapWrapper}
  * mode is enabled. Provides a simple hash model interface to the
  * underlying map (does not copy like {@link freemarker.template.SimpleHash}),
  * and a method interface to non-string keys.
diff --git a/src/main/java/freemarker/ext/beans/StaticModel.java b/src/main/java/freemarker/ext/beans/StaticModel.java
index bfbccb9..91ccd96 100644
--- a/src/main/java/freemarker/ext/beans/StaticModel.java
+++ b/src/main/java/freemarker/ext/beans/StaticModel.java
@@ -53,7 +53,7 @@
     }
 
     /**
-     * Returns the field or method named by the <tt>key</tt>
+     * Returns the field or method named by the {@code key}
      * parameter.
      */
     @Override
diff --git a/src/main/java/freemarker/ext/beans/StaticModels.java b/src/main/java/freemarker/ext/beans/StaticModels.java
index 28896be..7f51875 100644
--- a/src/main/java/freemarker/ext/beans/StaticModels.java
+++ b/src/main/java/freemarker/ext/beans/StaticModels.java
@@ -25,9 +25,9 @@
 /**
  * Utility class for instantiating {@link StaticModel} instances from
  * templates. If your template's data model contains an instance of
- * StaticModels (named, say <tt>StaticModels</tt>), then you can
+ * StaticModels (named, say {@code StaticModels}), then you can
  * instantiate an arbitrary StaticModel using get syntax (i.e.
- * <tt>StaticModels["java.lang.System"].currentTimeMillis()</tt>).
+ * {@code StaticModels["java.lang.System"].currentTimeMillis()}).
  */
 class StaticModels extends ClassBasedModelFactory {
     
diff --git a/src/main/java/freemarker/ext/jdom/NodeListModel.java b/src/main/java/freemarker/ext/jdom/NodeListModel.java
index dc66924..a096213 100644
--- a/src/main/java/freemarker/ext/jdom/NodeListModel.java
+++ b/src/main/java/freemarker/ext/jdom/NodeListModel.java
@@ -242,100 +242,100 @@
      * library to be present at run time. Below are listed the recognized keys.
      * In key descriptions, "applicable to this-and-that node type" means that if
      * a key is applied to a node list that contains a node of non-applicable type
-     * a TemplateMethodModel will be thrown. However, you can use <tt>_ftype</tt>
+     * a TemplateMethodModel will be thrown. However, you can use {@code _ftype}
      * key to explicitly filter out undesired node types prior to applying the
      * restricted-applicability key. Also "current nodes" means nodes contained in this
      * set.
      * <ul>
-     *    <li><tt>*</tt> or <tt>_children</tt>: all direct element children of current nodes (non-recursive). Applicable
+     *    <li>{@code *} or {@code _children}: all direct element children of current nodes (non-recursive). Applicable
      *  to element and document nodes.</li>
-     *    <li><tt>@*</tt> or <tt>_attributes</tt>: all attributes of current nodes. Applicable to elements only.</li>
-     *    <li><tt>_content</tt> the complete content of current nodes (non-recursive).
+     *    <li>{@code @*} or {@code _attributes}: all attributes of current nodes. Applicable to elements only.</li>
+     *    <li>{@code _content} the complete content of current nodes (non-recursive).
      *  Applicable to elements and documents.</li>
-     *    <li><tt>_text</tt>: the text of current nodes, one string per node (non-recursive).
+     *    <li>{@code _text}: the text of current nodes, one string per node (non-recursive).
      *  Applicable to elements, attributes, comments, processing instructions (returns its data)
      *  and CDATA sections. The reserved XML characters ('&lt;' and '&amp;') are escaped.</li>
-     *    <li><tt>_plaintext</tt>: same as <tt>_text</tt>, but does not escape any characters,
+     *    <li>{@code _plaintext}: same as {@code _text}, but does not escape any characters,
      *  and instead of returning a NodeList returns a SimpleScalar.</li>
-     *    <li><tt>_name</tt>: the names of current nodes, one string per node (non-recursive).
+     *    <li>{@code _name}: the names of current nodes, one string per node (non-recursive).
      *  Applicable to elements and attributes (returns their local name), 
      *  entities, processing instructions (returns its target), doctypes 
      * (returns its public ID)</li>
-     *    <li><tt>_qname</tt>: the qualified names of current nodes in <tt>[namespacePrefix:]localName</tt>
+     *    <li>{@code _qname}: the qualified names of current nodes in {@code [namespacePrefix:]localName}
      * form, one string per node (non-recursive). Applicable to elements and attributes</li>
-     *    <li><tt>_cname</tt>: the canonical names of current nodes (namespace URI + local name),
+     *    <li>{@code _cname}: the canonical names of current nodes (namespace URI + local name),
      * one string per node (non-recursive). Applicable to elements and attributes</li>
-     *    <li><tt>_nsprefix</tt>: namespace prefixes of current nodes,
+     *    <li>{@code _nsprefix}: namespace prefixes of current nodes,
      * one string per node (non-recursive). Applicable to elements and attributes</li>
-     *    <li><tt>_nsuri</tt>: namespace URIs of current nodes,
+     *    <li>{@code _nsuri}: namespace URIs of current nodes,
      * one string per node (non-recursive). Applicable to elements and attributes</li>
-     *    <li><tt>_parent</tt>: parent elements of current nodes. Applicable to element, attribute, comment,
+     *    <li>{@code _parent}: parent elements of current nodes. Applicable to element, attribute, comment,
      *  entity, processing instruction.</li>
-     *    <li><tt>_ancestor</tt>: all ancestors up to root element (recursive) of current nodes. Applicable
-     *  to same node types as <tt>_parent</tt>.</li>
-     *    <li><tt>_ancestorOrSelf</tt>: all ancestors of current nodes plus current nodes. Applicable
-     *  to same node types as <tt>_parent</tt>.</li>
-     *    <li><tt>_descendant</tt>: all recursive descendant element children of current nodes. Applicable to
+     *    <li>{@code _ancestor}: all ancestors up to root element (recursive) of current nodes. Applicable
+     *  to same node types as {@code _parent}.</li>
+     *    <li>{@code _ancestorOrSelf}: all ancestors of current nodes plus current nodes. Applicable
+     *  to same node types as {@code _parent}.</li>
+     *    <li>{@code _descendant}: all recursive descendant element children of current nodes. Applicable to
      *  document and element nodes.
-     *    <li><tt>_descendantOrSelf</tt>: all recursive descendant element children of current nodes
+     *    <li>{@code _descendantOrSelf}: all recursive descendant element children of current nodes
      *  plus current nodes. Applicable to document and element nodes.
-     *    <li><tt>_document</tt>: all documents the current nodes belong to.
+     *    <li>{@code _document}: all documents the current nodes belong to.
      *  Applicable to all nodes except text.
-     *    <li><tt>_doctype</tt>: doctypes of the current nodes.
+     *    <li>{@code _doctype}: doctypes of the current nodes.
      *  Applicable to document nodes only.
-     *    <li><tt>_fname</tt>: is a filter-by-name template method model. When called,
+     *    <li>{@code _fname}: is a filter-by-name template method model. When called,
      *  it will yield a node list that contains only those current nodes whose name
      *  matches one of names passed as argument. Attribute names should NOT be prefixed with the
      *  at sign (@). Applicable on all node types, however has no effect on unnamed nodes.</li>
-     *    <li><tt>_ftype</tt>: is a filter-by-type template method model. When called,
+     *    <li>{@code _ftype}: is a filter-by-type template method model. When called,
      *  it will yield a node list that contains only those current nodes whose type matches one
      *  of types passed as argument. You should pass a single string to this method
      *  containing the characters of all types to keep. Valid characters are:
      *  e (Element), a (Attribute), n (Entity), d (Document), t (DocType),
      *  c (Comment), p (ProcessingInstruction), x (text). If the string anywhere contains
      *  the exclamation mark (!), the filter's effect is inverted.</li>
-     *    <li><tt>_type</tt>: Returns a one-character String SimpleScalar containing
+     *    <li>{@code _type}: Returns a one-character String SimpleScalar containing
      *    the typecode of the first node in the node list. Valid characters are:
      *  e (Element), a (Attribute), n (Entity), d (Document), t (DocType),
      *  c (Comment), p (ProcessingInstruction), x (text). If the type of the node
      *  is unknown, returns '?'. If the node list is empty, returns an empty string scalar.</li>
-     *    <li><tt>_unique</tt>: a copy of the current nodes that keeps only the
+     *    <li>{@code _unique}: a copy of the current nodes that keeps only the
      *  first occurrence of every node, eliminating duplicates. Duplicates can
-     *  occur in the node list by applying uptree-traversals <tt>_parent</tt>,
-     *  <tt>_ancestor</tt>, <tt>_ancestorOrSelf</tt>, and <tt>_document</tt>.
-     *  I.e. <tt>foo._children._parent</tt> will return a node list that has
+     *  occur in the node list by applying uptree-traversals {@code _parent},
+     *  {@code _ancestor}, {@code _ancestorOrSelf}, and {@code _document}.
+     *  I.e. {@code foo._children._parent} will return a node list that has
      *  duplicates of nodes in foo - each node will have the number of occurrences
      *  equal to the number of its children. In these cases, use
-     *  <tt>foo._children._parent._unique</tt> to eliminate duplicates. Applicable
+     *  {@code foo._children._parent._unique} to eliminate duplicates. Applicable
      *  to all node types.</li>
-     *    <li><tt>_copy</tt>: a copy of the current node list. It is a shallow copy that
+     *    <li>{@code _copy}: a copy of the current node list. It is a shallow copy that
      *  shares the underlying node list with this node list, however it has a
      *  separate namespace registry, so it can be used to guarantee that subsequent
      *  changes to the set of registered namespaces does not affect the node lists
      *  that were used to create this node list. Applicable to all node types.</li>
-     *    <li><tt>_registerNamespace(prefix, uri)</tt>: register a XML namespace
+     *    <li>{@code _registerNamespace(prefix, uri)}: register a XML namespace
      *  with the specified prefix and URI for the current node list and all node
      *  lists that are derived from the current node list. After registering,
-     *  you can use the <tt>nodelist["prefix:localname"]</tt> or
-     *  <tt>nodelist["@prefix:localname"]</tt> syntaxes to reach elements and
+     *  you can use the {@code nodelist["prefix:localname"]} or
+     *  {@code nodelist["@prefix:localname"]} syntaxes to reach elements and
      *  attributes whose names are namespace-scoped. Note that the namespace
      *  prefix need not match the actual prefix used by the XML document itself
      *  since namespaces are compared solely by their URI. You can also register
      *  namespaces from Java code using the
      *  {@link #registerNamespace(String, String)} method.
      * </li>
-     *    <li><tt>@attributeName</tt>: named attributes of current nodes. Applicable to
+     *    <li>{@code @attributeName}: named attributes of current nodes. Applicable to
      *  elements, doctypes and processing instructions. On doctypes it supports
-     *  attributes <tt>publicId</tt>, <tt>systemId</tt> and <tt>elementName</tt>. On processing
-     *  instructions, it supports attributes <tt>target</tt> and <tt>data</tt>, as
-     *  well as any other attribute name specified in data as <tt>name="value"</tt> pair.
+     *  attributes {@code publicId}, {@code systemId} and {@code elementName}. On processing
+     *  instructions, it supports attributes {@code target} and {@code data}, as
+     *  well as any other attribute name specified in data as {@code name="value"} pair.
      *  The attribute nodes for doctype and processing instruction are synthetic, and
-     *  as such have no parent. Note, however that <tt>@*</tt> does NOT operate on
+     *  as such have no parent. Note, however that {@code @*} does NOT operate on
      *  doctypes or processing instructions.</li>
      *    <li>any other key: element children of current nodes with name matching the key.
-     *  This allows for convenience child traversal in <tt>book.chapter.title</tt> style syntax.
-     *  Note that <tt>nodeset.childname</tt> is technically equivalent to
-     *  <tt>nodeset._children._fname("childname")</tt>, but is both shorter to write
+     *  This allows for convenience child traversal in {@code book.chapter.title} style syntax.
+     *  Note that {@code nodeset.childname} is technically equivalent to
+     *  {@code nodeset._children._fname("childname")}, but is both shorter to write
      *  and evaluates faster. Applicable to document and element nodes.</li>
      * </ul>
      * The order of nodes in the resulting set is the order of evaluation of the key
@@ -566,13 +566,13 @@
      * refer to the registered namespace using its prefix in the
      * {@link #get(String)} method from this node list and all other
      * node lists that are derived from this node list. Use the
-     * <tt>nodelist["prefix:localname"]</tt> or the
-     * <tt>nodelist["@prefix:localname"]</tt> syntax to reach elements and
+     * {@code nodelist["prefix:localname"]} or the
+     * {@code nodelist["@prefix:localname"]} syntax to reach elements and
      *  attributes whose names are namespace-scoped. Note that the namespace
      * prefix need not match the actual prefix used by the XML document itself
      * since namespaces are compared solely by their URI. You can also register
      * namespaces during template evaluation using the
-     * <tt>nodelist._registerNamespace(prefix, uri)</tt> syntax in the template.
+     * {@code nodelist._registerNamespace(prefix, uri)} syntax in the template.
      * This mechanism is completely independent from the namespace declarations
      * in the XML document itself; its purpose is to give you an easy way
      * to refer to namespace-scoped elements in {@link #get(String)} and
@@ -1158,7 +1158,7 @@
     /**
      * Loads a template from a file passed as the first argument, loads an XML
      * document from the standard input, passes it to the template as variable
-     * <tt>document</tt> and writes the result of template processing to
+     * {@code document} and writes the result of template processing to
      * standard output.
      * 
      * @deprecated Will be removed (main method in a library, often classified as CWE-489 "Leftover Debug Code").
diff --git a/src/main/java/freemarker/ext/jsp/EventForwarding.java b/src/main/java/freemarker/ext/jsp/EventForwarding.java
index ce2dad6..493edd3 100644
--- a/src/main/java/freemarker/ext/jsp/EventForwarding.java
+++ b/src/main/java/freemarker/ext/jsp/EventForwarding.java
@@ -37,8 +37,8 @@
 import freemarker.log.Logger;
 
 /**
- * An instance of this class should be registered as a <tt>&lt;listener&gt;</tt> in
- * the <tt>web.xml</tt> descriptor in order to correctly dispatch events to
+ * An instance of this class should be registered as a <code>&lt;listener&gt;</code> in
+ * the {@code web.xml} descriptor in order to correctly dispatch events to
  * event listeners that are specified in TLD files.
  */
 public class EventForwarding
diff --git a/src/main/java/freemarker/ext/jsp/TaglibFactory.java b/src/main/java/freemarker/ext/jsp/TaglibFactory.java
index 0dfa9b6..91b7979 100644
--- a/src/main/java/freemarker/ext/jsp/TaglibFactory.java
+++ b/src/main/java/freemarker/ext/jsp/TaglibFactory.java
@@ -167,7 +167,7 @@
      *            {@code http://example.com/foo}), root relative URI (like {@code /bar/foo.tld}) and non-root relative
      *            URI (like {@code bar/foo.tld}). Note that if a non-root relative URI is used it's resolved relative to
      *            the URL of the current request. In this case, the current request is obtained by looking up a
-     *            {@link HttpRequestHashModel} object named <tt>Request</tt> in the root data model.
+     *            {@link HttpRequestHashModel} object named {@code Request} in the root data model.
      *            {@link FreemarkerServlet} provides this object under the expected name, and custom servlets that want
      *            to integrate JSP taglib support should do the same.
      * 
@@ -1116,7 +1116,7 @@
     }
 
     /**
-     * To search TLD-s under <tt>sevletContext:/WEB-INF/lib/*.{jar,zip}/META-INF/**</tt><tt>/*.tld</tt>, as requested by
+     * To search TLD-s under <code>sevletContext:/WEB-INF/lib/*.{jar,zip}/META-INF/**</code>{@code /*.tld}, as requested by
      * the JSP specification. Note that these also used to be in the classpath, so it's redundant to use this together
      * with a sufficiently permissive {@link ClasspathMetaInfTldSource}.
      * 
diff --git a/src/main/java/freemarker/ext/servlet/AllHttpScopesHashModel.java b/src/main/java/freemarker/ext/servlet/AllHttpScopesHashModel.java
index 16d9eab..bd74070 100644
--- a/src/main/java/freemarker/ext/servlet/AllHttpScopesHashModel.java
+++ b/src/main/java/freemarker/ext/servlet/AllHttpScopesHashModel.java
@@ -38,10 +38,10 @@
  * and "Request" keys largely obsolete, however we keep them for backward
  * compatibility (also, "Request" is required for proper operation of JSP
  * taglibs).
- * It is on purpose that we didn't override <tt>keys</tt> and <tt>values</tt>
+ * It is on purpose that we didn't override {@code keys} and {@code values}
  * methods. That way, only those variables assigned into the hash directly by a
- * subclass of <tt>FreemarkerServlet</tt> that overrides
- * <tt>preTemplateProcess</tt>) are discovered as "page" variables by the FM
+ * subclass of {@code FreemarkerServlet} that overrides
+ * {@code preTemplateProcess}) are discovered as "page" variables by the FM
  * JSP PageContext implementation.
  */
 public class AllHttpScopesHashModel extends SimpleHash {
@@ -65,8 +65,8 @@
     }
     
     /**
-     * Stores a model in the hash so that it doesn't show up in <tt>keys()</tt>
-     * and <tt>values()</tt> methods. Used to put the Application, Session,
+     * Stores a model in the hash so that it doesn't show up in {@code keys()}
+     * and {@code values()} methods. Used to put the Application, Session,
      * Request, RequestParameters and JspTaglibs objects.
      * @param key the key under which the model is stored
      * @param model the stored model
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index 6ceeeb9..b9164ab 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -115,9 +115,9 @@
  * this is interpreted as a {@link ServletContext} resource path, which practically means a web application directory
  * relative path, or a {@code WEB-INF/lib/*.jar/META-INF/resources}-relative path (note that this last haven't always
  * worked before FreeMarker 2.3.23).<br>
- * Alternatively, you can prepend it with <tt>file://</tt> to indicate a literal path in the file system (i.e.
- * <tt>file:///var/www/project/templates/</tt>). Note that three slashes were used to specify an absolute path.<br>
- * Also, you can prepend it with {@code classpath:}, like in <tt>classpath:com/example/templates</tt>, to indicate that
+ * Alternatively, you can prepend it with {@code file://} to indicate a literal path in the file system (i.e.
+ * {@code file:///var/www/project/templates/}). Note that three slashes were used to specify an absolute path.<br>
+ * Also, you can prepend it with {@code classpath:}, like in {@code classpath:com/example/templates}, to indicate that
  * you want to load templates from the specified package accessible through the Thread Context Class Loader of the
  * thread that initializes this servlet.<br>
  * If {@code incompatible_improvements} is set to 2.3.22 (or higher), you can specify multiple comma separated locations
@@ -130,10 +130,10 @@
  * {@link WebappTemplateLoader#setAttemptFileAccess(boolean)} and
  * {@link WebappTemplateLoader#setURLConnectionUsesCaches(Boolean)} to tune the {@link WebappTemplateLoader}. For
  * backward compatibility (not recommended!), you can use the {@code class://} prefix, like in
- * <tt>class://com/example/templates</tt> format, which is similar to {@code classpath:}, except that it uses the
+ * {@code class://com/example/templates} format, which is similar to {@code classpath:}, except that it uses the
  * defining class loader of this servlet's class. This can cause template-not-found errors, if that class (in
  * {@code freemarer.jar} usually) is not local to the web application, while the templates are.<br>
- * The default value is <tt>class://</tt> (that is, the root of the class hierarchy), which is not recommended anymore,
+ * The default value is {@code class://} (that is, the root of the class hierarchy), which is not recommended anymore,
  * and should be overwritten with the {@value #INIT_PARAM_TEMPLATE_PATH} init-param.</li>
  * 
  * <li><strong>{@value #INIT_PARAM_NO_CACHE}</strong>: If set to {@code true}, generates headers in the response that
@@ -147,8 +147,8 @@
  * <li>If the {@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE} init-param is {@value #INIT_PARAM_VALUE_NEVER} (the
  * default is {@value #INIT_PARAM_VALUE_ALWAYS}), then the value of {@link HttpServletResponse#getContentType()} is used
  * if that's non-{@code null}.
- * <li>The template's <tt>content_type</tt> custom attribute, usually specified via the <tt>attributes</tt> parameter of
- * the <tt>&lt;#ftl&gt;</tt> directive. This is a legacy feature, deprecated by the {@link OutputFormat} mechanism.
+ * <li>The template's {@code content_type} custom attribute, usually specified via the {@code attributes} parameter of
+ * the <code>&lt;#ftl&gt;</code> directive. This is a legacy feature, deprecated by the {@link OutputFormat} mechanism.
  * <li>The {@linkplain Template#getOutputFormat() output format of the template}, if that has non-{@code null} MIME-type
  * ({@link OutputFormat#getMimeType()}). When a template has no output format specified, {@link UndefinedOutputFormat}
  * is used, which has {@code null} MIME-type. (The output format of a template is deduced from {@link Configuration}
@@ -159,9 +159,9 @@
  * (the default is {@value #INIT_PARAM_VALUE_ALWAYS}), then the value of {@link HttpServletResponse#getContentType()} is
  * used if that's non-{@code null}.
  * </ol>
- * If none of the above gives a MIME type, then this init-param does. Defaults to <tt>"text/html"</tt>. If and only if
+ * If none of the above gives a MIME type, then this init-param does. Defaults to {@code "text/html"}. If and only if
  * the {@value #INIT_PARAM_RESPONSE_CHARACTER_ENCODING} init-param is set to {@value #INIT_PARAM_VALUE_LEGACY} (which is
- * the default of it), the content type may include the charset (as in <tt>"text/html; charset=utf-8"</tt>), in which
+ * the default of it), the content type may include the charset (as in {@code "text/html; charset=utf-8"}), in which
  * case that specifies the actual charset of the output. If the the {@value #INIT_PARAM_RESPONSE_CHARACTER_ENCODING}
  * init-param is not set to {@value #INIT_PARAM_VALUE_LEGACY}, then specifying the charset in the
  * {@value #INIT_PARAM_CONTENT_TYPE} init-param is not allowed, and will cause servlet initialization error.</li>
@@ -173,7 +173,7 @@
  * type in the response, unless {@link HttpServletResponse#getContentType()} is {@code null}. The third possible value
  * is {@value #INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE}, which means that we only set the content type if either
  * the template has an associated {@link OutputFormat} with non-{@code null} {@link OutputFormat#getMimeType()}, or it
- * has a custom attribute with name <tt>content_type</tt>, or {@link HttpServletResponse#getContentType()} is
+ * has a custom attribute with name {@code content_type}, or {@link HttpServletResponse#getContentType()} is
  * {@code null}. Setting this init-param allows you to specify the content type before forwarding to
  * {@link FreemarkerServlet}.</li>
  *
@@ -883,13 +883,29 @@
                 }
             }
         } catch (TemplateException e) {
+            boolean suppressServletException;
+
             final TemplateExceptionHandler teh = config.getTemplateExceptionHandler();
             // Ensure that debug handler responses aren't rolled back:
             if (teh == TemplateExceptionHandler.HTML_DEBUG_HANDLER || teh == TemplateExceptionHandler.DEBUG_HANDLER
-                    || teh.getClass().getName().indexOf("Debug") != -1) {
+                    || teh.getClass().getName().contains("Debug")) {
                 response.flushBuffer();
+
+                // Apparently, if the status is 200, yet the servlet throw an exception, Jetty (9.4.53) closes the
+                // connection, so the developer possibly won't see the debug error page (or not all of it).
+                suppressServletException = true;
+            } else {
+                suppressServletException = false;
             }
-            throw newServletExceptionWithFreeMarkerLogging("Error executing FreeMarker template", e);
+
+            if (suppressServletException) {
+                logServletExceptionWithFreemarkerLog("Error executing FreeMarker template", e);
+                log("Error executing FreeMarker template. " +
+                        "Servlet-level exception was suppressed to show debug page with HTTP 200. " +
+                        "See earlier FreeMarker log message for details!");
+            } else {
+                throw newServletExceptionWithFreeMarkerLogging("Error executing FreeMarker template", e);
+            }
         }
     }
 
@@ -937,13 +953,7 @@
     }
 
     private ServletException newServletExceptionWithFreeMarkerLogging(String message, Throwable cause) throws ServletException {
-        if (cause instanceof TemplateException) {
-            // For backward compatibility, we log into the same category as Environment did when
-            // log_template_exceptions was true.
-            LOG_RT.error(message, cause);
-        } else {
-            LOG.error(message, cause);
-        }
+        logServletExceptionWithFreemarkerLog(message, cause);
 
         ServletException e = new ServletException(message, cause);
         try {
@@ -953,9 +963,19 @@
         } catch (Exception ex) {
             // Ignored; see above
         }
-        throw e;
+        return e;
     }
-    
+
+    private static void logServletExceptionWithFreemarkerLog(String message, Throwable cause) {
+        if (cause instanceof TemplateException) {
+            // For backward compatibility, we log into the same category as Environment did when
+            // log_template_exceptions was true.
+            LOG_RT.error(message, cause);
+        } else {
+            LOG.error(message, cause);
+        }
+    }
+
     @SuppressFBWarnings(value={ "MSF_MUTABLE_SERVLET_FIELD", "DC_DOUBLECHECK" }, justification="Performance trick")
     private void logWarnOnObjectWrapperMismatch() {
         // Deliberately uses double check locking.
diff --git a/src/main/java/freemarker/ext/util/IdentityHashMap.java b/src/main/java/freemarker/ext/util/IdentityHashMap.java
index 038d5ad..7ed7fa5 100644
--- a/src/main/java/freemarker/ext/util/IdentityHashMap.java
+++ b/src/main/java/freemarker/ext/util/IdentityHashMap.java
@@ -96,7 +96,7 @@
 
     /**
      * Constructs a new, empty map with the specified initial capacity
-     * and default load factor, which is <tt>0.75</tt>.
+     * and default load factor, which is {@code 0.75}.
      *
      * @param   initialCapacity   the initial capacity of the IdentityHashMap.
      * @throws    IllegalArgumentException if the initial capacity is less
@@ -108,7 +108,7 @@
 
     /**
      * Constructs a new, empty map with a default capacity and load
-     * factor, which is <tt>0.75</tt>.
+     * factor, which is {@code 0.75}.
      */
     public IdentityHashMap() {
         this(11, 0.75f);
@@ -118,7 +118,7 @@
      * Constructs a new map with the same mappings as the given map.  The
      * map is created with a capacity of twice the number of mappings in
      * the given map or 11 (whichever is greater), and a default load factor,
-     * which is <tt>0.75</tt>.
+     * which is {@code 0.75}.
      *
      * @param t the map whose mappings are to be placed in this map.
      */
@@ -138,9 +138,9 @@
     }
 
     /**
-     * Returns <tt>true</tt> if this map contains no key-value mappings.
+     * Returns {@code true} if this map contains no key-value mappings.
      *
-     * @return <tt>true</tt> if this map contains no key-value mappings.
+     * @return {@code true} if this map contains no key-value mappings.
      */
     @Override
     public boolean isEmpty() {
@@ -148,11 +148,11 @@
     }
 
     /**
-     * Returns <tt>true</tt> if this map maps one or more keys to the
+     * Returns {@code true} if this map maps one or more keys to the
      * specified value.
      *
      * @param value value whose presence in this map is to be tested.
-     * @return <tt>true</tt> if this map maps one or more keys to the
+     * @return {@code true} if this map maps one or more keys to the
      *         specified value.
      */
     @Override
@@ -175,10 +175,10 @@
     }
 
     /**
-     * Returns <tt>true</tt> if this map contains a mapping for the specified
+     * Returns {@code true} if this map contains a mapping for the specified
      * key.
      *
-     * @return <tt>true</tt> if this map contains a mapping for the specified
+     * @return {@code true} if this map contains a mapping for the specified
      * key.
      * @param key key whose presence in this Map is to be tested.
      */
@@ -202,10 +202,10 @@
 
     /**
      * Returns the value to which this map maps the specified key.  Returns
-     * <tt>null</tt> if the map contains no mapping for this key.  A return
-     * value of <tt>null</tt> does not <i>necessarily</i> indicate that the
+     * {@code null} if the map contains no mapping for this key.  A return
+     * value of {@code null} does not <i>necessarily</i> indicate that the
      * map contains no mapping for the key; it's also possible that the map
-     * explicitly maps the key to <tt>null</tt>.  The <tt>containsKey</tt>
+     * explicitly maps the key to {@code null}.  The {@code containsKey}
      * operation may be used to distinguish these two cases.
      *
      * @return the value to which this map maps the specified key.
@@ -231,7 +231,7 @@
     }
 
     /**
-     * Rehashes the contents of this map into a new <tt>IdentityHashMap</tt> instance
+     * Rehashes the contents of this map into a new {@code IdentityHashMap} instance
      * with a larger capacity. This method is called automatically when the
      * number of keys in this map exceeds its capacity and load factor.
      */
@@ -265,10 +265,10 @@
      *
      * @param key key with which the specified value is to be associated.
      * @param value value to be associated with the specified key.
-     * @return previous value associated with specified key, or <tt>null</tt>
-     *	       if there was no mapping for key.  A <tt>null</tt> return can
+     * @return previous value associated with specified key, or {@code null}
+     *	       if there was no mapping for key.  A {@code null} return can
      *	       also indicate that the IdentityHashMap previously associated
-     *	       <tt>null</tt> with the specified key.
+     *	       {@code null} with the specified key.
      */
     @Override
     public Object put(Object key, Object value) {
@@ -317,9 +317,9 @@
      * Removes the mapping for this key from this map if present.
      *
      * @param key key whose mapping is to be removed from the map.
-     * @return previous value associated with specified key, or <tt>null</tt>
-     *	       if there was no mapping for key.  A <tt>null</tt> return can
-     *	       also indicate that the map previously associated <tt>null</tt>
+     * @return previous value associated with specified key, or {@code null}
+     *	       if there was no mapping for key.  A {@code null} return can
+     *	       also indicate that the map previously associated {@code null}
      *	       with the specified key.
      */
     @Override
@@ -398,7 +398,7 @@
     }
 
     /**
-     * Returns a shallow copy of this <tt>IdentityHashMap</tt> instance: the keys and
+     * Returns a shallow copy of this {@code IdentityHashMap} instance: the keys and
      * values themselves are not cloned.
      *
      * @return a shallow copy of this map.
@@ -433,10 +433,10 @@
      * Returns a set view of the keys contained in this map.  The set is
      * backed by the map, so changes to the map are reflected in the set, and
      * vice versa.  The set supports element removal, which removes the
-     * corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
-     * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
-     * <tt>clear</tt> operations.  It does not support the <tt>add</tt> or
-     * <tt>addAll</tt> operations.
+     * corresponding mapping from this map, via the {@code Iterator.remove},
+     * {@code Set.remove}, {@code removeAll}, {@code retainAll}, and
+     * {@code clear} operations.  It does not support the {@code add} or
+     * {@code addAll} operations.
      *
      * @return a set view of the keys contained in this map.
      */
@@ -477,9 +477,9 @@
      * collection is backed by the map, so changes to the map are reflected in
      * the collection, and vice versa.  The collection supports element
      * removal, which removes the corresponding mapping from this map, via the
-     * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
-     * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
-     * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
+     * {@code Iterator.remove}, {@code Collection.remove},
+     * {@code removeAll}, {@code retainAll}, and {@code clear} operations.
+     * It does not support the {@code add} or {@code addAll} operations.
      *
      * @return a collection view of the values contained in this map.
      */
@@ -511,13 +511,13 @@
 
     /**
      * Returns a collection view of the mappings contained in this map.  Each
-     * element in the returned collection is a <tt>Map.Entry</tt>.  The
+     * element in the returned collection is a {@code Map.Entry}.  The
      * collection is backed by the map, so changes to the map are reflected in
      * the collection, and vice versa.  The collection supports element
      * removal, which removes the corresponding mapping from the map, via the
-     * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
-     * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
-     * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
+     * {@code Iterator.remove}, {@code Collection.remove},
+     * {@code removeAll}, {@code retainAll}, and {@code clear} operations.
+     * It does not support the {@code add} or {@code addAll} operations.
      *
      * @return a collection view of the mappings contained in this map.
      * @see java.util.Map.Entry
@@ -781,7 +781,7 @@
     }
 
     /**
-     * Save the state of the <tt>IdentityHashMap</tt> instance to a stream (i.e.,
+     * Save the state of the {@code IdentityHashMap} instance to a stream (i.e.,
      * serialize it).
      *
      * @serialData The <i>capacity</i> of the IdentityHashMap (the length of the
@@ -815,7 +815,7 @@
     }
 
     /**
-     * Reconstitute the <tt>IdentityHashMap</tt> instance from a stream (i.e.,
+     * Reconstitute the {@code IdentityHashMap} instance from a stream (i.e.,
      * deserialize it).
      */
     private void readObject(java.io.ObjectInputStream s)
diff --git a/src/main/java/freemarker/ext/xml/NodeListModel.java b/src/main/java/freemarker/ext/xml/NodeListModel.java
index 30c792a..76ccf95 100644
--- a/src/main/java/freemarker/ext/xml/NodeListModel.java
+++ b/src/main/java/freemarker/ext/xml/NodeListModel.java
@@ -55,8 +55,8 @@
  * <p><strong>Implementation note:</strong> If you are using W3C DOM documents
  * built by the Crimson XML parser (or you are using the built-in JDK 1.4 XML
  * parser, which is essentially Crimson), make sure you call
- * <tt>setNamespaceAware(true)</tt> on the 
- * <tt>javax.xml.parsers.DocumentBuilderFactory</tt> instance used for document
+ * {@code setNamespaceAware(true)} on the 
+ * {@code javax.xml.parsers.DocumentBuilderFactory} instance used for document
  * building even when your documents don't use XML namespaces. Failing to do so,
  * you will experience incorrect behavior when using the documents wrapped with
  * this model.</p>
@@ -210,78 +210,79 @@
      * Returns a new NodeListModel containing the nodes that result from applying
      * an operator to this model's nodes.
      * @param key the operator to apply to nodes. Available operators are:
-     * <table style="width: auto; border-collapse: collapse" border="1" summary="XML node hash keys">
+     * <table style="width: auto; border-collapse: collapse" border="1">
+     *   <caption style="display: none">XML node hash keys</caption>
      *   <thead>
      *     <tr>
-     *       <th align="left">Key name</th>
-     *       <th align="left">Evaluates to</th>
+     *       <th>Key name</th>
+     *       <th>Evaluates to</th>
      *     </tr>  
      *   </thead>
      *   <tbody>
      *     <tr>
-     *       <td><tt>*</tt> or <tt>_children</tt></td>
+     *       <td>{@code *} or {@code _children}</td>
      *       <td>all direct element children of current nodes (non-recursive).
      *         Applicable to element and document nodes.</td>
      *     </tr>  
      *     <tr>
-     *       <td><tt>@*</tt> or <tt>_attributes</tt></td>
+     *       <td>{@code @*} or {@code _attributes}</td>
      *       <td>all attributes of current nodes. Applicable to elements only.
      *         </td>
      *     </tr>
      *     <tr>
-     *       <td><tt>@<i>attributeName</i></tt></td>
+     *       <td><code>@<i>attributeName</i></code></td>
      *       <td>named attributes of current nodes. Applicable to elements, 
      *         doctypes and processing instructions. On doctypes it supports 
-     *         attributes <tt>publicId</tt>, <tt>systemId</tt> and 
-     *         <tt>elementName</tt>. On processing instructions, it supports 
-     *         attributes <tt>target</tt> and <tt>data</tt>, as well as any 
+     *         attributes {@code publicId}, {@code systemId} and 
+     *         {@code elementName}. On processing instructions, it supports 
+     *         attributes {@code target} and {@code data}, as well as any 
      *         other attribute name specified in data as 
-     *         <tt>name=&quot;value&quot;</tt> pair on dom4j or JDOM models. 
+     *         <code>name=&quot;value&quot;</code> pair on dom4j or JDOM models. 
      *         The attribute nodes for doctype and processing instruction are 
      *         synthetic, and as such have no parent. Note, however that 
-     *         <tt>@*</tt> does NOT operate on doctypes or processing 
+     *         {@code @*} does NOT operate on doctypes or processing 
      *         instructions.</td>
      *     </tr>  
      * 
      *     <tr>
-     *       <td><tt>_ancestor</tt></td>
+     *       <td>{@code _ancestor}</td>
      *       <td>all ancestors up to root element (recursive) of current nodes.
-     *         Applicable to same node types as <tt>_parent</tt>.</td>
+     *         Applicable to same node types as {@code _parent}.</td>
      *     </tr>  
      *     <tr>
-     *       <td><tt>_ancestorOrSelf</tt></td>
+     *       <td>{@code _ancestorOrSelf}</td>
      *       <td>all ancestors of current nodes plus current nodes. Applicable 
-     *         to same node types as <tt>_parent</tt>.</td>
+     *         to same node types as {@code _parent}.</td>
      *     </tr>  
      *     <tr>
-     *       <td><tt>_content</tt></td>
+     *       <td>{@code _content}</td>
      *       <td>the complete content of current nodes, including children 
      *         elements, text, entity references, and processing instructions 
      *         (non-recursive). Applicable to elements and documents.</td>
      *     </tr>  
      *     <tr>
-     *       <td><tt>_descendant</tt></td>
+     *       <td>{@code _descendant}</td>
      *       <td>all recursive descendant element children of current nodes. 
      *         Applicable to document and element nodes.</td>
      *     </tr>  
      *     <tr>
-     *       <td><tt>_descendantOrSelf</tt></td>
+     *       <td>{@code _descendantOrSelf}</td>
      *       <td>all recursive descendant element children of current nodes 
      *         plus current nodes. Applicable to document and element nodes.
      *         </td>
      *     </tr>
      *     <tr>
-     *       <td><tt>_document</tt></td>
+     *       <td>{@code _document}</td>
      *       <td>all documents the current nodes belong to. Applicable to all 
      *       nodes except text.</td>
      *     </tr>
      *     <tr>
-     *       <td><tt>_doctype</tt></td>
+     *       <td>{@code _doctype}</td>
      *       <td>doctypes of the current nodes. Applicable to document nodes 
      *       only.</td>
      *     </tr>
      *     <tr>
-     *       <td><tt>_filterType</tt></td>
+     *       <td>{@code _filterType}</td>
      *       <td>is a filter-by-type template method model. When called, it 
      *         will yield a node list that contains only those current nodes 
      *         whose type matches one of types passed as argument. You can pass
@@ -294,7 +295,7 @@
      *         &quot;text&quot;.</td>
      *     </tr>
      *     <tr>
-     *       <td><tt>_name</tt></td>
+     *       <td>{@code _name}</td>
      *       <td>the names of current nodes, one string per node 
      *         (non-recursive). Applicable to elements and attributes 
      *         (returns their local names), entity references, processing 
@@ -302,34 +303,34 @@
      *         ID)</td>
      *     </tr>
      *     <tr>
-     *       <td><tt>_nsprefix</tt></td>
+     *       <td>{@code _nsprefix}</td>
      *       <td>the namespace prefixes of current nodes, one string per node 
      *         (non-recursive). Applicable to elements and attributes</td>
      *     </tr>
      *     <tr>
-     *       <td><tt>_nsuri</tt></td>
+     *       <td>{@code _nsuri}</td>
      *       <td>the namespace URIs of current nodes, one string per node 
      *       (non-recursive). Applicable to elements and attributes</td>
      *     </tr>
      *     <tr>
-     *       <td><tt>_parent</tt></td>
+     *       <td>{@code _parent}</td>
      *       <td>parent elements of current nodes. Applicable to element, 
      *       attribute, comment, entity, processing instruction.</td>
      *     </tr>
      *     <tr>
-     *       <td><tt>_qname</tt></td>
+     *       <td>{@code _qname}</td>
      *       <td>the qualified names of current nodes in 
-     *         <tt>[namespacePrefix:]localName</tt> form, one string per node 
+     *         {@code [namespacePrefix:]localName} form, one string per node 
      *         (non-recursive). Applicable to elements and attributes</td>
      *     </tr>
      *     <tr>
-     *       <td><tt>_registerNamespace(prefix, uri)</tt></td>
+     *       <td>{@code _registerNamespace(prefix, uri)}</td>
      *       <td>register a XML namespace with the specified prefix and URI for
      *         the current node list and all node lists that are derived from 
      *         the current node list. After registering, you can use the
-     *         <tt>nodelist[&quot;prefix:localname&quot;]</tt>, or
-     *         <tt>nodelist[&quot;@prefix:localname&quot;]</tt> syntax
-     *          (or <tt>nodelist.prefix\:localname</tt>, or <tt>nodelist.@prefix\:localname</tt>)
+     *         <code>nodelist[&quot;prefix:localname&quot;]</code>, or
+     *         <code>nodelist[&quot;@prefix:localname&quot;]</code> syntax
+     *          (or {@code nodelist.prefix\:localname}, or {@code nodelist.@prefix\:localname})
      *         to reach elements, and attributes whose names are namespace-scoped.
      *         Note that the namespace prefix need not match the actual prefix 
      *         used by the XML document itself since namespaces are compared 
@@ -339,7 +340,7 @@
      *         example, you certainly should have used {@code doc._registerNamespace(...)}.
      *     </tr>
      *     <tr>
-     *       <td><tt>_text</tt></td>
+     *       <td>{@code _text}</td>
      *       <td>the text of current nodes, one string per node 
      *         (non-recursive). Applicable to elements, attributes, comments, 
      *         processing instructions (returns its data) and CDATA sections. 
@@ -347,7 +348,7 @@
      *         escaped.</td>
      *     </tr>
      *     <tr>
-     *       <td><tt>_type</tt></td>
+     *       <td>{@code _type}</td>
      *       <td>Returns a string describing the type of nodes, one
      *         string per node. The returned values are &quot;attribute&quot;,
      *         &quot;cdata&quot;, &quot;comment&quot;, &quot;document&quot;,
@@ -357,23 +358,23 @@
      *         &quot;text&quot;, or &quot;unknown&quot;.</td>
      *     </tr>
      *     <tr>
-     *       <td><tt>_unique</tt></td>
+     *       <td>{@code _unique}</td>
      *       <td>a copy of the current nodes that keeps only the first 
      *         occurrence of every node, eliminating duplicates. Duplicates can
      *         occur in the node list by applying uptree-traversals 
-     *         <tt>_parent</tt>, <tt>_ancestor</tt>, <tt>_ancestorOrSelf</tt>,
-     *         and <tt>_document</tt> on a node list with multiple elements. 
-     *         I.e. <tt>foo._children._parent</tt> will return a node list that
+     *         {@code _parent}, {@code _ancestor}, {@code _ancestorOrSelf},
+     *         and {@code _document} on a node list with multiple elements. 
+     *         I.e. {@code foo._children._parent} will return a node list that
      *         has duplicates of nodes in foo - each node will have the number 
      *         of occurrences equal to the number of its children. In these 
-     *         cases, use <tt>foo._children._parent._unique</tt> to eliminate 
+     *         cases, use {@code foo._children._parent._unique} to eliminate 
      *         duplicates. Applicable to all node types.</td>
      *     </tr>
      *     <tr>
      *       <td>any other key</td>
      *       <td>element children of current nodes with name matching the key. 
      *       This allows for convenience child traversal in 
-     *       <tt>book.chapter.title</tt> style syntax. Applicable to document 
+     *       {@code book.chapter.title} style syntax. Applicable to document 
      *       and element nodes.</td>
      *     </tr>
      *   </tbody>
diff --git a/src/main/java/freemarker/log/Logger.java b/src/main/java/freemarker/log/Logger.java
index be7f965..e0aceb8 100644
--- a/src/main/java/freemarker/log/Logger.java
+++ b/src/main/java/freemarker/log/Logger.java
@@ -27,7 +27,7 @@
 
 /**
  * Delegates logger creation to an actual logging library. By default it looks for logger libraries in this order (in
- * FreeMarker 2.3.x): Log4J, Avalon LogKit, JUL (i.e., <tt>java.util.logging</tt>). Prior to FreeMarker 2.4, SLF4J and
+ * FreeMarker 2.3.x): Log4J, Avalon LogKit, JUL (i.e., {@code java.util.logging}). Prior to FreeMarker 2.4, SLF4J and
  * Apache Commons Logging aren't searched automatically due to backward compatibility constraints. But if you have
  * {@code log4j-over-slf4j} properly installed (means, you have no real Log4j in your class path, and SLF4J has a
  * backing implementation like {@code logback-classic}), then FreeMarker will use SLF4J directly instead of Log4j (since
@@ -214,7 +214,7 @@
      * subsystem, the change in this value will have no effect on them.
      * 
      * @param libraryEnum
-     *            One of <tt>LIBRARY_...</tt> constants. By default, {@link #LIBRARY_AUTO} is used.
+     *            One of {@code LIBRARY_...} constants. By default, {@link #LIBRARY_AUTO} is used.
      * 
      * @throws ClassNotFoundException
      *             if an explicit logging library is asked for (that is, not {@value #LIBRARY_AUTO} or
diff --git a/src/main/java/freemarker/log/SLF4JLoggerFactory.java b/src/main/java/freemarker/log/SLF4JLoggerFactory.java
index f18b054..fa87790 100644
--- a/src/main/java/freemarker/log/SLF4JLoggerFactory.java
+++ b/src/main/java/freemarker/log/SLF4JLoggerFactory.java
@@ -40,7 +40,7 @@
 
     /**
      * Logger where the log entry issuer (class, method) will be correctly
-     * shown to be the caller of <tt>LocationAwareSLF4JLogger</tt> methods.
+     * shown to be the caller of {@code LocationAwareSLF4JLogger} methods.
      */
     private static final class LocationAwareSLF4JLogger extends Logger {
             
diff --git a/src/main/java/freemarker/template/AdapterTemplateModel.java b/src/main/java/freemarker/template/AdapterTemplateModel.java
index 77eec24..bb4c437 100644
--- a/src/main/java/freemarker/template/AdapterTemplateModel.java
+++ b/src/main/java/freemarker/template/AdapterTemplateModel.java
@@ -37,8 +37,8 @@
      * class, but if that is impossible, it must at least return the underlying 
      * object as-is. As a minimal requirement, an implementation must always 
      * return the exact underlying object when 
-     * <tt>hint.isInstance(underlyingObject)</tt> holds. When called 
-     * with <tt>java.lang.Object.class</tt>, it should return a generic Java 
+     * {@code hint.isInstance(underlyingObject)} holds. When called 
+     * with {@code java.lang.Object.class}, it should return a generic Java 
      * object (i.e. if the model is wrapping a scripting language object that is
      * further wrapping a Java object, the deepest underlying Java object should
      * be returned). 
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 7d4933c..8f2af85 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -24,6 +24,7 @@
 import java.io.Writer;
 import java.lang.reflect.InvocationTargetException;
 import java.net.URLConnection;
+import java.text.Collator;
 import java.text.DecimalFormat;
 import java.text.SimpleDateFormat;
 import java.util.Collection;
@@ -436,7 +437,16 @@
     public static final int ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY = 21;
     /** Enable auto-escaping if the {@link OutputFormat} supports it. */
     public static final int ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY = 22;
-    
+    /**
+     * This policy is to always require auto-escaping, to avoid accidents where because of misconfiguration, or a
+     * mistake of the template author it's disabled. With this policy, using output formats that don't support escaping
+     * will not be allowed. Using built-ins, and directives that disable auto-escaping (like {@code ?no_esc}) will also
+     * be errors (on parse-time). Note that if markup (like HTML) comers from the data model, then with this policy you
+     * will have to ensure that they come as {@link TemplateMarkupOutputModel}-s (which won't be auto-escaped even with
+     * this policy), not as {@link String}-s, because the template authors can't disable escaping for the value anymore.
+     */
+    public static final int FORCE_AUTO_ESCAPING_POLICY = 23;
+
     /** FreeMarker version 2.3.0 (an {@link #Configuration(Version) incompatible improvements break-point}) */
     public static final Version VERSION_2_3_0 = new Version(2, 3, 0);
     
@@ -649,8 +659,8 @@
      *   <li><p>
      *     2.3.20 (or higher): {@code ?html} will escape apostrophe-quotes just like {@code ?xhtml} does. Utilizing
      *     this is highly recommended, because otherwise if interpolations are used inside attribute values that use
-     *     apostrophe-quotation (<tt>&lt;foo bar='${val}'&gt;</tt>) instead of plain quotation mark
-     *     (<tt>&lt;foo bar="${val}"&gt;</tt>), they might produce HTML/XML that's not well-formed. Note that
+     *     apostrophe-quotation (<code>&lt;foo bar='${val}'&gt;</code>) instead of plain quotation mark
+     *     (<code>&lt;foo bar="${val}"&gt;</code>), they might produce HTML/XML that's not well-formed. Note that
      *     {@code ?html} didn't do this because long ago there was no cross-browser way of doing this, but it's not a
      *     concern anymore.
      *   </li>
@@ -801,7 +811,7 @@
      *               {@code <param-value>[ WEB-INF/templates, classpath:com/example/myapp/templates ]</param-value>}
      *             </li>
      *             <li><p>
-     *               Initial <tt>"{"</tt> in the {@code TemplatePath} init-param is reserved for future purposes, and
+     *               Initial <code>"{"</code> in the {@code TemplatePath} init-param is reserved for future purposes, and
      *               thus will throw exception.
      *             </li>
      *          </ul>
@@ -976,6 +986,21 @@
      *       </li>
      *     </ul>
      *   </li>
+     *   <li>
+     *       <p>
+     *       2.3.33 (or higher):
+     *       <ul>
+     *           <li><p>Comparing strings is now way faster. If your template does lot of string comparisons, this can
+     *           mean very significant speedup. We now use a simpler way of comparing strings, and because templates
+     *           were only ever allowed equality comparisons between strings (not less-than, or greater-than), it's very
+     *           unlikely to change the behavior of your templates. (Technically, what changes is that instead of using
+     *           Java's localized {@link Collator}-s, we switch to a simple binary comparison after UNICODE NFKC
+     *           normalization. So, in theory it's possible that for some locales two different but similarly looking
+     *           characters were treated as equal by the collator, but will count as different now. But it's very
+     *           unlikely that anyone wanted to depend on such fragile logic anyway. Note again that we still do UNICODE
+     *           normalization, so combining characters won't break your comparison.)</p></li>
+     *       </ul>
+     *   </li>
      * </ul>
      * 
      * @throws IllegalArgumentException
@@ -1178,7 +1203,8 @@
      * The previous content of the encoding map will be lost.
      * This default map currently contains the following mappings:
      * 
-     * <table style="width: auto; border-collapse: collapse" border="1" summary="preset language to encoding mapping">
+     * <table style="width: auto; border-collapse: collapse" border="1">
+     *   <caption style="display: none">Preset language to encoding mapping</caption>
      *   <tr><td>ar</td><td>ISO-8859-6</td></tr>
      *   <tr><td>be</td><td>ISO-8859-5</td></tr>
      *   <tr><td>bg</td><td>ISO-8859-5</td></tr>
@@ -2157,7 +2183,8 @@
      * 
      * @param autoEscapingPolicy
      *          One of the {@link #ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY},
-     *          {@link #ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY}, and {@link #DISABLE_AUTO_ESCAPING_POLICY} constants.  
+     *          {@link #ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY}, {@link #DISABLE_AUTO_ESCAPING_POLICY}, and
+     *          {@link #FORCE_AUTO_ESCAPING_POLICY} constants.  
      * 
      * @see TemplateConfiguration#setAutoEscapingPolicy(int)
      * @see Configuration#setOutputFormat(OutputFormat)
@@ -2532,8 +2559,8 @@
      * <p>The {@code tagSyntax} parameter must be one of:
      * <ul>
      *   <li>{@link Configuration#AUTO_DETECT_TAG_SYNTAX}:
-     *     Use the syntax of the first FreeMarker tag (can be anything, like <tt>#list</tt>,
-     *     <tt>#include</tt>, user defined, etc.)
+     *     Use the syntax of the first FreeMarker tag (can be anything, like {@code #list},
+     *     {@code #include}, user defined, etc.)
      *   <li>{@link Configuration#ANGLE_BRACKET_TAG_SYNTAX}:
      *     Use the angle bracket tag syntax (the normal syntax), like {@code <#include ...>}
      *   <li>{@link Configuration#SQUARE_BRACKET_TAG_SYNTAX}:
@@ -2990,7 +3017,7 @@
      * {@code "UTF-8"} is usually a good choice.
      * 
      * <p>Note that individual templates may specify their own charset by starting with
-     * <tt>&lt;#ftl encoding="..."&gt;</tt>
+     * <code>&lt;#ftl encoding="..."&gt;</code>
      * 
      * @param encoding The name of the charset, such as {@code "UTF-8"} or {@code "ISO-8859-1"}
      */
@@ -3091,7 +3118,7 @@
      * configuration, if the data model does not contain a
      * variable with the same name.
      *
-     * <p>Never use <tt>TemplateModel</tt> implementation that is not thread-safe for shared sharedVariables,
+     * <p>Never use {@code TemplateModel} implementation that is not thread-safe for shared sharedVariables,
      * if the configuration is used by multiple threads! It is the typical situation for Servlet based Web sites.
      * 
      * <p>This method is <b>not</b> thread safe; use it with the same restrictions as those that modify setting values. 
@@ -3198,7 +3225,7 @@
      * {@link #setSharedVariable(String, Object)} calls, one for each hash entry. It doesn't remove the already added
      * shared variable before doing this.
      *
-     * <p>Never use <tt>TemplateModel</tt> implementation that is not thread-safe for shared shared variable values,
+     * <p>Never use {@code TemplateModel} implementation that is not thread-safe for shared shared variable values,
      * if the configuration is used by multiple threads! It is the typical situation for Servlet based Web sites.
      *
      * <p>This method is <b>not</b> thread safe; use it with the same restrictions as those that modify setting values. 
@@ -3388,6 +3415,8 @@
                     setAutoEscapingPolicy(ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY);
                 } else if ("enable_if_supported".equals(value) || "enableIfSupported".equals(value)) {
                     setAutoEscapingPolicy(ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY);
+                } else if ("force".equals(value)) {
+                    setAutoEscapingPolicy(FORCE_AUTO_ESCAPING_POLICY);
                 } else if ("disable".equals(value)) {
                     setAutoEscapingPolicy(DISABLE_AUTO_ESCAPING_POLICY);
                 } else {
@@ -3693,7 +3722,7 @@
      *       when micro was 0 the version strings was like major.minor instead of the proper major.minor.0, but that's
      *       not like that anymore.)
      *   <li>When only the micro version is increased, compatibility with previous versions with the same
-     *       major.minor is kept. Thus <tt>freemarker.jar</tt> can be replaced in an existing application without
+     *       major.minor is kept. Thus {@code freemarker.jar} can be replaced in an existing application without
      *       breaking it.</li>
      *   <li>For non-final/unstable versions (that almost nobody uses), the format is:
      *       <ul>
@@ -3767,7 +3796,7 @@
 
     /**
      * Returns the names of the directives that are predefined by FreeMarker. These are the things that you call like
-     * <tt>&lt;#directiveName ...&gt;</tt>.
+     * <code>&lt;#directiveName ...&gt;</code>.
      * 
      * @param namingConvention
      *            One of {@link #AUTO_DETECT_NAMING_CONVENTION}, {@link #LEGACY_NAMING_CONVENTION}, and
diff --git a/src/main/java/freemarker/template/SimpleCollection.java b/src/main/java/freemarker/template/SimpleCollection.java
index ec19581..194f877 100644
--- a/src/main/java/freemarker/template/SimpleCollection.java
+++ b/src/main/java/freemarker/template/SimpleCollection.java
@@ -25,15 +25,15 @@
 
 /**
  * A simple implementation of {@link TemplateCollectionModel}.
- * It's able to wrap <tt>java.util.Iterator</tt>-s and <tt>java.util.Collection</tt>-s.
- * If you wrap an <tt>Iterator</tt>, the variable can be &lt;#list&gt;-ed only once!
+ * It's able to wrap {@code java.util.Iterator}-s and {@code java.util.Collection}-s.
+ * If you wrap an {@code Iterator}, the variable can be &lt;#list&gt;-ed only once!
  *
- * <p>Consider using {@link SimpleSequence} instead of this class if you want to wrap <tt>Iterator</tt>s.
- * <tt>SimpleSequence</tt> will read all elements of the <tt>Iterator</tt>, and store them in a <tt>List</tt>
+ * <p>Consider using {@link SimpleSequence} instead of this class if you want to wrap {@code Iterator}s.
+ * {@code SimpleSequence} will read all elements of the {@code Iterator}, and store them in a {@code List}
  * (this may cause too high resource consumption in some applications), so you can list the variable
- * for unlimited times. Also, if you want to wrap <tt>Collection</tt>s, and then list the resulting
- * variable for many times, <tt>SimpleSequence</tt> may gives better performance, as the
- * wrapping of non-<tt>TemplateModel</tt> objects happens only once.
+ * for unlimited times. Also, if you want to wrap {@code Collection}s, and then list the resulting
+ * variable for many times, {@code SimpleSequence} may gives better performance, as the
+ * wrapping of non-{@code TemplateModel} objects happens only once.
  *
  * <p>This class is thread-safe. The returned {@link TemplateModelIterator}-s
  * are <em>not</em> thread-safe.
@@ -98,10 +98,10 @@
     /**
      * Retrieves a template model iterator that is used to iterate over the elements in this collection.
      *  
-     * <p>When you wrap an <tt>Iterator</tt> and you get <tt>TemplateModelIterator</tt> for multiple times,
-     * only on of the returned <tt>TemplateModelIterator</tt> instances can be really used. When you have called a
-     * method of a <tt>TemplateModelIterator</tt> instance, all other instance will throw a
-     * <tt>TemplateModelException</tt> when you try to call their methods, since the wrapped <tt>Iterator</tt>
+     * <p>When you wrap an {@code Iterator} and you get {@code TemplateModelIterator} for multiple times,
+     * only on of the returned {@code TemplateModelIterator} instances can be really used. When you have called a
+     * method of a {@code TemplateModelIterator} instance, all other instance will throw a
+     * {@code TemplateModelException} when you try to call their methods, since the wrapped {@code Iterator}
      * can't return the first element anymore.
      */
     @Override
diff --git a/src/main/java/freemarker/template/SimpleDate.java b/src/main/java/freemarker/template/SimpleDate.java
index b92b335..c605a80 100644
--- a/src/main/java/freemarker/template/SimpleDate.java
+++ b/src/main/java/freemarker/template/SimpleDate.java
@@ -20,7 +20,7 @@
 package freemarker.template;
 
 /**
- * A simple implementation of the <tt>TemplateDateModel</tt>
+ * A simple implementation of the {@code TemplateDateModel}
  * interface. Note that this class is immutable.
  * <p>This class is thread-safe.
  */
diff --git a/src/main/java/freemarker/template/SimpleHash.java b/src/main/java/freemarker/template/SimpleHash.java
index ff5510e..396d760 100644
--- a/src/main/java/freemarker/template/SimpleHash.java
+++ b/src/main/java/freemarker/template/SimpleHash.java
@@ -201,7 +201,7 @@
     /**
      * Puts a boolean in the map
      *
-     * @param key the name by which the resulting <tt>TemplateModel</tt>
+     * @param key the name by which the resulting {@code TemplateModel}
      * is identified in the template.
      * @param b the boolean to store.
      */
diff --git a/src/main/java/freemarker/template/SimpleNumber.java b/src/main/java/freemarker/template/SimpleNumber.java
index 5de9cc7..6a047dd 100644
--- a/src/main/java/freemarker/template/SimpleNumber.java
+++ b/src/main/java/freemarker/template/SimpleNumber.java
@@ -23,7 +23,7 @@
 
 
 /**
- * A simple implementation of the <tt>TemplateNumberModel</tt>
+ * A simple implementation of the {@code TemplateNumberModel}
  * interface. Note that this class is immutable.
  *
  * <p>This class is thread-safe.
@@ -31,7 +31,7 @@
 public final class SimpleNumber implements TemplateNumberModel, Serializable {
 
     /**
-     * @serial the value of this <tt>SimpleNumber</tt> 
+     * @serial the value of this {@code SimpleNumber} 
      */
     private final Number value;
 
diff --git a/src/main/java/freemarker/template/SimpleScalar.java b/src/main/java/freemarker/template/SimpleScalar.java
index a5f31ad..ae0d80e 100644
--- a/src/main/java/freemarker/template/SimpleScalar.java
+++ b/src/main/java/freemarker/template/SimpleScalar.java
@@ -22,8 +22,8 @@
 import java.io.Serializable;
 
 /**
- * A simple implementation of the <tt>TemplateScalarModel</tt>
- * interface, using a <tt>String</tt>.
+ * A simple implementation of the {@code TemplateScalarModel}
+ * interface, using a {@code String}.
  * As of version 2.0 this object is immutable.
  *
  * <p>This class is thread-safe.
@@ -35,13 +35,13 @@
 implements TemplateScalarModel, Serializable {
     
     /**
-     * @serial the value of this <tt>SimpleScalar</tt> if it wraps a
-     * <tt>String</tt>.
+     * @serial the value of this {@code SimpleScalar} if it wraps a
+     * {@code String}.
      */
     private final String value;
 
     /**
-     * Constructs a <tt>SimpleScalar</tt> containing a string value.
+     * Constructs a {@code SimpleScalar} containing a string value.
      * @param value the string value. If this is {@code null}, its value in FTL will be {@code ""}.
      */
     public SimpleScalar(String value) {
diff --git a/src/main/java/freemarker/template/Template.java b/src/main/java/freemarker/template/Template.java
index 06e9cb1..967d222 100644
--- a/src/main/java/freemarker/template/Template.java
+++ b/src/main/java/freemarker/template/Template.java
@@ -383,7 +383,7 @@
 
     /**
      * Like {@link #process(Object, Writer)}, but also sets a (XML-)node to be recursively processed by the template.
-     * That node is accessed in the template with <tt>.node</tt>, <tt>#recurse</tt>, etc. See the
+     * That node is accessed in the template with {@code .node}, {@code #recurse}, etc. See the
      * <a href="https://freemarker.apache.org/docs/xgui_declarative.html" target="_blank">Declarative XML Processing</a> as a
      * typical example of recursive node processing.
      * 
@@ -413,11 +413,12 @@
     }
     
    /**
-    * Creates a {@link freemarker.core.Environment Environment} object, using this template, the data-model provided as
-    * parameter. You have to call {@link Environment#process()} on the return value to set off the actual rendering.
+    * Creates a {@link freemarker.core.Environment Environment} object, with this template as the main (top-level
+    * template), and the data-model provided as parameter. You have to call {@link Environment#process()} on the return
+    * value to start actual template processing (that is, to run the template, to generate output).
     * 
     * <p>Use this method if you want to do some special initialization on the {@link Environment} before template
-    * processing, or if you want to read the {@link Environment} after template processing. Otherwise using
+    * processing, or if you want to read the {@link Environment} after template processing. Otherwise, using
     * {@link Template#process(Object, Writer)} is simpler.
     *
     * <p>Example:
@@ -431,7 +432,7 @@
     * <pre>
     * myTemplate.process(root, out);</pre>
     * 
-    * <p>But with <tt>createProcessingEnvironment</tt>, you can manipulate the environment
+    * <p>But with {@code createProcessingEnvironment}, you can manipulate the environment
     * before and after the processing:
     * 
     * <pre>
@@ -445,12 +446,12 @@
     * TemplateModel x = env.getVariable("x");  // read back a variable set by the template</pre>
     *
     * @param dataModel the holder of the variables visible from all templates; see {@link #process(Object, Writer)} for
-    *     more details.
+    *     more details. If {@code null}, the data model will be empty.
     * @param wrapper The {@link ObjectWrapper} to use to wrap objects into {@link TemplateModel}
     *     instances. Normally you left it {@code null}, in which case {@link Configurable#getObjectWrapper()} will be
     *     used.
     * @param out The {@link Writer} where the output of the template will go; see {@link #process(Object, Writer)} for
-    *     more details.
+    *     more details. Can't be {@code null}.
     *     
     * @return the {@link Environment} object created for processing. Call {@link Environment#process()} to process the
     *    template.
diff --git a/src/main/java/freemarker/template/TemplateHashModel.java b/src/main/java/freemarker/template/TemplateHashModel.java
index fa176f1..fb83bc4 100644
--- a/src/main/java/freemarker/template/TemplateHashModel.java
+++ b/src/main/java/freemarker/template/TemplateHashModel.java
@@ -28,11 +28,11 @@
 public interface TemplateHashModel extends TemplateModel {
     
     /**
-     * Gets a <tt>TemplateModel</tt> from the hash.
+     * Gets a {@code TemplateModel} from the hash.
      *
-     * @param key the name by which the <tt>TemplateModel</tt>
+     * @param key the name by which the {@code TemplateModel}
      * is identified in the template.
-     * @return the <tt>TemplateModel</tt> referred to by the key,
+     * @return the {@code TemplateModel} referred to by the key,
      * or null if not found.
      */
     TemplateModel get(String key) throws TemplateModelException;
diff --git a/src/main/java/freemarker/template/TemplateMethodModel.java b/src/main/java/freemarker/template/TemplateMethodModel.java
index 5250304..27e1056 100644
--- a/src/main/java/freemarker/template/TemplateMethodModel.java
+++ b/src/main/java/freemarker/template/TemplateMethodModel.java
@@ -48,7 +48,7 @@
      * objects instead of on their string representations, implement the 
      * {@link TemplateMethodModelEx} instead.
      * 
-     * @param arguments a <tt>List</tt> of <tt>String</tt> objects
+     * @param arguments a {@code List} of {@code String} objects
      *     containing the values of the arguments passed to the method.
      *  
      * @return the return value of the method, or {@code null}. If the returned value
diff --git a/src/main/java/freemarker/template/TemplateModelException.java b/src/main/java/freemarker/template/TemplateModelException.java
index df1efd9..0ace383 100644
--- a/src/main/java/freemarker/template/TemplateModelException.java
+++ b/src/main/java/freemarker/template/TemplateModelException.java
@@ -31,7 +31,7 @@
     private final boolean replaceWithCause;
 
     /**
-     * Constructs a <tt>TemplateModelException</tt> with no
+     * Constructs a {@code TemplateModelException} with no
      * specified detail message.
      */
     public TemplateModelException() {
@@ -39,7 +39,7 @@
     }
 
     /**
-     * Constructs a <tt>TemplateModelException</tt> with the
+     * Constructs a {@code TemplateModelException} with the
      * specified detail message.
      *
      * @param description the detail message.
@@ -57,7 +57,7 @@
     }
 
     /**
-     * Constructs a <tt>TemplateModelException</tt> with the given underlying
+     * Constructs a {@code TemplateModelException} with the given underlying
      * Exception, but no detail message.
      *
      * @param cause the underlying {@link Exception} that caused this
diff --git a/src/main/java/freemarker/template/TransformControl.java b/src/main/java/freemarker/template/TransformControl.java
index 562be9e..a5dd6e8 100644
--- a/src/main/java/freemarker/template/TransformControl.java
+++ b/src/main/java/freemarker/template/TransformControl.java
@@ -61,9 +61,9 @@
      * Called before the body is evaluated for the first time.
      * @return 
      * <ul>
-     * <li><tt>SKIP_BODY</tt> if the transform wants to ignore the body. In this
+     * <li>{@code SKIP_BODY} if the transform wants to ignore the body. In this
      * case, only {@link java.io.Writer#close()} is called next and processing ends.</li>
-     * <li><tt>EVALUATE_BODY</tt> to normally evaluate the body of the transform
+     * <li>{@code EVALUATE_BODY} to normally evaluate the body of the transform
      * and feed it to the writer</li>
      * </ul>
      */
@@ -73,8 +73,8 @@
      * Called after the body has been evaluated.
      * @return
      * <ul>
-     * <li><tt>END_EVALUATION</tt> if the transformation should be ended.</li>
-     * <li><tt>REPEAT_EVALUATION</tt> to have the engine re-evaluate the 
+     * <li>{@code END_EVALUATION} if the transformation should be ended.</li>
+     * <li>{@code REPEAT_EVALUATION} to have the engine re-evaluate the 
      * transform body and feed it again to the writer.</li>
      * </ul>
      */
diff --git a/src/main/java/freemarker/template/_TemplateAPI.java b/src/main/java/freemarker/template/_TemplateAPI.java
index 8fa3250..d1f61fb 100644
--- a/src/main/java/freemarker/template/_TemplateAPI.java
+++ b/src/main/java/freemarker/template/_TemplateAPI.java
@@ -140,10 +140,12 @@
     public static void validateAutoEscapingPolicyValue(int autoEscaping) {
         if (autoEscaping != Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY
                 && autoEscaping != Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY
+                && autoEscaping != Configuration.FORCE_AUTO_ESCAPING_POLICY
                 && autoEscaping != Configuration.DISABLE_AUTO_ESCAPING_POLICY) {
             throw new IllegalArgumentException("\"auto_escaping\" can only be set to one of these: "
                     + "Configuration.ENABLE_AUTO_ESCAPING_IF_DEFAULT, "
                     + "or Configuration.ENABLE_AUTO_ESCAPING_IF_SUPPORTED"
+                    + "or Configuration.FORCE_AUTO_ESCAPING_POLICY"
                     + "or Configuration.DISABLE_AUTO_ESCAPING");
         }
     }
diff --git a/src/main/java/freemarker/template/utility/DeepUnwrap.java b/src/main/java/freemarker/template/utility/DeepUnwrap.java
index 090aa6c..b0a200b 100644
--- a/src/main/java/freemarker/template/utility/DeepUnwrap.java
+++ b/src/main/java/freemarker/template/utility/DeepUnwrap.java
@@ -51,7 +51,7 @@
      * The converting of the {@link TemplateModel} object happens with the following rules:
      * <ol>
      *   <li>If the object implements {@link AdapterTemplateModel}, then the result
-     *       of {@link AdapterTemplateModel#getAdaptedObject(Class)} for <tt>Object.class</tt> is returned.
+     *       of {@link AdapterTemplateModel#getAdaptedObject(Class)} for {@code Object.class} is returned.
      *   <li>If the object implements {@link WrapperTemplateModel}, then the result
      *       of {@link WrapperTemplateModel#getWrappedObject()} is returned.
      *   <li>If the object is identical to the null model of the current object 
diff --git a/src/main/java/freemarker/template/utility/Execute.java b/src/main/java/freemarker/template/utility/Execute.java
index d649b45..aa7fb1c 100644
--- a/src/main/java/freemarker/template/utility/Execute.java
+++ b/src/main/java/freemarker/template/utility/Execute.java
@@ -65,9 +65,9 @@
     /**
      * Executes a method call.
      *
-     * @param arguments a <tt>List</tt> of <tt>String</tt> objects containing the values
+     * @param arguments a {@code List} of {@code String} objects containing the values
      * of the arguments passed to the method.
-     * @return the <tt>TemplateModel</tt> produced by the method, or null.
+     * @return the {@code TemplateModel} produced by the method, or null.
      */
     @Override
     public Object exec (List arguments) throws TemplateModelException {
diff --git a/src/main/java/freemarker/template/utility/StringUtil.java b/src/main/java/freemarker/template/utility/StringUtil.java
index 6d5f3fa..75ed0aa 100644
--- a/src/main/java/freemarker/template/utility/StringUtil.java
+++ b/src/main/java/freemarker/template/utility/StringUtil.java
@@ -252,8 +252,8 @@
     }
 
     /**
-     *  XML encoding for attribute values quoted with <tt>"</tt> (not with <tt>'</tt>!).
-     *  Also can be used for HTML attributes that are quoted with <tt>"</tt>.
+     *  XML encoding for attribute values quoted with {@code "} (not with {@code '}!).
+     *  Also can be used for HTML attributes that are quoted with {@code "}.
      *  @see #XMLEnc(String)
      */
     public static String XMLEncQAttr(String s) {
@@ -1268,7 +1268,7 @@
      *
      * <p>All characters under UCS code point 0x20 will be escaped.
      * Where they have no dedicated escape sequence in Java, they will
-     * be replaced with hexadecimal escape (<tt>\</tt><tt>u<i>XXXX</i></tt>). 
+     * be replaced with hexadecimal escape ({@code \}<code>u<i>XXXX</i></code>). 
      * 
      * @see #jQuote(String)
      */ 
@@ -1375,44 +1375,45 @@
      * touching pieces that were escaped with this, no character sequence can occur that closes the
      * JavaScript/JSON string literal, or has a meaning in HTML/XML that causes the HTML script section to be closed.
      * (If, however, the escaped section is preceded by or followed by strings from other sources, this can't be
-     * guaranteed in some rare cases. Like <tt>x = "&lt;/${a?js_string}"</tt> might closes the "script"
+     * guaranteed in some rare cases. Like <code>x = "&lt;/${a?js_string}"</code> might closes the "script"
      * element if {@code a} is {@code "script>"}.)
      *
      * The escaped characters are:
      *
-     * <table style="width: auto; border-collapse: collapse" border="1" summary="Characters escaped by jsStringEnc">
+     * <table style="width: auto; border-collapse: collapse" border="1">
+     * <caption style="display: none">Characters escaped by jsStringEnc</caption>
      * <tr>
      *   <th>Input
      *   <th>Output
      * <tr>
-     *   <td><tt>"</tt>
-     *   <td><tt>\"</tt>
+     *   <td>{@code "}
+     *   <td>{@code \"}
      * <tr>
-     *   <td><tt>'</tt> if not in JSON-mode, nor is the {@code quited} argument {@code true}
-     *   <td><tt>\'</tt>
+     *   <td>{@code '} if not in JSON-mode, nor is the {@code quited} argument {@code true}
+     *   <td>{@code \'}
      * <tr>
-     *   <td><tt>\</tt>
-     *   <td><tt>\\</tt>
+     *   <td>{@code \}
+     *   <td>{@code \\}
      * <tr>
-     *   <td><tt>/</tt> if the method can't know that it won't be directly after <tt>&lt;</tt>
-     *   <td><tt>\/</tt>
+     *   <td>{@code /} if the method can't know that it won't be directly after <code>&lt;</code>
+     *   <td>{@code \/}
      * <tr>
-     *   <td><tt>&gt;</tt> if the method can't know that it won't be directly after <tt>]]</tt> or <tt>--</tt>
-     *   <td>JavaScript: <tt>\&gt;</tt>; JSON: <tt>\</tt><tt>u003E</tt>
+     *   <td><code>&gt;</code> if the method can't know that it won't be directly after {@code ]]} or {@code --}
+     *   <td>JavaScript: <code>\&gt;</code>; JSON: {@code \}{@code u003E}
      * <tr>
-     *   <td><tt>&lt;</tt> if the method can't know that it won't be directly followed by <tt>!</tt> or <tt>?</tt>
-     *   <td><tt><tt>\</tt>u003C</tt>
+     *   <td><code>&lt;</code> if the method can't know that it won't be directly followed by {@code !} or {@code ?}
+     *   <td><code>\{@code u}003C</code>
      * <tr>
      *   <td>
      *     u0000-u001f (UNICODE control characters - disallowed by JSON)<br>
      *     u007f-u009f (UNICODE control characters - disallowed by JSON)
-     *   <td><tt>\n</tt>, <tt>\r</tt> and such, or if there's no such dedicated escape:
-     *       JavaScript: <tt>\x<i>XX</i></tt>, JSON: <tt>\<tt>u</tt><i>XXXX</i></tt>
+     *   <td>{@code \n}, {@code \r} and such, or if there's no such dedicated escape:
+     *       JavaScript: <code>\x<i>XX</i></code>, JSON: <code>\{@code u}<i>XXXX</i></code>
      * <tr>
      *   <td>
      *     u2028 (Line separator - source code line-break in ECMAScript)<br>
      *     u2029 (Paragraph separator - source code line-break in ECMAScript)<br>
-     *   <td><tt>\<tt>u</tt><i>XXXX</i></tt>
+     *   <td><code>\{@code u}<i>XXXX</i></code>
      * </table>
      *
      * @param s The string to escape
@@ -1555,7 +1556,7 @@
     /**
      * Parses a name-value pair list, where the pairs are separated with comma,
      * and the name and value is separated with colon.
-     * The keys and values can contain only letters, digits and <tt>_</tt>. They
+     * The keys and values can contain only letters, digits and {@code _}. They
      * can't be quoted. White-space around the keys and values are ignored. The
      * value can be omitted if <code>defaultValue</code> is not null. When a
      * value is omitted, then the colon after the key must be omitted as well.
diff --git a/src/main/java/freemarker/template/utility/XmlEscape.java b/src/main/java/freemarker/template/utility/XmlEscape.java
index ac6fb71..c54c4a1 100644
--- a/src/main/java/freemarker/template/utility/XmlEscape.java
+++ b/src/main/java/freemarker/template/utility/XmlEscape.java
@@ -27,10 +27,10 @@
 
 /**
  * Performs an XML escaping of a given template fragment. Specifically,
- * <tt>&lt;</tt> <tt>&gt;</tt> <tt>&quot;</tt> <tt>'</tt> and <tt>&amp;</tt> are all turned into entity references.
+ * <code>&lt;</code> <code>&gt;</code> <code>&quot;</code> {@code '} and <code>&amp;</code> are all turned into entity references.
  *
  * <p>An instance of this transform is initially visible as shared
- * variable called <tt>xml_escape</tt>.</p>
+ * variable called {@code xml_escape}.</p>
  */
 public class XmlEscape implements TemplateTransformModel {
 
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index 1ecfa4d..f439086 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -281,17 +281,17 @@
             _TemplateAPI.setOutputFormat(template, outputFormat);
         }
     }
-    
+
     void setupStringLiteralMode(FMParser parentParser, OutputFormat outputFormat) {
         FMParserTokenManager parentTokenSource = parentParser.token_source;
-         
+
         token_source.initialNamingConvention = parentTokenSource.initialNamingConvention;
         token_source.namingConvention = parentTokenSource.namingConvention;
         token_source.namingConventionEstabilisher = parentTokenSource.namingConventionEstabilisher;
         token_source.SwitchTo(NO_DIRECTIVE);
-        
+
         this.outputFormat = outputFormat;
-        recalculateAutoEscapingField();                                
+        recalculateAutoEscapingField();
         if (incompatibleImprovements < _VersionInts.V_2_3_24) {
             // Emulate bug, where the string literal parser haven't inherited the IcI:
             incompatibleImprovements = _VersionInts.V_2_3_0;
@@ -358,7 +358,8 @@
         if (outputFormat instanceof MarkupOutputFormat) {
             if (autoEscapingPolicy == Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY) {
                 autoEscaping = ((MarkupOutputFormat) outputFormat).isAutoEscapedByDefault();
-            } else if (autoEscapingPolicy == Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY) {
+            } else if (autoEscapingPolicy == Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY
+	        || autoEscapingPolicy == Configuration.FORCE_AUTO_ESCAPING_POLICY) {
                 autoEscaping = true;
             } else if (autoEscapingPolicy == Configuration.DISABLE_AUTO_ESCAPING_POLICY) {
                 autoEscaping = false;
@@ -521,6 +522,15 @@
         }
     }
 
+    private static String forcedAutoEscapingPolicyExceptionMessage(OutputFormat outputFormat) {
+        return forcedAutoEscapingPolicyExceptionMessage("Non-markup output format " + outputFormat);
+    }
+
+    private static String forcedAutoEscapingPolicyExceptionMessage(String whatCanNotBeUsed) {
+        return whatCanNotBeUsed + " can't be used when the \"" + Configuration.AUTO_ESCAPING_POLICY_KEY + "\" "
+                + "configuration setting was set to \"force\" (FORCE_AUTO_ESCAPING_POLICY).";
+    }
+
     private ParserIteratorBlockContext pushIteratorBlockContext() {
         if (iteratorBlockContexts == null) {
             iteratorBlockContexts = new ArrayList<ParserIteratorBlockContext>(4);
@@ -565,7 +575,7 @@
 	    // [2.4] Use camel case as the default
 	    return token_source.namingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION ? "#forEach" : "#foreach";
 	}
-    
+
 }
 
 PARSER_END(FMParser)
@@ -2214,16 +2224,24 @@
         }
         
         if (result instanceof BuiltInBannedWhenAutoEscaping) {
-	        if (outputFormat instanceof MarkupOutputFormat && autoEscaping) {
-	            throw new ParseException(
-	                    "Using ?" + t.image + " (legacy escaping) is not allowed when auto-escaping is on with "
-	                    + "a markup output format (" + outputFormat.getName() + "), to avoid double-escaping mistakes.",
-	                    template, t);
-	        }
+	    if (outputFormat instanceof MarkupOutputFormat && autoEscaping) {
+	        throw new ParseException(
+	                "Using ?" + t.image + " (legacy escaping) is not allowed when auto-escaping is on with "
+	                + "a markup output format (" + outputFormat.getName() + "), to avoid double-escaping mistakes.",
+	                template, t);
+	    }
             
             return result;
         }
 
+        if (result instanceof BuiltInBannedWhenForcedAutoEscaping) {
+            if (autoEscapingPolicy == Configuration.FORCE_AUTO_ESCAPING_POLICY) {
+                throw new ParseException(
+                        forcedAutoEscapingPolicyExceptionMessage("The ?" + t.image + " expression"),
+                        template, t);
+            }
+        }
+
         if (result instanceof MarkupOutputFormatBoundBuiltIn) {
             if (!(outputFormat instanceof MarkupOutputFormat)) {
                 throw new ParseException(
@@ -2367,7 +2385,7 @@
 }
 
 /**
- * production for when a key is specified by <DOT> + keyname
+ * production for when a key is specified by {@code <DOT>} + keyname
  */
 Expression DotVariable(Expression exp) :
 {
@@ -4046,6 +4064,10 @@
             } else {
                 outputFormat = template.getConfiguration().getOutputFormat(paramStr);
             }
+            if (!(outputFormat instanceof MarkupOutputFormat)
+                    && autoEscapingPolicy == Configuration.FORCE_AUTO_ESCAPING_POLICY) {
+                throw new ParseException(forcedAutoEscapingPolicyExceptionMessage(outputFormat), template, start);
+            }
             recalculateAutoEscapingField();
         } catch (IllegalArgumentException e) {
             throw new ParseException("Invalid format name: " + e.getMessage(), template, start, e.getCause());
@@ -4100,6 +4122,12 @@
 {
     start = <NOAUTOESC>
     {
+        if (autoEscapingPolicy == Configuration.FORCE_AUTO_ESCAPING_POLICY) {
+            throw new ParseException(
+                    forcedAutoEscapingPolicyExceptionMessage(
+                            "<#" + (token_source.namingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION ? "noAutoEsc" : "noautoesc") + ">"),
+                    template, start);
+        }
         lastAutoEscapingPolicy = autoEscapingPolicy;
         autoEscapingPolicy = Configuration.DISABLE_AUTO_ESCAPING_POLICY;
         recalculateAutoEscapingField();
@@ -4539,6 +4567,11 @@
                                 autoEscRequester = key;
                                 autoEscapingPolicy = Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY;
                             } else {
+                                if (autoEscapingPolicy == Configuration.FORCE_AUTO_ESCAPING_POLICY) {
+                                    throw new ParseException(
+                                            forcedAutoEscapingPolicyExceptionMessage(ks + "=false"),
+                                            exp);
+                                }
                                 autoEscapingPolicy = Configuration.DISABLE_AUTO_ESCAPING_POLICY;
                             }
                             recalculateAutoEscapingField();
@@ -4718,6 +4751,11 @@
         LOOKAHEAD([<STATIC_TEXT_WS>](<TRIVIAL_FTL_HEADER>|<FTL_HEADER>))
         HeaderElement()
     ]
+    {
+        if (!(outputFormat instanceof MarkupOutputFormat) && autoEscapingPolicy == Configuration.FORCE_AUTO_ESCAPING_POLICY) {
+            throw new IllegalArgumentException(forcedAutoEscapingPolicyExceptionMessage(outputFormat));
+        }
+    }
     children = MixedContentElements()
     <EOF>
     {
diff --git a/src/main/resources/freemarker/version.properties b/src/main/resources/freemarker/version.properties
index fa1fc2a..95cd4ef 100644
--- a/src/main/resources/freemarker/version.properties
+++ b/src/main/resources/freemarker/version.properties
@@ -56,11 +56,11 @@
 #   continue working without modification or recompilation.
 # - When the major version number is increased, major backward
 #   compatibility violations are allowed, but still should be avoided.
-version=2.3.32
+version=2.3.33-nightly_@timestampInVersion@
 # This exists as for Maven we use "-SNAPSHOT" for nightly releases,
 # and no _nightly_@timestampInVersion@. For final releases it's the
 # same as "version".
-mavenVersion=2.3.32
+mavenVersion=2.3.33-SNAPSHOT
 
 # Version string that conforms to OSGi
 # ------------------------------------
@@ -73,7 +73,7 @@
 #   2.4.0.rc01
 #   2.4.0.pre01
 #   2.4.0.nightly_@timestampInVersion@
-versionForOSGi=2.3.32.stable
+versionForOSGi=2.3.33.nightly_@timestampInVersion@
 
 # Version string that conforms to legacy MF
 # -----------------------------------------
@@ -92,7 +92,7 @@
 # "97 denotes "nightly", 98 denotes "pre", 99 denotes "rc" build.
 # In general, for the nightly/preview/rc Y of version 2.X, the versionForMf is
 # 2.X-1.(99|98).Y. Note the X-1.
-versionForMf=2.3.32
+versionForMf=2.3.32.97
 
 # The date of the build.
 # This should be automatically filled by the building tool (Ant).
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 3d62b1e..26bc653 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -20,10 +20,7 @@
 <book conformance="docgen" version="5.0" xml:lang="en"
       xmlns="http://docbook.org/ns/docbook"
       xmlns:xlink="http://www.w3.org/1999/xlink"
-      xmlns:ns5="http://www.w3.org/1998/Math/MathML"
-      xmlns:ns4="http://www.w3.org/2000/svg"
-      xmlns:ns3="http://www.w3.org/1999/xhtml"
-      xmlns:ns="http://docbook.org/ns/docbook">
+>
   <info>
     <title>Apache FreeMarker Manual</title>
 
@@ -3850,16 +3847,16 @@
               <literal>!</literal>, like <literal>1 + x</literal>,
               <emphasis>always</emphasis> use parentheses, like
               <literal>${x!(1 + y)}</literal> or <literal>${(x!1) +
-              y)}</literal>, depending on which interpretation you meant.
+              y}</literal>, depending on which interpretation you meant.
               That's needed because due to a programming mistake in FreeMarker
               2.3.x, the precedence of <literal>!</literal> (when it's used as
               default value operator) is very low at its right side. This
               means that, for example, <literal>${x!1 + y}</literal> is
               misinterpreted by FreeMarker as <literal>${x!(1 + y)}</literal>
               while it should mean <literal>${(x!1) + y}</literal>. This
-              programming error will be fixed in FreeMarker 2.4, so you should
-              not utilize this wrong behavior, or else your templates will
-              break with FreeMarker 2.4!</para>
+              programming error will be possibly fixed in some future version
+              (maybe 2.4), and you should not utilize this wrong behavior, so
+              that the fix can be enabled in your project!</para>
             </warning>
 
             <para>If the default value is omitted, then it will be empty
@@ -7923,7 +7920,7 @@
         <para>FTL's type system is technically represented by the
         <literal>TemplateModel</literal> sub-interfaces that were introduced
         earlier (<literal>TemplateScalarModel</literal>,
-        <literal>TemplateHashMode</literal>,
+        <literal>TemplateHashModel</literal>,
         <literal>TemplateSequenceModel</literal>, etc). To map a Java object
         to FTL's type system, object wrapper's <literal>TemplateModel
         wrap(java.lang.Object obj)</literal> method will be called.</para>
@@ -9628,10 +9625,10 @@
         <literal>auto_escaping_policy</literal>, which can be used to disable
         auto-escaping even if the current output format supports it, or enable
         auto-escaping even if the format by default doesn't escape (but it
-        supports it). Using this setting rarely advisable, as it's potentially
-        confusing for the template authors. (Instead, escaping can be turned
-        on/off explicitly inside the templates with the
-        <literal>auto_esc</literal> parameter of the <link
+        supports it), or to enforce auto-escaping. Using this setting rarely
+        advisable, as it's potentially confusing for the template authors.
+        (Instead, escaping can be turned on/off explicitly inside the
+        templates with the <literal>auto_esc</literal> parameter of the <link
         linkend="ref_directive_ftl"><literal>ftl</literal> directive</link>,
         or with the <link
         linkend="ref_directive_autoesc"><literal>noautoesc</literal></link>
@@ -12844,7 +12841,7 @@
           </listitem>
 
           <listitem>
-            <para>c <link linkend="ref_builtin_c">for strings</link>, <link
+            <para>c <link linkend="ref_builtin_c">for numbers</link>, <link
             linkend="ref_builtin_c_boolean">for booleans</link>, <link
             linkend="ref_builtin_c_string">for strings</link></para>
           </listitem>
@@ -16105,8 +16102,9 @@
                   setting</link> is less than 2.3.21: Gives what
                   <literal>java.text.DecimalFormat</literal> does with US
                   locale, which are <literal>∞</literal>,
-                  <literal>-∞</literal>, and <literal>�</literal> (U+FFFD,
-                  replacement character).</para>
+                  <literal>-∞</literal>, and <literal>NaN</literal> (before
+                  Java 11 was <literal>�</literal>, i.e., U+FFFD, replacement
+                  character).</para>
                 </listitem>
               </itemizedlist>
             </listitem>
@@ -30059,6 +30057,170 @@
     <appendix xml:id="app_versions">
       <title>Version history</title>
 
+      <section xml:id="versions_2_3_33">
+        <title>2.3.33</title>
+
+        <para>Release date: [TODO]</para>
+
+        <para>Please note that with this version the minimum required Java
+        version was increased from Java 7 to Java 8.</para>
+
+        <section>
+          <title>Changes on the FTL side</title>
+
+          <itemizedlist>
+            <listitem>
+              <para><link
+              xlink:href="https://github.com/apache/freemarker/pull/87">GitHub
+              PR 87</link> Comparing strings is now way faster, if the <link
+              linkend="pgui_config_incompatible_improvements_how_to_set"><literal>incompatible_improvements</literal>
+              setting</link> is at least 2.3.33. If your template does lot of
+              string comparisons, this can mean very significant speedup. With
+              this enabled, we use a simpler way of comparing strings, and
+              because templates were only ever allowed equality comparisons
+              between strings (not less-than, or greater-than), it's very
+              unlikely to change the behavior of your templates. (Technically,
+              what changes is that instead of using Java's localized
+              <literal>Collator</literal>-s, we switch to a simple binary
+              comparison after UNICODE NFKC normalization. So, in theory it's
+              possible that for some locales two different but similarly
+              looking characters were treated as equal by the collator, but
+              will count as different now. But it's very unlikely that anyone
+              wanted to depend on such fragile logic anyway. Note again that
+              we still do UNICODE normalization, so combining characters won't
+              break your comparison.)</para>
+            </listitem>
+
+            <listitem>
+              <para>When concatenating many sequences (like of Java
+              <literal>List</literal>-s) with the <literal>+</literal>
+              operator, the resulting sequence is now much less slow to read.
+              (Background: the <literal>+</literal> operator, when applied on
+              two sequences, produces a result sequence that just view, not a
+              copy of the original sequences. Thus is very fast to add
+              together long sequences. But, if you concatenate many, like
+              hundreds, of sequences, reading the sequence back will become
+              expensive. It was always like that, so it's still not
+              recommended to concatenate more then a few tens of sequences.
+              But if that recommendation is not kept, now the slowdown can be
+              way less punishing.)</para>
+
+              <itemizedlist>
+                <listitem>
+                  <para>Iteration (like listing) is now reasonably fast, and
+                  need not be concerned about. The speed of fetching the next
+                  item is now mostly independent of the number of sequences
+                  concatenated, while earlier it has become slower as that
+                  number grew. By iteration we mean going through all the
+                  items, strictly in order, without using an index explicitly.
+                  Examples of iteration are <literal>&lt;#list
+                  <replaceable>concatedSeq</replaceable> as
+                  <replaceable>...</replaceable>&gt;</literal>, and
+                  <literal><replaceable>concatedSeq</replaceable>?join(',
+                  ')</literal>.</para>
+                </listitem>
+
+                <listitem>
+                  <para>Accessing items by index, like
+                  <literal><replaceable>concatedSeq</replaceable>[i]</literal>,
+                  is now much faster than before, but still can be slow if you
+                  had concatenated a lot of sequences. Item access speed is
+                  now roughly O(N), where N is number of concatenated
+                  sequences (not the number of items!), so traversing the
+                  whole sequence by index is O(N²).</para>
+                </listitem>
+
+                <listitem>
+                  <para>In previous versions
+                  <literal><replaceable>concatedSeq</replaceable>?size</literal>
+                  could cause stack overflow if it
+                  <literal><replaceable>concatedSeq</replaceable></literal>
+                  was concatenated together from thousands of sequences. Now
+                  the stack usage is constant and negligible.</para>
+                </listitem>
+              </itemizedlist>
+            </listitem>
+          </itemizedlist>
+        </section>
+
+        <section>
+          <title>Changes on the Java side</title>
+
+          <itemizedlist>
+            <listitem>
+              <para><link
+              xlink:href="https://github.com/apache/freemarker/pull/88">GitHub
+              PR 88</link>: Added a new possible value for the
+              <literal>auto_escaping_policy</literal> configuration setting,
+              <literal>force</literal>
+              (<literal>Configuration.FORCE_AUTO_ESCAPING_POLICY</literal>).
+              This policy is to always require auto-escaping, to avoid
+              accidents where because of misconfiguration, or a mistake of the
+              template author it's disabled. With this policy, using output
+              formats that don't support escaping will not be allowed. Using
+              built-ins, and directives that disable auto-escaping (like
+              <literal>?no_esc</literal>) will also be errors (on parse-time).
+              Note that if markup (like HTML) comers from the data model, then
+              with this policy you will have to ensure that they come as
+              <literal>TemplateMarkupOutputModel</literal>-s (which won't be
+              auto-escaped even with this policy), not as
+              <literal>String</literal>-s, because the template authors can't
+              disable escaping for the value anymore.</para>
+            </listitem>
+
+            <listitem>
+              <para><link
+              xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-214">FREEMARKER-214</link>,
+              <link
+              xlink:href="https://github.com/apache/freemarker/pull/90">GitHub
+              PR 90</link>: Update JavaCC (used for generating the template
+              parser) from 6.1.2 to 7.0.12, to avoid creating new
+              <literal>LookAheadSuccess</literal> with stack trace instance
+              for each <literal>FMParser</literal> instance.</para>
+            </listitem>
+
+            <listitem>
+              <para>When <literal>FreemarkerServlet</literal> was used with
+              the <literal>TemplateExceptionHandler.DEBUG_HANDLER</literal> or
+              <literal>HTML_DEBUG_HANDLER</literal>, on Jetty the response web
+              page was possibly empty or partial, as the Jetty has abruptly
+              closed the connection after we flushed the HTTP response with
+              status code 200, and yet thrown an exception. Now in this
+              situation we only log the exception, but don't throw it.
+              (Production system should always use
+              <literal>RETHROW_HANDLER</literal>, and this change has no
+              effect there.)</para>
+            </listitem>
+
+            <listitem>
+              <para>When concatenating sequences (like Java
+              <literal>List</literal>-s) with the <literal>+</literal>
+              operation in templates, the resulting
+              <literal>TemplateSequenceModel</literal> now also implements
+              <literal>TemplateCollectionModelEx</literal> (with is similar to
+              Java's <literal>Iterable</literal>). It's because using that is
+              much more efficient then indexed access, if the sequence was
+              concatenated together from a lot of sequences.</para>
+            </listitem>
+
+            <listitem>
+              <para>We don't generate RMIC stub classes for
+              <literal>freemarker.debug.impl.Rmi*Impl</literal> classes
+              anymore. Almost nobody uses that API, but if you do, you
+              certainly already rely on dynamic stubs anyway. (The
+              <literal>rmic</literal> tool used to generate the stub classes
+              was removed from the JDK, starting with JDK 15.)</para>
+            </listitem>
+
+            <listitem>
+              <para>To build FreeMarker itself (not for just using it), now
+              you need JDK 16 (or later). This doesn't influence the minimum
+              Java version requirement for using FreeMarker.</para>
+            </listitem>
+          </itemizedlist>
+        </section>
+      </section>
+
       <section xml:id="versions_2_3_32">
         <title>2.3.32</title>
 
diff --git a/src/manual/en_US/docgen.cjson b/src/manual/en_US/docgen.cjson
index 21adabc..a6b9eed 100644
--- a/src/manual/en_US/docgen.cjson
+++ b/src/manual/en_US/docgen.cjson
@@ -1,4 +1,4 @@
-//charset: UTF-8
+//charset: UTF-8
 
 // Licensed to the Apache Software Foundation (ASF) under one
 // or more contributor license agreements.  See the NOTICE file
@@ -70,7 +70,7 @@
   mailing-lists: "https://freemarker.apache.org/mailing-lists.html"
   
   // External URL-s:
-  onlineTemplateTester: "http://try.freemarker.org/"
+  onlineTemplateTester: "https://try.freemarker.apache.org/"
   twitter: "https://twitter.com/freemarker"
   sourceforgeProject: "https://sourceforge.net/projects/freemarker/"
   githubProject: "https://github.com/freemarker/freemarker"
diff --git a/src/test/java/freemarker/core/CAndCnBuiltInTest.java b/src/test/java/freemarker/core/CAndCnBuiltInTest.java
index 9e73e8a..b3c8f50 100644
--- a/src/test/java/freemarker/core/CAndCnBuiltInTest.java
+++ b/src/test/java/freemarker/core/CAndCnBuiltInTest.java
@@ -107,7 +107,7 @@
                 expectedNaN = "NaN";
             } else {
                 expectedInf = "\u221E";
-                expectedNaN = "\uFFFD";
+                expectedNaN = "NaN"; // was \uFFFD before Java 11
             }
 
             assertOutput("${" + type + "Inf?" + builtInName + "}", expectedInf);
diff --git a/src/test/java/freemarker/core/ConcatenatedSequenceTest.java b/src/test/java/freemarker/core/ConcatenatedSequenceTest.java
new file mode 100644
index 0000000..8bc2137
--- /dev/null
+++ b/src/test/java/freemarker/core/ConcatenatedSequenceTest.java
@@ -0,0 +1,421 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package freemarker.core;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.function.Supplier;
+
+import org.junit.Test;
+
+import freemarker.core.AddConcatExpression.ConcatenatedSequence;
+import freemarker.template.Configuration;
+import freemarker.template.DefaultListAdapter;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.DefaultObjectWrapperBuilder;
+import freemarker.template.SimpleCollection;
+import freemarker.template.SimpleScalar;
+import freemarker.template.SimpleSequence;
+import freemarker.template.TemplateCollectionModelEx;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateModelIterator;
+import freemarker.template.TemplateScalarModel;
+import freemarker.template.TemplateSequenceModel;
+
+public class ConcatenatedSequenceTest {
+    interface SeqFactory {
+        TemplateSequenceModel create(String... items);
+        boolean isUnrepeatable();
+    }
+
+    @Test
+    public void testForSimpleSequences() throws TemplateModelException {
+        testWithSegmentFactory(new SeqFactory() {
+            @Override
+            public TemplateSequenceModel create(String... items) {
+                return new SimpleSequence(Arrays.asList(items));
+            }
+
+            @Override
+            public boolean isUnrepeatable() {
+                return false;
+            }
+        });
+    }
+
+    @Test
+    public void testForListAdapter() throws TemplateModelException {
+        DefaultObjectWrapper objectWrapper = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_32).build();
+        testWithSegmentFactory(new SeqFactory() {
+            @Override
+            public TemplateSequenceModel create(String... items) {
+                return DefaultListAdapter.adapt(Arrays.asList(items), objectWrapper);
+            }
+
+            @Override
+            public boolean isUnrepeatable() {
+                return false;
+            }
+        });
+    }
+
+    @Test
+    public void testForSequenceAndCollectionModelEx() throws TemplateModelException {
+        testWithSegmentFactory(new SeqFactory() {
+            @Override
+            public TemplateSequenceModel create(String... items) {
+                return new SequenceAndCollectionModelEx(Arrays.asList(items));
+            }
+
+            @Override
+            public boolean isUnrepeatable() {
+                return false;
+            }
+        });
+    }
+
+    @Test
+    public void testForCollectionsWrappingIterable() throws TemplateModelException {
+        testWithSegmentFactory(new SeqFactory() {
+            @Override
+            public TemplateSequenceModel create(String... items) {
+                return new CollectionAndSequence(new SimpleCollection(Arrays.asList(items)));
+            }
+
+            @Override
+            public boolean isUnrepeatable() {
+                return false;
+            }
+        });
+    }
+
+    @Test
+    public void testForCollectionsWrappingIterator() throws TemplateModelException {
+        testWithSegmentFactory(new SeqFactory() {
+            @Override
+            public TemplateSequenceModel create(String... items) {
+                return new CollectionAndSequence(new SimpleCollection(Arrays.asList(items).iterator()));
+            }
+
+            @Override
+            public boolean isUnrepeatable() {
+                return true;
+            }
+        });
+    }
+
+    public void testWithSegmentFactory(SeqFactory segmentFactory) throws TemplateModelException {
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(segmentFactory.create(), segmentFactory.create()));
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(segmentFactory.create(), segmentFactory.create("b")),
+                "b");
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(segmentFactory.create("a"), segmentFactory.create()),
+                "a");
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(segmentFactory.create("a"), segmentFactory.create("b")),
+                "a", "b");
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(
+                        new ConcatenatedSequence(
+                                segmentFactory.create(),
+                                segmentFactory.create()),
+                        new ConcatenatedSequence(
+                                segmentFactory.create(),
+                                segmentFactory.create())
+                )
+        );
+
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(
+                        new ConcatenatedSequence(
+                                segmentFactory.create("a", "b"),
+                                segmentFactory.create()),
+                        new ConcatenatedSequence(
+                                segmentFactory.create(),
+                                segmentFactory.create())
+                ),
+                "a", "b"
+        );
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                        new ConcatenatedSequence(
+                                new ConcatenatedSequence(
+                                        segmentFactory.create(),
+                                        segmentFactory.create("a", "b")),
+                                new ConcatenatedSequence(
+                                        segmentFactory.create(),
+                                        segmentFactory.create())
+                        ),
+                "a", "b"
+        );
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                        new ConcatenatedSequence(
+                                new ConcatenatedSequence(
+                                        segmentFactory.create(),
+                                        segmentFactory.create()),
+                                new ConcatenatedSequence(
+                                        segmentFactory.create("a", "b"),
+                                        segmentFactory.create())
+                        ),
+                "a", "b"
+        );
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                        new ConcatenatedSequence(
+                                new ConcatenatedSequence(
+                                        segmentFactory.create(),
+                                        segmentFactory.create()),
+                                new ConcatenatedSequence(
+                                        segmentFactory.create(),
+                                        segmentFactory.create("a", "b"))
+                        ),
+                "a", "b"
+        );
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(
+                        new ConcatenatedSequence(
+                                segmentFactory.create("a"),
+                                segmentFactory.create("b")),
+                        new ConcatenatedSequence(
+                                segmentFactory.create(),
+                                segmentFactory.create())
+                ),
+                "a", "b"
+        );
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                        new ConcatenatedSequence(
+                                new ConcatenatedSequence(
+                                        segmentFactory.create(),
+                                        segmentFactory.create("a")),
+                                new ConcatenatedSequence(
+                                        segmentFactory.create("b"),
+                                        segmentFactory.create())
+                        ),
+                "a", "b"
+        );
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                        new ConcatenatedSequence(
+                                new ConcatenatedSequence(
+                                        segmentFactory.create(),
+                                        segmentFactory.create()),
+                                new ConcatenatedSequence(
+                                        segmentFactory.create("a"),
+                                        segmentFactory.create("b"))
+                        ),
+                "a", "b"
+        );
+
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(
+                        new ConcatenatedSequence(
+                                new ConcatenatedSequence(
+                                        segmentFactory.create("a"),
+                                        segmentFactory.create("b")),
+                                segmentFactory.create("c")),
+                        segmentFactory.create("d")),
+                "a", "b", "c", "d");
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(
+                        new ConcatenatedSequence(
+                                segmentFactory.create("a"),
+                                segmentFactory.create("b")),
+                        new ConcatenatedSequence(
+                                segmentFactory.create("c"),
+                                segmentFactory.create("d"))
+                ),
+                "a", "b", "c", "d");
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(
+                    segmentFactory.create("a"),
+                    new ConcatenatedSequence(
+                            segmentFactory.create("b"),
+                            new ConcatenatedSequence(
+                                    segmentFactory.create("c"),
+                                    segmentFactory.create("d")
+                            )
+                    )
+                ),
+                "a", "b", "c", "d");
+
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(
+                        new ConcatenatedSequence(
+                                segmentFactory.create("a", "b"),
+                                segmentFactory.create("c", "d")),
+                        new ConcatenatedSequence(
+                                segmentFactory.create("e", "f"),
+                                segmentFactory.create("g", "h"))
+                ),
+                "a", "b", "c", "d", "e", "f", "g", "h");
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(
+                        segmentFactory.create("a", "b", "c"),
+                        new ConcatenatedSequence(
+                                segmentFactory.create("d", "e"),
+                                segmentFactory.create("f", "g", "h"))
+                ),
+                "a", "b", "c", "d", "e", "f", "g", "h");
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                new ConcatenatedSequence(
+                        new ConcatenatedSequence(
+                                segmentFactory.create("a", "b"),
+                                segmentFactory.create("c", "d")),
+                        segmentFactory.create("e", "f", "g", "h")
+                ),
+                "a", "b", "c", "d", "e", "f", "g", "h");
+
+        if (!segmentFactory.isUnrepeatable()) {
+            // Test when the same segment seq instance is for multiple times in a concatenated seq.
+            assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                    {
+                        TemplateSequenceModel ab = segmentFactory.create("a", "b");
+                        ConcatenatedSequence abab = new ConcatenatedSequence(ab, ab);
+                        return new ConcatenatedSequence(abab, abab);
+                    },
+                    "a", "b", "a", "b", "a", "b", "a", "b");
+        }
+
+        assertConcatenationResult(segmentFactory.isUnrepeatable(), () ->
+                        new ConcatenatedSequence(
+                                new ConcatenatedSequence(
+                                        segmentFactory.create(null, "a"),
+                                        segmentFactory.create("b", null)),
+                                segmentFactory.create((String) null)
+                        ),
+                null, "a", "b", null, null);
+    }
+
+    private void assertConcatenationResult(
+            boolean repeatable,
+            Supplier<ConcatenatedSequence> seqSupplier,
+            String... expectedItems)
+            throws TemplateModelException {
+        ConcatenatedSequence seq = seqSupplier.get();
+
+        {
+            List<String> actualItems = new ArrayList<>();
+            for (TemplateModelIterator iter = seq.iterator(); iter.hasNext(); ) {
+                actualItems.add(asNullableString((TemplateScalarModel) iter.next()));
+            }
+            assertEquals(Arrays.asList(expectedItems), actualItems);
+        }
+
+        if (repeatable) {
+            seq = seqSupplier.get();
+        }
+
+        {
+            List<String> actualItems = new ArrayList<>();
+            for (TemplateModelIterator iter = seq.iterator(); iter.hasNext(); ) {
+                assertTrue(iter.hasNext());
+                actualItems.add(asNullableString((TemplateScalarModel) iter.next()));
+            }
+            assertEquals(Arrays.asList(expectedItems), actualItems);
+        }
+
+        if (repeatable) {
+            seq = seqSupplier.get();
+        }
+
+        {
+            List<String> actualItems = new ArrayList<>();
+            int size = seq.size();
+            for (int i = 0; i < size; i++) {
+                actualItems.add(asNullableString((TemplateScalarModel) seq.get(i)));
+            }
+            assertEquals(Arrays.asList(expectedItems), actualItems);
+            assertNull(seq.get(-1));
+            assertNull(seq.get(size));
+            assertNull(seq.get(size + 1));
+        }
+
+        if (repeatable) {
+            seq = seqSupplier.get();
+        }
+
+        assertEquals(expectedItems.length, seq.size());
+
+        if (repeatable) {
+            seq = seqSupplier.get();
+        }
+
+        assertEquals(expectedItems.length == 0, seq.isEmpty());
+    }
+
+    private String asNullableString(TemplateScalarModel model) throws TemplateModelException {
+        return model != null ? model.getAsString() : null;
+    }
+
+    /**
+     * This is to test {@link TemplateSequenceModel} that's also a {@link TemplateCollectionModelEx}.
+     */
+    private static class SequenceAndCollectionModelEx implements TemplateSequenceModel, TemplateCollectionModelEx {
+        private final List<String> items;
+
+        public SequenceAndCollectionModelEx(List<String> items) {
+            this.items = items;
+        }
+
+        @Override
+        public TemplateModelIterator iterator() throws TemplateModelException {
+            return new TemplateModelIterator() {
+                    private final Iterator<String> it = items.iterator();
+
+                    @Override
+                    public TemplateModel next() throws TemplateModelException {
+                        try {
+                            String value = it.next();
+                            return value != null ? new SimpleScalar(value) : null;
+                        } catch (NoSuchElementException e) {
+                            throw new TemplateModelException("The collection has no more items.", e);
+                        }
+                    }
+
+                    @Override
+                    public boolean hasNext() throws TemplateModelException {
+                        return it.hasNext();
+                    }
+            };
+        }
+
+        @Override
+        public boolean isEmpty() throws TemplateModelException {
+            return items.isEmpty();
+        }
+
+        @Override
+        public TemplateModel get(int index) throws TemplateModelException {
+            String value = items.get(index);
+            return value != null ? new SimpleScalar(value) : null;
+        }
+
+        @Override
+        public int size() throws TemplateModelException {
+            return items.size();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/freemarker/core/DateFormatTest.java b/src/test/java/freemarker/core/DateFormatTest.java
index 3f8a81c..5324361 100644
--- a/src/test/java/freemarker/core/DateFormatTest.java
+++ b/src/test/java/freemarker/core/DateFormatTest.java
@@ -382,7 +382,7 @@
                 + "<#setting locale='en_GB_Win'>${d} "
                 + "<#setting locale='fr_FR'>${d} "
                 + "<#setting locale='hu_HU'>${d}",
-                "2015-Sep_en 2015-Sep_en_GB 2015-Sep_en_GB 2015-sept._fr_FR 2015-szept.");
+                "2015-Sep_en 2015-Sept_en_GB 2015-Sept_en_GB 2015-sept._fr_FR 2015-szept.");
     }
     
     /**
diff --git a/src/test/java/freemarker/core/NumberFormatTest.java b/src/test/java/freemarker/core/NumberFormatTest.java
index 4d4ce23..b9f9978 100644
--- a/src/test/java/freemarker/core/NumberFormatTest.java
+++ b/src/test/java/freemarker/core/NumberFormatTest.java
@@ -332,7 +332,7 @@
             boolean cBuiltInBroken = ici.intValue() < Configuration.VERSION_2_3_21.intValue();
             boolean cNumberFormatBroken = ici.intValue() < Configuration.VERSION_2_3_31.intValue();
 
-            String humanAudienceOutput = "\u221e -\u221e \ufffd";
+            String humanAudienceOutput = "\u221e -\u221e NaN"; // NaN was \uFFFD before Java 11
             String computerAudienceOutput = ici.intValue() < Configuration.VERSION_2_3_32.intValue()
                     ? "INF -INF NaN" : "Infinity -Infinity NaN";
 
diff --git a/src/test/java/freemarker/core/OutputFormatTest.java b/src/test/java/freemarker/core/OutputFormatTest.java
index 3e3207d..7b8219f 100644
--- a/src/test/java/freemarker/core/OutputFormatTest.java
+++ b/src/test/java/freemarker/core/OutputFormatTest.java
@@ -851,7 +851,47 @@
                     + dExpted);
         }
     }
-    
+
+    @Test
+    public void testForcedAutoEsc() throws Exception {
+        Configuration cfg = getConfiguration();
+        cfg.setRegisteredCustomOutputFormats(ImmutableList.of(
+                SeldomEscapedOutputFormat.INSTANCE, DummyOutputFormat.INSTANCE));
+        cfg.setAutoEscapingPolicy(Configuration.FORCE_AUTO_ESCAPING_POLICY);
+
+        String commonFTL = "${'.'} ${.autoEsc?c}";
+        String esced = "\\. true";
+
+        cfg.setOutputFormat(SeldomEscapedOutputFormat.INSTANCE);
+        assertOutput(commonFTL, esced);
+
+        cfg.setOutputFormat(DummyOutputFormat.INSTANCE);
+        assertOutput(commonFTL, esced);
+
+        cfg.setOutputFormat(PlainTextOutputFormat.INSTANCE);
+        assertOutput("<#ftl outputFormat='seldomEscaped'>" + commonFTL, esced);
+
+        cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
+        assertOutput("<#outputFormat 'seldomEscaped'>" + commonFTL + "</#outputFormat>", esced);
+
+        cfg.setOutputFormat(PlainTextOutputFormat.INSTANCE);
+        assertErrorContains("", IllegalArgumentException.class,
+                "plainText", "auto_escaping_policy", "force");
+        cfg.setOutputFormat(DummyOutputFormat.INSTANCE);
+        assertErrorContains("<#ftl auto_esc=false>", ParseException.class,
+                "auto_esc=false", "auto_escaping_policy", "force");
+        assertErrorContains("<#outputformat 'plainText'></#outputformat>", ParseException.class,
+                "plainText", "auto_escaping_policy", "force");
+        assertErrorContains("<#noAutoEsc></#noAutoEsc>", ParseException.class,
+                "noAutoEsc", "auto_escaping_policy", "force");
+        assertErrorContains("<#noautoesc></#noautoesc>", ParseException.class,
+                "noautoesc", "auto_escaping_policy", "force");
+        assertErrorContains("<#assign foo='bar'>${foo?no_esc}", ParseException.class,
+                "?no_esc", "auto_escaping_policy", "force");
+        assertErrorContains("<#assign foo='bar'>${foo?noEsc}", ParseException.class,
+                "?noEsc", "auto_escaping_policy", "force");
+    }
+
     @Test
     public void testDynamicParsingBIsInherticContextOutputFormat() throws Exception {
         // Dynamic parser BI-s are supposed to use the parserConfiguration of the calling template, and ignore anything
diff --git a/src/test/java/freemarker/core/TagSyntaxVariationsTest.java b/src/test/java/freemarker/core/TagSyntaxVariationsTest.java
index 08030d2..7343796 100644
--- a/src/test/java/freemarker/core/TagSyntaxVariationsTest.java
+++ b/src/test/java/freemarker/core/TagSyntaxVariationsTest.java
@@ -206,7 +206,7 @@
     }
     
     /**
-     * @param expected the expected output or <tt>null</tt> if we expect
+     * @param expected the expected output or {@code null} if we expect
      * a parsing error.
      */
     private static final void test(
diff --git a/src/test/java/freemarker/ext/beans/BeansWrapperMiscTest.java b/src/test/java/freemarker/ext/beans/BeansWrapperMiscTest.java
index c3ff772..ac7a7b7 100644
--- a/src/test/java/freemarker/ext/beans/BeansWrapperMiscTest.java
+++ b/src/test/java/freemarker/ext/beans/BeansWrapperMiscTest.java
@@ -29,7 +29,6 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import freemarker.core._JavaVersions;
 import freemarker.template.Configuration;
 import freemarker.template.TemplateBooleanModel;
 import freemarker.template.TemplateHashModel;
@@ -101,7 +100,6 @@
 
     @Test
     public void java8InaccessibleIndexedAccessibleNonIndexedReadMethodTest() throws TemplateModelException {
-        assertTrue("This test case must be ran on Java 8 or later", _JavaVersions.JAVA_8 != null);
         assertFalse(Modifier.isPublic(BeanWithInaccessibleIndexedProperty.class.getModifiers()));
         
         for (Version ici : new Version[] { Configuration.VERSION_2_3_26, Configuration.VERSION_2_3_27 }) {
diff --git a/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java b/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java
index ad0c7b6..a89d798 100644
--- a/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java
+++ b/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java
@@ -207,13 +207,13 @@
     public void tldDiscoveryRelative() throws Exception {
         assertExpectedEqualsOutput(WEBAPP_TLD_DISCOVERY, "subdir/test-rel.txt", "tester?view=subdir/test-rel.ftl");
     }
-    
+
     @Test
     public void errorStatusCodes() throws Exception {
         assertEquals(404, getResponseStatusCode(WEBAPP_ERRORS, "missing.jsp"));
         assertEquals(500, getResponseStatusCode(WEBAPP_ERRORS, "failing-runtime.jsp"));
         assertEquals(500, getResponseStatusCode(WEBAPP_ERRORS, "failing-parsetime.jsp"));
-        
+
         assertEquals(200, getResponseStatusCode(WEBAPP_ERRORS,
                 "tester?view=not-failing.ftl&viewServlet=freemarker-default-dev"));
         assertEquals(404, getResponseStatusCode(WEBAPP_ERRORS,
diff --git a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
index 541bf1b..d67d195 100644
--- a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
+++ b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
@@ -104,6 +104,7 @@
         expected.add(Configuration.VERSION_2_3_27); // no non-BC change in 2.3.30
         expected.add(Configuration.VERSION_2_3_27); // no non-BC change in 2.3.31
         expected.add(Configuration.VERSION_2_3_27); // no non-BC change in 2.3.32
+        expected.add(Configuration.VERSION_2_3_27); // no non-BC change in 2.3.33
 
         List<Version> actual = new ArrayList<>();
         for (int i = _VersionInts.V_2_3_0; i <= Configuration.getVersion().intValue(); i++) {
diff --git a/src/test/java/freemarker/template/MockServletContext.java b/src/test/java/freemarker/template/MockServletContext.java
index f833bec..0ed47ec 100644
--- a/src/test/java/freemarker/template/MockServletContext.java
+++ b/src/test/java/freemarker/template/MockServletContext.java
@@ -22,12 +22,20 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.Map;
 import java.util.Set;
 
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.Servlet;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
 
 public class MockServletContext implements ServletContext {
 
@@ -55,6 +63,11 @@
         return null;
     }
 
+    @Override
+    public boolean setInitParameter(String s, String s1) {
+        return false;
+    }
+
     public int getMajorVersion() {
         return 0;
     }
@@ -67,6 +80,16 @@
         return 0;
     }
 
+    @Override
+    public int getEffectiveMajorVersion() {
+        return 0;
+    }
+
+    @Override
+    public int getEffectiveMinorVersion() {
+        return 0;
+    }
+
     public RequestDispatcher getNamedDispatcher(String arg0) {
         return null;
     }
@@ -103,6 +126,126 @@
         return "MyApp";
     }
 
+    @Override
+    public ServletRegistration.Dynamic addServlet(String s, String s1) {
+        return null;
+    }
+
+    @Override
+    public ServletRegistration.Dynamic addServlet(String s, Servlet servlet) {
+        return null;
+    }
+
+    @Override
+    public ServletRegistration.Dynamic addServlet(String s, Class<? extends Servlet> aClass) {
+        return null;
+    }
+
+    @Override
+    public <T extends Servlet> T createServlet(Class<T> aClass) throws ServletException {
+        return null;
+    }
+
+    @Override
+    public ServletRegistration getServletRegistration(String s) {
+        return null;
+    }
+
+    @Override
+    public Map<String, ? extends ServletRegistration> getServletRegistrations() {
+        return null;
+    }
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(String s, String s1) {
+        return null;
+    }
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(String s, Filter filter) {
+        return null;
+    }
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(String s, Class<? extends Filter> aClass) {
+        return null;
+    }
+
+    @Override
+    public <T extends Filter> T createFilter(Class<T> aClass) throws ServletException {
+        return null;
+    }
+
+    @Override
+    public FilterRegistration getFilterRegistration(String s) {
+        return null;
+    }
+
+    @Override
+    public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
+        return null;
+    }
+
+    @Override
+    public SessionCookieConfig getSessionCookieConfig() {
+        return null;
+    }
+
+    @Override
+    public void setSessionTrackingModes(Set<SessionTrackingMode> set) {
+
+    }
+
+    @Override
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
+        return null;
+    }
+
+    @Override
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
+        return null;
+    }
+
+    @Override
+    public void addListener(String s) {
+
+    }
+
+    @Override
+    public <T extends EventListener> void addListener(T t) {
+
+    }
+
+    @Override
+    public void addListener(Class<? extends EventListener> aClass) {
+
+    }
+
+    @Override
+    public <T extends EventListener> T createListener(Class<T> aClass) throws ServletException {
+        return null;
+    }
+
+    @Override
+    public JspConfigDescriptor getJspConfigDescriptor() {
+        return null;
+    }
+
+    @Override
+    public ClassLoader getClassLoader() {
+        return null;
+    }
+
+    @Override
+    public void declareRoles(String... strings) {
+
+    }
+
+    @Override
+    public String getVirtualServerName() {
+        return null;
+    }
+
     public Enumeration getServletNames() {
         return null;
     }
diff --git a/src/test/java/freemarker/test/package.html b/src/test/java/freemarker/test/package.html
index 3dbb3fb..14632bc 100644
--- a/src/test/java/freemarker/test/package.html
+++ b/src/test/java/freemarker/test/package.html
@@ -22,7 +22,7 @@
 <body>
 <p>Testing-related classes that don't fit into the normal packages.
 Normally you put the test classes into the same package where the
-tested classes are, but under <tt>src/test/java/<tt> instead of
-under <tt>src/main/java/<tt>.</p>
+tested classes are, but under <code>src/test/java/<code> instead of
+under <code>src/main/java/<code>.</p>
 </body>
 </html>
diff --git a/src/test/java/freemarker/test/servlet/WebAppTestCase.java b/src/test/java/freemarker/test/servlet/WebAppTestCase.java
index 0366bde..ab14fb2 100644
--- a/src/test/java/freemarker/test/servlet/WebAppTestCase.java
+++ b/src/test/java/freemarker/test/servlet/WebAppTestCase.java
@@ -34,6 +34,10 @@
 
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
+import org.eclipse.jetty.annotations.ServletContainerInitializersStarter;
+import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
+import org.eclipse.jetty.plus.annotation.ContainerInitializer;
+import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.webapp.WebAppContext;
@@ -111,7 +115,7 @@
 
         ensureWebAppIsDeployed(webAppName);
 
-        final URI uri = new URI("http://localhost:" + server.getConnectors()[0].getLocalPort()
+        final URI uri = new URI("http://localhost:" + ((NetworkConnector) server.getConnectors()[0]).getLocalPort()
                 + "/" + webAppName + "/" + webAppRelURL);
 
         final HttpURLConnection httpCon = (HttpURLConnection) uri.toURL().openConnection();
@@ -234,6 +238,9 @@
         context.setAttribute(
                 ATTR_JETTY_CONTAINER_INCLUDE_JAR_PATTERN,
                 ".*taglib.*\\.jar$");
+
+        addJasperInitializer(context);
+
         contextHandlers.addHandler(context);
         // As we add this after the Server was started, it has to be started manually:
         context.start();
@@ -242,6 +249,20 @@
         LOG.info("Deployed web app.: {}", webAppName);
     }
 
+    /**
+     * Without this, we will have this error when loading a taglib:
+     * NullPointerException: Cannot invoke "org.apache.jasper.compiler.TldCache.getTldResourcePath(String)" because the
+     * return value of "org.apache.jasper.Options.getTldCache()" is null
+     */
+    private static void addJasperInitializer(WebAppContext context) {
+        JettyJasperInitializer jettyJasperInitializer = new JettyJasperInitializer();
+        ServletContainerInitializersStarter servletContainerInitializersStarter
+                = new ServletContainerInitializersStarter(context);
+        ContainerInitializer containerInitializer = new ContainerInitializer(jettyJasperInitializer, null);
+        context.setAttribute("org.eclipse.jetty.containerInitializers", List.of(containerInitializer));
+        context.addBean(servletContainerInitializersStarter, true);
+    }
+
     private static void deleteTemporaryDirectories() throws IOException {
         if (testTempDirectory.getParentFile() == null) {
             throw new IOException("Won't delete the root directory");
diff --git a/src/test/java/freemarker/test/templatesuite/models/BooleanHash1.java b/src/test/java/freemarker/test/templatesuite/models/BooleanHash1.java
index 6e9993d..d734124 100644
--- a/src/test/java/freemarker/test/templatesuite/models/BooleanHash1.java
+++ b/src/test/java/freemarker/test/templatesuite/models/BooleanHash1.java
@@ -30,11 +30,11 @@
 public class BooleanHash1 implements TemplateHashModel {
 
     /**
-     * Gets a <tt>TemplateModel</tt> from the hash.
+     * Gets a {@code TemplateModel} from the hash.
      *
-     * @param key the name by which the <tt>TemplateModel</tt>
+     * @param key the name by which the {@code TemplateModel}
      * is identified in the template.
-     * @return the <tt>TemplateModel</tt> referred to by the key,
+     * @return the {@code TemplateModel} referred to by the key,
      * or null if not found.
      */
     public TemplateModel get(String key) {
diff --git a/src/test/java/freemarker/test/templatesuite/models/BooleanHash2.java b/src/test/java/freemarker/test/templatesuite/models/BooleanHash2.java
index b6c1147..a30027a 100644
--- a/src/test/java/freemarker/test/templatesuite/models/BooleanHash2.java
+++ b/src/test/java/freemarker/test/templatesuite/models/BooleanHash2.java
@@ -28,11 +28,11 @@
 public class BooleanHash2 implements TemplateHashModel {
 
     /**
-     * Gets a <tt>TemplateModel</tt> from the hash.
+     * Gets a {@code TemplateModel} from the hash.
      *
-     * @param key the name by which the <tt>TemplateModel</tt>
+     * @param key the name by which the {@code TemplateModel}
      * is identified in the template.
-     * @return the <tt>TemplateModel</tt> referred to by the key,
+     * @return the {@code TemplateModel} referred to by the key,
      * or null if not found.
      */
     public TemplateModel get(String key) {
diff --git a/src/test/java/freemarker/test/templatesuite/models/MultiModel1.java b/src/test/java/freemarker/test/templatesuite/models/MultiModel1.java
index 09d8a71..173cb0e 100644
--- a/src/test/java/freemarker/test/templatesuite/models/MultiModel1.java
+++ b/src/test/java/freemarker/test/templatesuite/models/MultiModel1.java
@@ -55,11 +55,11 @@
     }
 
     /**
-     * Gets a <tt>TemplateModel</tt> from the hash.
+     * Gets a {@code TemplateModel} from the hash.
      *
-     * @param key the name by which the <tt>TemplateModel</tt>
+     * @param key the name by which the {@code TemplateModel}
      * is identified in the template.
-     * @return the <tt>TemplateModel</tt> referred to by the key,
+     * @return the {@code TemplateModel} referred to by the key,
      * or null if not found.
      */
     public TemplateModel get(String key) {
diff --git a/src/test/java/freemarker/test/templatesuite/models/MultiModel2.java b/src/test/java/freemarker/test/templatesuite/models/MultiModel2.java
index f5ef98b..6ef4d9a 100644
--- a/src/test/java/freemarker/test/templatesuite/models/MultiModel2.java
+++ b/src/test/java/freemarker/test/templatesuite/models/MultiModel2.java
@@ -43,9 +43,9 @@
     /**
      * Executes a method call.
      *
-     * @param arguments a <tt>List</tt> of <tt>String</tt> objects containing the values
+     * @param arguments a {@code List} of {@code String} objects containing the values
      * of the arguments passed to the method.
-     * @return the <tt>TemplateModel</tt> produced by the method, or null.
+     * @return the {@code TemplateModel} produced by the method, or null.
      */
     public Object exec(List arguments) {
         StringBuilder  aResults = new StringBuilder( "Arguments are:<br />" );
diff --git a/src/test/java/freemarker/test/templatesuite/models/MultiModel3.java b/src/test/java/freemarker/test/templatesuite/models/MultiModel3.java
index bd362e5..3fcab5c 100644
--- a/src/test/java/freemarker/test/templatesuite/models/MultiModel3.java
+++ b/src/test/java/freemarker/test/templatesuite/models/MultiModel3.java
@@ -46,11 +46,11 @@
     }
 
     /**
-     * Gets a <tt>TemplateModel</tt> from the hash.
+     * Gets a {@code TemplateModel} from the hash.
      *
-     * @param key the name by which the <tt>TemplateModel</tt>
+     * @param key the name by which the {@code TemplateModel}
      * is identified in the template.
-     * @return the <tt>TemplateModel</tt> referred to by the key,
+     * @return the {@code TemplateModel} referred to by the key,
      * or null if not found.
      */
     public TemplateModel get(String key) {
diff --git a/src/test/java/freemarker/test/templatesuite/models/MultiModel4.java b/src/test/java/freemarker/test/templatesuite/models/MultiModel4.java
index b2848c6..284897d 100644
--- a/src/test/java/freemarker/test/templatesuite/models/MultiModel4.java
+++ b/src/test/java/freemarker/test/templatesuite/models/MultiModel4.java
@@ -40,11 +40,11 @@
     }
 
     /**
-     * Gets a <tt>TemplateModel</tt> from the hash.
+     * Gets a {@code TemplateModel} from the hash.
      *
-     * @param key the name by which the <tt>TemplateModel</tt>
+     * @param key the name by which the {@code TemplateModel}
      * is identified in the template.
-     * @return the <tt>TemplateModel</tt> referred to by the key,
+     * @return the {@code TemplateModel} referred to by the key,
      * or null if not found.
      */
     public TemplateModel get(String key) {
diff --git a/src/test/java/freemarker/test/templatesuite/models/MultiModel5.java b/src/test/java/freemarker/test/templatesuite/models/MultiModel5.java
index adf4aa3..a4e9031 100644
--- a/src/test/java/freemarker/test/templatesuite/models/MultiModel5.java
+++ b/src/test/java/freemarker/test/templatesuite/models/MultiModel5.java
@@ -56,11 +56,11 @@
     }
 
     /**
-     * Gets a <tt>TemplateModel</tt> from the hash.
+     * Gets a {@code TemplateModel} from the hash.
      *
-     * @param key the name by which the <tt>TemplateModel</tt>
+     * @param key the name by which the {@code TemplateModel}
      * is identified in the template.
-     * @return the <tt>TemplateModel</tt> referred to by the key,
+     * @return the {@code TemplateModel} referred to by the key,
      * or null if not found.
      */
     public TemplateModel get(String key) {
diff --git a/src/test/java/freemarker/test/templatesuite/models/SimpleTestMethod.java b/src/test/java/freemarker/test/templatesuite/models/SimpleTestMethod.java
index 9dc4ded..9d92294 100644
--- a/src/test/java/freemarker/test/templatesuite/models/SimpleTestMethod.java
+++ b/src/test/java/freemarker/test/templatesuite/models/SimpleTestMethod.java
@@ -32,9 +32,9 @@
     /**
      * Executes a method call.
      *
-     * @param arguments a <tt>List</tt> of <tt>String</tt> objects containing
+     * @param arguments a {@code List} of {@code String} objects containing
      * the values of the arguments passed to the method.
-     * @return the <tt>TemplateModel</tt> produced by the method, or null.
+     * @return the {@code TemplateModel} produced by the method, or null.
      */
     public Object exec(List arguments) {
         if ( arguments.size() == 0 ) {
diff --git a/src/test/java/freemarker/test/templatesuite/models/TransformHashWrapper.java b/src/test/java/freemarker/test/templatesuite/models/TransformHashWrapper.java
index 5d4049c..3a5f6a8 100644
--- a/src/test/java/freemarker/test/templatesuite/models/TransformHashWrapper.java
+++ b/src/test/java/freemarker/test/templatesuite/models/TransformHashWrapper.java
@@ -45,11 +45,11 @@
     }
 
     /**
-     * Gets a <tt>TemplateModel</tt> from the hash.
+     * Gets a {@code TemplateModel} from the hash.
      *
-     * @param key the name by which the <tt>TemplateModel</tt>
+     * @param key the name by which the {@code TemplateModel}
      * is identified in the template.
-     * @return the <tt>TemplateModel</tt> referred to by the key,
+     * @return the {@code TemplateModel} referred to by the key,
      * or null if not found.
      */
     public TemplateModel get(String key) throws TemplateModelException {
diff --git a/src/test/java/freemarker/test/templatesuite/models/TransformMethodWrapper1.java b/src/test/java/freemarker/test/templatesuite/models/TransformMethodWrapper1.java
index 51ae46c..32d236e 100644
--- a/src/test/java/freemarker/test/templatesuite/models/TransformMethodWrapper1.java
+++ b/src/test/java/freemarker/test/templatesuite/models/TransformMethodWrapper1.java
@@ -33,9 +33,9 @@
     /**
      * Executes a method call.
      *
-     * @param arguments a <tt>List</tt> of <tt>String</tt> objects containing
+     * @param arguments a {@code List} of {@code String} objects containing
      * the values of the arguments passed to the method.
-     * @return the <tt>TemplateModel</tt> produced by the method, or null.
+     * @return the {@code TemplateModel} produced by the method, or null.
      */
     public Object exec(List arguments) {
 
diff --git a/src/test/java/freemarker/test/templatesuite/models/TransformMethodWrapper2.java b/src/test/java/freemarker/test/templatesuite/models/TransformMethodWrapper2.java
index 87c0768..f89bfce 100644
--- a/src/test/java/freemarker/test/templatesuite/models/TransformMethodWrapper2.java
+++ b/src/test/java/freemarker/test/templatesuite/models/TransformMethodWrapper2.java
@@ -32,9 +32,9 @@
     /**
      * Executes a method call.
      *
-     * @param arguments a <tt>List</tt> of <tt>String</tt> objects containing
+     * @param arguments a {@code List} of {@code String} objects containing
      * the values of the arguments passed to the method.
-     * @return the <tt>TemplateModel</tt> produced by the method, or null.
+     * @return the {@code TemplateModel} produced by the method, or null.
      */
     public Object exec(List arguments) {
         TransformModel1 cTransformer = new TransformModel1();
diff --git a/src/test/java/freemarker/test/templatesuite/package.html b/src/test/java/freemarker/test/templatesuite/package.html
index cabffb0..08f4c9b 100644
--- a/src/test/java/freemarker/test/templatesuite/package.html
+++ b/src/test/java/freemarker/test/templatesuite/package.html
@@ -25,17 +25,17 @@
 output to reference files.</p>
 
 <p>To add a test-case, go to
-<tt>src/test/resources/freemarker/test/templatesuite/</tt> and inside that
+<code>src/test/resources/freemarker/test/templatesuite/</code> and inside that
 directory:</p>
 <ol>
-  <li>Add a template to under <tt>templates/</tt> with whatever meaningful
+  <li>Add a template to under <code>templates/</code> with whatever meaningful
       file name</li>
-  <li>Add the expected output to <tt>references/</tt> with exactly the same
+  <li>Add the expected output to <code>references/</code> with exactly the same
       file name</li>
-  <li>Add a new <tt>testcase</tt> elemen to <tt>testcases.xml</tt></li>
+  <li>Add a new <code>testcase</code> elemen to <code>testcases.xml</code></li>
   <li>If you want to add items to the data-model or do something else special,
-      modify the <tt>setUp()</tt> method in
-      <tt>src/test/java/freemarker/test/templatesuite/TemplateTestCase.java</tt>
+      modify the <code>setUp()</code> method in
+      <code>src/test/java/freemarker/test/templatesuite/TemplateTestCase.java</code>
       </li>
 </ol>
 </body>
diff --git a/src/test/resources/freemarker/test/templatesuite/expected/comparisons.txt b/src/test/resources/freemarker/test/templatesuite/expected/comparisons.txt
index 8d9d372..149fc95 100644
--- a/src/test/resources/freemarker/test/templatesuite/expected/comparisons.txt
+++ b/src/test/resources/freemarker/test/templatesuite/expected/comparisons.txt
@@ -88,6 +88,5 @@
    <p>Item is: 12</p>
    <p>Item is greater than two.</p>
    <p>Item is greater than or equal to ten.</p>
-
 </body>
 </html>
diff --git a/src/test/resources/freemarker/test/templatesuite/expected/number-format.txt b/src/test/resources/freemarker/test/templatesuite/expected/number-format.txt
index 5d8237d..326bddb 100644
--- a/src/test/resources/freemarker/test/templatesuite/expected/number-format.txt
+++ b/src/test/resources/freemarker/test/templatesuite/expected/number-format.txt
@@ -18,7 +18,7 @@
  */
 1
 1
-1 234 567,89
+1 234 567,89
 1234567.886
 1,00
 1
@@ -27,4 +27,4 @@
 1
 100000.5
 100000.5
-100 000,5
\ No newline at end of file
+100 000,5
\ No newline at end of file
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/comparisons.ftl b/src/test/resources/freemarker/test/templatesuite/templates/comparisons.ftl
index 3889765..da9d45e 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/comparisons.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/comparisons.ftl
@@ -82,6 +82,7 @@
    <p>Item is greater than or equal to ten.</p>
    </#if>
 </#foreach>
+<@noOutput>
 
 <#-- Signum-based optimization test, all 9 permutations: -->
 <#-- 1 -->
@@ -214,5 +215,26 @@
 <@assert test= (p3 >= m3) />
 <@assert test= !(p3 < m3) />
 <@assert test= !(p3 <= m3) />
+
+<#-- String comparison: -->
+<#assign s = 'a'>
+<@assert test= '' == '' />
+<@assert test= 'a' == 'a' />
+<@assert test= s == 'a' />
+<@assert test= s + 'b' == 'ab' />
+<@assert test= 'á' == 'a\x0301' />
+<@assert test= 'a\x0301' == 'á'/>
+<@assert test= 'a' != 'A' />
+<@assert test= s != 'A' />
+<@assert test= 'A' != 'a' />
+<@assert test= '' != 'a' />
+<@assert test= 'a' != '' />
+<@assert test= 'ab' != 'ac' />
+<@assertFails message="Can't use operator \"<\" on string values.">${s < s}</@>
+<@assertFails message="Can't use operator \">\" on string values.">${s > s}</@>
+<@assertFails message="Can't use operator \"<=\" on string values.">${s <= s}</@>
+<@assertFails message="Can't use operator \">=\" on string values.">${(s >= s)}</@>
+
+</@noOutput>
 </body>
 </html>
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/number-format.ftl b/src/test/resources/freemarker/test/templatesuite/templates/number-format.ftl
index 9d4b61f..5ffaf5c 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/number-format.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/number-format.ftl
@@ -58,5 +58,5 @@
 <#else>
   <@assertEquals expected="\x221E" actual="INF"?number?string />
   <@assertEquals expected="-\x221E" actual="-INF"?number?string />
-  <@assertEquals expected="\xFFFD" actual="NaN"?number?string />
+  <@assertEquals expected="NaN"<#-- was \xFFFD before Java 11 --> actual="NaN"?number?string />
 </#if>
\ No newline at end of file
diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml
index e3876ec..2ddfb82 100644
--- a/src/test/resources/logback-test.xml
+++ b/src/test/resources/logback-test.xml
@@ -26,6 +26,8 @@
 	</appender>
 	
 	<logger name="org.eclipse.jetty" level="INFO" />
+	<logger name="org.apache.tomcat" level="INFO" />
+	<logger name="org.apache.jasper" level="INFO" />
 
 	<root level="debug">
 		<appender-ref ref="STDOUT" />