Merge remote-tracking branch 'origin/2.3-gae' into 2.3
diff --git a/ivy.xml b/ivy.xml
index 0d54d11..251ec1b 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -138,8 +138,8 @@
 
     <dependency org="ch.qos.logback" name="logback-classic" rev="1.1.2" conf="test->default" />
 
-    <dependency org="commons-io" name="commons-io" rev="2.2" conf="test->default" />
-    <dependency org="com.google.guava" name="guava-jdk5" rev="17.0" conf="test->default" />
+    <dependency org="commons-io" name="commons-io" rev="2.7" conf="test->default" />
+    <dependency org="com.google.guava" name="guava" rev="29.0-jre" conf="test->default" />
 
     <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" />
diff --git a/src/main/java/freemarker/ext/beans/ClassIntrospector.java b/src/main/java/freemarker/ext/beans/ClassIntrospector.java
index e0c5135..36b2511 100644
--- a/src/main/java/freemarker/ext/beans/ClassIntrospector.java
+++ b/src/main/java/freemarker/ext/beans/ClassIntrospector.java
@@ -775,25 +775,27 @@
                 Method[] methods = clazz.getMethods();
                 for (int i = 0; i < methods.length; i++) {
                     Method method = methods[i];
-                    ExecutableMemberSignature sig = new ExecutableMemberSignature(method);
-                    // Contrary to intuition, a class can actually have several
-                    // different methods with same signature *but* different
-                    // return types. These can't be constructed using Java the
-                    // language, as this is illegal on source code level, but
-                    // the compiler can emit synthetic methods as part of
-                    // generic type reification that will have same signature
-                    // yet different return type than an existing explicitly
-                    // declared method. Consider:
-                    // public interface I<T> { T m(); }
-                    // public class C implements I<Integer> { Integer m() { return 42; } }
-                    // C.class will have both "Object m()" and "Integer m()" methods.
-                    List<Method> methodList = accessibles.get(sig);
-                    if (methodList == null) {
-                     // TODO Collection.singletonList is more efficient, though read only.
-                        methodList = new LinkedList<>();
-                        accessibles.put(sig, methodList);
+                    if (Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
+                        ExecutableMemberSignature sig = new ExecutableMemberSignature(method);
+                        // Contrary to intuition, a class can actually have several
+                        // different methods with same signature *but* different
+                        // return types. These can't be constructed using Java the
+                        // language, as this is illegal on source code level, but
+                        // the compiler can emit synthetic methods as part of
+                        // generic type reification that will have same signature
+                        // yet different return type than an existing explicitly
+                        // declared method. Consider:
+                        // public interface I<T> { T m(); }
+                        // public class C implements I<Integer> { Integer m() { return 42; } }
+                        // C.class will have both "Object m()" and "Integer m()" methods.
+                        List<Method> methodList = accessibles.get(sig);
+                        if (methodList == null) {
+                            // TODO Collection.singletonList is more efficient, though read only.
+                            methodList = new LinkedList<>();
+                            accessibles.put(sig, methodList);
+                        }
+                        methodList.add(method);
                     }
-                    methodList.add(method);
                 }
                 return;
             } catch (SecurityException e) {
@@ -814,21 +816,26 @@
         }
     }
 
+    // This is needed as java.bean.Introspector sometimes gives back a method that's actually not accessible,
+    // as it's an override of an accessible method in a non-public subclass. While that's still a public method, calling
+    // it directly via reflection will throw java.lang.IllegalAccessException, and we are supposed to call the overidden
+    // accessible method instead. Like, we migth get two PropertyDescriptor-s for the same property name, and only one
+    // will have a reader method that we can actually call. So we have to find that method here.
+    // Furthermore, the return type of the inaccisable method is possibly different (more specific) than the return type
+    // of the overidden accessible method. Also Introspector behavior changed with Java 9, as earlier in such case the
+    // Introspector returned all variants of the method (so the accessible one was amongst them at least), while in
+    // Java 9 it apparently always returns one variant only, but that's sometimes (not sure if it's predictable) the
+    // inaccessbile one.
     private static Method getMatchingAccessibleMethod(Method m, Map<ExecutableMemberSignature, List<Method>> accessibles) {
         if (m == null) {
             return null;
         }
-        ExecutableMemberSignature sig = new ExecutableMemberSignature(m);
-        List<Method> ams = accessibles.get(sig);
-        if (ams == null) {
-            return null;
-        }
-        for (Method am : ams) {
-            if (am.getReturnType() == m.getReturnType()) {
-                return am;
-            }
-        }
-        return null;
+        List<Method> ams = accessibles.get(new ExecutableMemberSignature(m));
+        // Certainly we could return any of the accessbiles, as Java reflection will call the correct override of the
+        // method anyway. There's an ambiguity when the return type is "overloaded", but in practice it probably doesn't
+        // matter which variant we call. Though, technically, they could do totaly different things. So, to avoid any
+        // corner cases that cause problems after an upgrade, we make an effort to give same result as before 2.3.31.
+        return ams != null ? _MethodUtil.getMethodWithClosestNonSubReturnType(m.getReturnType(), ams) : null;
     }
 
     private static Method getFirstAccessibleMethod(
diff --git a/src/main/java/freemarker/ext/beans/_MethodUtil.java b/src/main/java/freemarker/ext/beans/_MethodUtil.java
index 1540853..99a733e 100644
--- a/src/main/java/freemarker/ext/beans/_MethodUtil.java
+++ b/src/main/java/freemarker/ext/beans/_MethodUtil.java
@@ -25,6 +25,7 @@
 import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -458,4 +459,85 @@
         return getInheritableFieldAnnotation(superClass, fieldName, false, annotationClass);
     }
 
+    public static Method getMethodWithClosestNonSubReturnType(
+            Class<?> returnType, Collection<Method> methods) {
+        // Exact match:
+        for (Method method : methods) {
+            if (method.getReturnType() == returnType) {
+                return method;
+            }
+        }
+
+        if (returnType == Object.class || returnType.isPrimitive()) {
+            // We can't go wider than these types, so we give up. Note that void is primitive.
+            return null;
+        }
+
+        // Super-class match, which we prefer over Interface match, except if the match is just Object:
+        Class<?> superClass = returnType.getSuperclass();
+        while (superClass != null && superClass != Object.class) {
+            for (Method method : methods) {
+                if (method.getReturnType() == superClass) {
+                    return method;
+                }
+            }
+            superClass = superClass.getSuperclass();
+        }
+
+        // Interface match:
+        Method result = getMethodWithClosestNonSubInterfaceReturnType(returnType, methods);
+        if (result != null) {
+            return result;
+        }
+
+        // As the returnType is non-primitive, Object return type will do, as a last resort:
+        for (Method method : methods) {
+            if (method.getReturnType() == Object.class) {
+                return method;
+            }
+        }
+
+        return null;
+    }
+
+    private static Method getMethodWithClosestNonSubInterfaceReturnType(
+            Class<?> returnType, Collection<Method> methods) {
+        HashSet<Class<?>> nullResultReturnTypeInterfaces = new HashSet<>();
+        do {
+            Method result
+                    = getMethodWithClosestNonSubInterfaceReturnType(returnType, methods, nullResultReturnTypeInterfaces);
+            if (result != null) {
+                return result;
+            }
+            // As Class.getInterfaces() doesn't return the inherited interfaces, we have to do this.
+            returnType = returnType.getSuperclass();
+        } while (returnType != null);
+        return null;
+    }
+
+    private static Method getMethodWithClosestNonSubInterfaceReturnType(
+            Class<?> returnType, Collection<Method> methods, Set<Class<?>> nullResultReturnTypeInterfaces) {
+        boolean returnTypeIsInterface = returnType.isInterface();
+        if (returnTypeIsInterface) {
+            if (nullResultReturnTypeInterfaces.contains(returnType)) {
+                return null;
+            }
+            for (Method method : methods) {
+                if (method.getReturnType() == returnType) {
+                    return method;
+                }
+            }
+        }
+        for (Class<?> subInterface : returnType.getInterfaces()) {
+            Method result = getMethodWithClosestNonSubInterfaceReturnType(subInterface, methods, nullResultReturnTypeInterfaces);
+            if (result != null) {
+                return result;
+            }
+        }
+        if (returnTypeIsInterface) {
+            nullResultReturnTypeInterfaces.add(returnType);
+        }
+        return null;
+    }
+
 }
\ No newline at end of file
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 93139ae..bd7585a 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -472,6 +472,9 @@
     /** FreeMarker version 2.3.30 (an {@link #Configuration(Version) incompatible improvements break-point}) */
     public static final Version VERSION_2_3_30 = new Version(2, 3, 30);
 
+    /** FreeMarker version 2.3.31 (an {@link #Configuration(Version) incompatible improvements break-point}) */
+    public static final Version VERSION_2_3_31 = new Version(2, 3, 31);
+
     /** The default of {@link #getIncompatibleImprovements()}, currently {@link #VERSION_2_3_0}. */
     public static final Version DEFAULT_INCOMPATIBLE_IMPROVEMENTS = Configuration.VERSION_2_3_0;
     /** @deprecated Use {@link #DEFAULT_INCOMPATIBLE_IMPROVEMENTS} instead. */
diff --git a/src/main/java/freemarker/template/_TemplateAPI.java b/src/main/java/freemarker/template/_TemplateAPI.java
index 18c16e4..71feecb 100644
--- a/src/main/java/freemarker/template/_TemplateAPI.java
+++ b/src/main/java/freemarker/template/_TemplateAPI.java
@@ -54,6 +54,7 @@
     public static final int VERSION_INT_2_3_28 = Configuration.VERSION_2_3_28.intValue();
     public static final int VERSION_INT_2_3_29 = Configuration.VERSION_2_3_29.intValue();
     public static final int VERSION_INT_2_3_30 = Configuration.VERSION_2_3_30.intValue();
+    public static final int VERSION_INT_2_3_31 = Configuration.VERSION_2_3_31.intValue();
     public static final int VERSION_INT_2_4_0 = Version.intValueFor(2, 4, 0);
 
     /**
diff --git a/src/main/resources/freemarker/version.properties b/src/main/resources/freemarker/version.properties
index 641b6a6..13d378b 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.30
+version=2.3.31-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.30
+mavenVersion=2.3.31-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.30.stable
+versionForOSGi=2.3.31.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.30
+versionForMf=2.3.30.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 89e0fb3..29dce43 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -29,7 +29,7 @@
 
     <titleabbrev>Manual</titleabbrev>
 
-    <productname>Freemarker 2.3.30</productname>
+    <productname>Freemarker 2.3.31</productname>
   </info>
 
   <preface role="index.html" xml:id="preface">
@@ -21602,37 +21602,59 @@
         <section>
           <title>Description</title>
 
-          <para>Imports a library. That is, it creates a new empty namespace,
-          and then executes the template given with
-          <literal><replaceable>path</replaceable></literal> parameter in that
-          namespace so the template populates the namespace with variables
-          (macros, functions, ...etc.). Then it makes the newly created
-          namespace available to the caller with a hash variable. The hash
-          variable will be created as a plain variable in the namespace used
-          by the caller of <literal>import</literal> (as if you would create
-          it with <literal>assign</literal> directive), with the name given
-          with the <literal><replaceable>hash</replaceable></literal>
-          parameter. If the import happens in the namespace of the main
+          <para>Used for making a collection of macros, functions, and other
+          variables available for the importing template, which were defined
+          in the imported template. For example, let's say you have written
+          macros to generate some commonly needed pieces output, and you have
+          put them into <literal>/libs/commons.ftl</literal>. Then, in the
+          template where you want to use them, do this (near the top of the
+          the template by convention, next to any other
+          <literal>import</literal>-s):</para>
+
+          <programlisting role="template">&lt;#import "/libs/commons.ftl" as com&gt;
+</programlisting>
+
+          <para>Later in same template, let's say you want to use the
+          <literal>copyright</literal> macro defined in
+          <literal>/libs/commons.ftl</literal>. Then you can call that macro
+          like this:</para>
+
+          <programlisting role="template">&lt;@com.copyright date="1999-2002"/&gt;</programlisting>
+
+          <para>Note the <literal>com.</literal> before the macro name above.
+          All that was defined in <literal>/libs/commons.ftl</literal> will be
+          inside <literal>com</literal>.</para>
+
+          <para>Described more technically, <literal>import</literal> first
+          creates a new empty <link
+          linkend="dgui_misc_namespace">namespace</link>, and then executes
+          the template given with
+          <literal><replaceable>path</replaceable></literal> parameter inside
+          that namespace, so the template populates the namespace with
+          variables (macros, functions, ...etc.). Then the namespace is
+          assigned to the variable specified with the
+          <literal><replaceable>hash</replaceable></literal> parameter, and
+          you can access its contents through that. A namespace is <link
+          linkend="dgui_datamodel_container">a hash</link>, hence <link
+          linkend="dgui_template_exp_var_hash">the dot operator</link> worked
+          above. The assignment is like the <literal>assign</literal>
+          directive, that is, it sets the variable in the current namespace.
+          Except, if the import happens in the namespace of the main (topmost)
           template, the hash variable is also created in the global
           namespace.</para>
 
           <para>If you call <literal>import</literal> with the same
           <literal><replaceable>path</replaceable></literal> for multiple
           times, it will create the namespace and run the template for the
-          very first call of <literal>import</literal> only. The later calls
-          will just create a hash by which you can access the
-          <emphasis>same</emphasis> namespace.</para>
+          first call of <literal>import</literal> only. The later calls will
+          just give back the namespace that was created and initialized when
+          the template was imported for the first time, and will not execute
+          the imported template.</para>
 
-          <para>The output printed by the imported template will be ignored
-          (will not be inserted at the place of importing). The template is
-          executed to populate the namespace with variables, and not to write
-          to the output.</para>
-
-          <para>Example:</para>
-
-          <programlisting role="template">&lt;#import "/libs/mylib.ftl" as my&gt;
-
-&lt;@my.copyright date="1999-2002"/&gt;</programlisting>
+          <para>Any output printed by the imported template will be ignored
+          (will not be inserted at the place of <literal>import</literal>
+          directive invocation). An imported template is executed to populate
+          its namespace with variables, and not to write to the output.</para>
 
           <para>The <literal><replaceable>path</replaceable></literal>
           parameter can be a relative path like <literal>"foo.ftl"</literal>
@@ -21641,26 +21663,34 @@
           directory of the template that uses the <literal>import</literal>
           directive. Absolute paths are relative to a base (often referred as
           the ''root directory of the templates'') that the programmer defines
-          when he configures FreeMarker.</para>
+          when configuring FreeMarker.</para>
 
           <para>Always use <literal>/</literal> (slash) to separate path
           components, never <literal>\</literal> (backslash). If you are
           loading templates from your local file system and it uses
-          backslashes (like under. Windows), FreeMarker will convert them
-          automatically.</para>
+          backslashes (like under Windows), FreeMarker will do the necessary
+          conversions automatically.</para>
 
           <para>Like with the <literal>include</literal> directive, <link
           linkend="ref_directive_include_acquisition">acquisition</link> and
           <link linkend="ref_directive_include_localized">localized
           lookup</link> may be used for resolving the path.</para>
 
-          <para><phrase role="forProgrammers">Note, that it is possible to
+          <para><phrase role="forProgrammers">Note that it's possible to
           automatically do the commonly used imports for all templates, with
-          the "auto imports" setting of
+          the "auto imports" setting of <literal>Configuration</literal>.
+          Because templates may not use all the automatically imported
+          namespaces, it's also possible to make imports lazy (on demand),
+          with the <quote>lazy auto imports</quote> setting of
           <literal>Configuration</literal>.</phrase></para>
 
           <para>If you are new to namespaces, you should read: <xref
           linkend="dgui_misc_namespace"/></para>
+
+          <para>In case you are not sure if you should use the
+          <literal>import</literal>, or the somewhat similar
+          <literal>include</literal> directive, <link
+          linkend="topic.import_vs_include">then see this</link>.</para>
         </section>
       </section>
 
@@ -21723,6 +21753,16 @@
         <section>
           <title>Description</title>
 
+          <note>
+            <para><emphasis>Using <literal>include</literal> directive is
+            almost always a bad practice</emphasis>, and you should consider
+            using <link linkend="ref.directive.import">the
+            <literal>import</literal> directive</link> instead! Even if using
+            <literal>import</literal> adds some verbosity, on the long run it
+            can pay off. See <link linkend="topic.import_vs_include">the
+            reasons here...</link></para>
+          </note>
+
           <para>You can use it to insert another FreeMarker template file
           (specified by the <literal><replaceable>path</replaceable></literal>
           parameter) into your template. The output from the included template
@@ -22055,6 +22095,73 @@
               </listitem>
             </itemizedlist>
           </section>
+
+          <section xml:id="topic.import_vs_include">
+            <title>Why <literal>import</literal> should be used instead of
+            <literal>include</literal></title>
+
+            <para>Generally, using <link linkend="ref.directive.import">the
+            <literal>import</literal> directive</link> is a better practice
+            than using <literal>include</literal>.</para>
+
+            <para>At first glance, import is only fitting if you have a
+            collection of commonly used macros, functions, and other
+            variables, which you put into a template for reuse. But, people
+            often use <literal>include</literal> to insert a common fragment
+            of output (e.g. page footer) into multiple templates. The
+            <literal>import</literal> directive has no output, so it's clearly
+            not a direct replacement. But, it's usually a better practice to
+            <link linkend="dgui_misc_userdefdir">put those output fragments
+            into macros</link>, as macros can have parameters, or even nested
+            content. If you do that, you have a collection of macros in a
+            template, that you can <literal>import</literal>.</para>
+
+            <para>So if you have collection of macros, functions and other
+            variables in a template, this is why <literal>import</literal> is
+            generally a better choice:</para>
+
+            <itemizedlist>
+              <listitem>
+                <para>Imported templates are processed only when first
+                requested. To compare with <literal>include</literal>, let's
+                say template <literal>top.ftl</literal> includes
+                <literal>commons.ftl</literal>, and
+                <literal>tables.ftl</literal>. If
+                <literal>tables.ftl</literal> also includes
+                <literal>commons.ftl</literal>, then now
+                <literal>commons.ftl</literal> will be processed twice. On the
+                other hand, importing <literal>commons.ftl</literal> for the
+                second time just gives back the namespace that was already
+                initialized during the first import.</para>
+              </listitem>
+
+              <listitem>
+                <para>With imports, each imported template has its own
+                namespace. As they don't just drop everything into a common
+                shared namespace, it's easier to see in templates where a
+                referred variable, or macro/function is coming from.
+                Accidental name clashes are also avoided.</para>
+              </listitem>
+
+              <listitem>
+                <para>If you have several collections of useful
+                macros/functions/constants (say,
+                <literal>commons.ftl</literal>, <literal>form.ftl</literal>,
+                <literal>report.ftl</literal>, etc.), and you decide to
+                auto-import them, but a top-level template usually only uses
+                some of them, you can configure auto-imports to be lazy (i.e.,
+                they on happen when something in their namespace is actually
+                accessed). This is not possible with auto-includes.</para>
+              </listitem>
+
+              <listitem>
+                <para><literal>import</literal> never prints to the output,
+                while <literal>include</literal> might prints unwanted output,
+                like some whitespace that wasn't removed by the automatic
+                whitespace removal.</para>
+              </listitem>
+            </itemizedlist>
+          </section>
         </section>
       </section>
 
@@ -28903,7 +29010,7 @@
             application developers, system administrators, or other highly
             trusted personnel. Consider templates as part of the source code
             just like <literal>*.java</literal> files are. If you still want
-            to allow users to upload templates, here's what to
+            to allow untrusted users to upload templates, here's what to
             consider:</para>
 
             <itemizedlist>
@@ -28912,7 +29019,7 @@
                 (<literal>Configuration.setObjectWrapper</literal>): The
                 data-model might gives access to the public Java API of some
                 objects that you have put into the data-model. By default, for
-                objects that aren't instances of a the bunch of specially
+                objects that aren't instances of the bunch of specially
                 handled types (<literal>String</literal>,
                 <literal>Number</literal>, <literal>Boolean</literal>,
                 <literal>Date</literal>, <literal>Map</literal>,
@@ -28929,7 +29036,7 @@
                 <literal>List</literal>-s, <literal>Array</literal>-s,
                 <literal>String</literal>-s, <literal>Number</literal>-s,
                 <literal>Boolean</literal>-s and <literal>Date</literal>-s.
-                But for many application that's too restrictive, and instead
+                But for many applications that's too restrictive, and instead
                 you have to create a
                 <literal>WhitelistMemberAccessPolicy</literal>, and create a
                 <literal>DefaultObjectWrapper</literal> (or other
@@ -28941,10 +29048,24 @@
                 <literal>ObjectWrapper</literal> implementation of
                 course.)</para>
 
+                <para>Always expect that templates may get some objects that
+                you haven't put into the data-model yourself. Notably,
+                templates can always get a <literal>Locale</literal> object
+                with the <literal>.locale_object</literal> expression. Or the
+                web application framework you are using may exposes some
+                objects, like attributes from the Servlet scopes. Such objects
+                will be still wrapped with the
+                <literal>ObjectWrapper</literal> that you set in the
+                <literal>Configuration</literal>, and this is why it's
+                important to ensure safety on that level. Controlling what
+                objects the template will have access to is hard, but you can
+                control centrally what members of any object they have access
+                to.</para>
+
                 <para>If you are creating <literal>TemplateModel</literal>-s
                 in custom code (instead of the
-                <literal>ObjectWrapper</literal> creating those), be sure you
-                avoid deprecated container constructors like <literal>new
+                <literal>ObjectWrapper</literal> creating those), be sure that
+                you avoid deprecated container constructors like <literal>new
                 SimpleSequence()</literal>, as those will use the also
                 deprecated default object wrapper instance, which doesn't have
                 the same restrictions than the
@@ -28969,18 +29090,39 @@
                 by the <literal>MemberAccessPolicy</literal> also won't be
                 visible with <literal>?api</literal> (assuming you are using a
                 well behaving <literal>ObjectWrapper</literal>, like
-                <literal>DefaultObjectWrapper</literal> is, hopefully.)</para>
+                <literal>DefaultObjectWrapper</literal>.)</para>
 
-                <para>Last not least, some maybe aware of that the standard
-                object wrappers filters out some well known
-                <quote>unsafe</quote> methods, like
+                <para>If you are using the default object wrapper class
+                (<literal>freemarker.template.DefaultObjectWrapper</literal>),
+                or a subclass of it, you should disable the XML (DOM) wrapping
+                feature of it, by overriding <literal>wrapDomNode(Object
+                obj)</literal> so that it does this: <literal>return
+                getModelFactory(obj.getClass()).create(obj, this);</literal>.
+                The problem with the XML wrapping feature, which wraps
+                <literal>org.w3c.dom.Node</literal> objects on special way to
+                make them easier to work with in templates, is that this
+                facility by design lets template authors evaluate arbitrary
+                XPath expressions, and XPath can do too much in certain
+                setups. If you really need the XML wrapping facility, review
+                carefully what XPath expressions are possible in your setup.
+                Also, be sure you don't use the long deprecated, and more
+                dangerous <literal>freemarker.ext.xml</literal> package, only
+                <literal>freemarker.ext.dom</literal>. Also, note that when
+                using the XML wrapping feature, not allowing
+                <literal>org.w3c.dom.Node</literal> methods in the
+                <literal>MemberAccessPolicy</literal> has no effect, since it
+                doesn't expose Java <literal>Node</literal> members to
+                templates directly.</para>
+
+                <para>Last not least, some maybe aware of the historical
+                legacy that standard object wrappers filter out some well
+                known <quote>unsafe</quote> methods, like
                 <literal>System.exit</literal>. Do not ever rely on that,
-                since it only blocks the methods that's in a small predefined
-                list (for some historical reasons). The standard Java API is
-                huge and ever growing, and then there are the 3rd party
-                libraries, and the API-s of your own application. Clearly it's
-                impossible to blacklist all the problematic members in
-                those.</para>
+                since it only blocks the methods from a small predefined list.
+                The standard Java API is huge and ever growing, and then there
+                are the 3rd party libraries, and the API-s of your own
+                application. Clearly it's impossible to blacklist all the
+                problematic members in those.</para>
               </listitem>
 
               <listitem>
@@ -29020,11 +29162,13 @@
                 <literal>TemplateModel</literal>, its static initialization
                 will be run. To avoid these, you should use a
                 <literal>TemplateClassResolver</literal> that restricts the
-                accessible classes (possibly based on which template asks for
-                them), such as
+                accessible classes to the absolute minimum (possibly based on
+                which template asks for them), such as
                 <literal>TemplateClassResolver.ALLOWS_NOTHING_RESOLVER</literal>.
-                Note that if, and only if your
-                <literal>ObjectWrapper</literal> is a
+                Do <emphasis>not</emphasis> use
+                <literal>TemplateClassResolver.SAFER_RESOLVER</literal>, it's
+                not restrictive enough for this purpose! Note that if, and
+                only if your <literal>ObjectWrapper</literal> is a
                 <literal>BeansWrapper</literal> or a subclass of it (typically
                 <literal>DefaultObjectWrapper</literal>), constructors not
                 allowed by the <literal>MemberAccessPolicy</literal> also
@@ -29167,10 +29311,49 @@
     <appendix xml:id="app_versions">
       <title>Version history</title>
 
+      <section xml:id="versions_2_3_31">
+        <title>2.3.31</title>
+
+        <para>Release date: FIXME</para>
+
+        <section>
+          <title>Changes on the Java side</title>
+
+          <itemizedlist>
+            <listitem>
+              <para><link
+              xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-145">FREEMARKER-145</link>:
+              Fixed bug where methods with "overloaded" return type may become
+              inaccessible on Java 9+, if some overriding subclasses are not
+              public. (This is because
+              <literal>java.beans.Introspector</literal> behavior has changed
+              with Java 9.)</para>
+            </listitem>
+
+            <listitem>
+              <para><link
+              xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-133">FREEMARKER-133</link>:
+              Fixed bug where FreeMarker sometimes tries to expose public
+              methods that are defined or overridden in a non-public class, if
+              the non-public class was then extended by a public class.
+              Calling such method just ends up with
+              <literal>IllegalAccessException</literal>, but they shouldn't be
+              exposed on the first place. Furthermore, such a wrong method
+              sometimes replaces the good version of the method, which would
+              be callable. When this glitch happens is somewhat unpredictable,
+              as it also depends on what methods
+              <literal>java.beans.Introspector</literal> exposes (which at
+              very least can depend on the Java version), and in what
+              order.</para>
+            </listitem>
+          </itemizedlist>
+        </section>
+      </section>
+
       <section xml:id="versions_2_3_30">
         <title>2.3.30</title>
 
-        <para>Release date: 2020-02-16 + release process</para>
+        <para>Release date: 2020-03-05</para>
 
         <para>Please note that with this version the minimum required Java
         version was increased from Java 5 to Java 7.</para>
diff --git a/src/test/java/freemarker/ext/beans/Java9InstrospectorBugWorkaround.java b/src/test/java/freemarker/ext/beans/Java9InstrospectorBugWorkaround.java
new file mode 100644
index 0000000..05fa695
--- /dev/null
+++ b/src/test/java/freemarker/ext/beans/Java9InstrospectorBugWorkaround.java
@@ -0,0 +1,38 @@
+/*
+ * 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.ext.beans;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import org.junit.Test;
+
+import freemarker.template.TemplateException;
+import freemarker.test.TemplateTest;
+
+public class Java9InstrospectorBugWorkaround extends TemplateTest {
+
+    @Test
+    public void test() throws IOException, TemplateException {
+        addToDataModel("path", Paths.get("foo", "bar"));
+        assertOutput("<#assign _ = path.parent>", "");
+    }
+
+}
diff --git a/src/test/java/freemarker/ext/beans/MethodUtilTest2.java b/src/test/java/freemarker/ext/beans/MethodUtilTest2.java
new file mode 100644
index 0000000..9723613
--- /dev/null
+++ b/src/test/java/freemarker/ext/beans/MethodUtilTest2.java
@@ -0,0 +1,164 @@
+/*
+ * 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.ext.beans;
+
+import static freemarker.ext.beans._MethodUtil.*;
+import static org.junit.Assert.*;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Test;
+
+public class MethodUtilTest2 {
+
+    @Test
+    public void testGetMethodWithClosestNonSubReturnType1() {
+        List<Method> methods = getMethods(ObjectM.class, ListM.class, CollectionM.class);
+        assertEquals(getMethod(ObjectM.class), getMethodWithClosestNonSubReturnType(Object.class, methods));
+        assertEquals(getMethod(CollectionM.class), getMethodWithClosestNonSubReturnType(Collection.class, methods));
+        assertEquals(getMethod(ListM.class), getMethodWithClosestNonSubReturnType(List.class, methods));
+        assertEquals(getMethod(ListM.class), getMethodWithClosestNonSubReturnType(ArrayList.class, methods));
+        assertEquals(getMethod(ObjectM.class), getMethodWithClosestNonSubReturnType(String.class, methods));
+        assertNull(getMethodWithClosestNonSubReturnType(int.class, methods));
+        assertNull(getMethodWithClosestNonSubReturnType(void.class, methods));
+    }
+
+    @Test
+    public void testGetMethodWithClosestNonSubReturnType2() {
+        List<Method> methods = getMethods(ListM.class, CollectionM.class);
+        assertNull(getMethodWithClosestNonSubReturnType(Object.class, methods));
+        assertEquals(getMethod(CollectionM.class), getMethodWithClosestNonSubReturnType(Collection.class, methods));
+        assertEquals(getMethod(ListM.class), getMethodWithClosestNonSubReturnType(List.class, methods));
+        assertEquals(getMethod(ListM.class), getMethodWithClosestNonSubReturnType(ArrayList.class, methods));
+        assertNull(getMethodWithClosestNonSubReturnType(String.class, methods));
+        assertNull(getMethodWithClosestNonSubReturnType(int.class, methods));
+        assertNull(getMethodWithClosestNonSubReturnType(void.class, methods));
+    }
+
+    @Test
+    public void testGetMethodWithClosestNonSubReturnType3() {
+        List<Method> methods = getMethods(ObjectM.class, SerializableM.class);
+        assertEquals(getMethod(SerializableM.class), getMethodWithClosestNonSubReturnType(String.class, methods));
+        assertEquals(getMethod(SerializableM.class), getMethodWithClosestNonSubReturnType(Serializable.class, methods));
+        assertEquals(getMethod(ObjectM.class), getMethodWithClosestNonSubReturnType(List.class, methods));
+        assertNull(getMethodWithClosestNonSubReturnType(int.class, methods));
+        assertNull(getMethodWithClosestNonSubReturnType(void.class, methods));
+    }
+
+    @Test
+    public void testGetMethodWithClosestNonSubReturnType4() {
+        List<Method> methods = getMethods(ReturnType1M.class, ReturnType2M.class, ObjectM.class);
+        assertEquals(getMethod(ReturnType2M.class), getMethodWithClosestNonSubReturnType(ReturnType3.class, methods));
+        assertEquals(getMethod(ReturnType2M.class), getMethodWithClosestNonSubReturnType(ReturnType2.class, methods));
+        assertEquals(getMethod(ReturnType1M.class), getMethodWithClosestNonSubReturnType(ReturnType1.class, methods));
+        assertEquals(getMethod(ObjectM.class), getMethodWithClosestNonSubReturnType(Serializable.class, methods));
+    }
+
+    @Test
+    public void testGetMethodWithClosestNonSubReturnType5() {
+        List<Method> methods = getMethods(SerializableM.class, ReturnType1M.class);
+        assertEquals(getMethod(ReturnType1M.class), getMethodWithClosestNonSubReturnType(ReturnType3.class, methods));
+    }
+
+    @Test
+    public void testGetMethodWithClosestNonSubReturnType6() {
+        List<Method> methods = getMethods(SerializableM.class);
+        assertEquals(getMethod(SerializableM.class), getMethodWithClosestNonSubReturnType(ReturnType3.class, methods));
+    }
+
+    @Test
+    public void testGetMethodWithClosestNonSubReturnType7() {
+        List<Method> methods = getMethods(IntM.class, VoidM.class, ObjectM.class, CollectionM.class);
+        assertEquals(getMethod(IntM.class), getMethodWithClosestNonSubReturnType(int.class, methods));
+        assertEquals(getMethod(VoidM.class), getMethodWithClosestNonSubReturnType(void.class, methods));
+        assertNull(getMethodWithClosestNonSubReturnType(long.class, methods));
+        assertEquals(getMethod(ObjectM.class), getMethodWithClosestNonSubReturnType(Long.class, methods));
+        assertEquals(getMethod(CollectionM.class), getMethodWithClosestNonSubReturnType(List.class, methods));
+    }
+
+    private static List<Method> getMethods(Class<?>... methodHolders) {
+        List<Method> result = new ArrayList<>();
+        for (Class<?> methodHolder : methodHolders) {
+            result.add(getMethod(methodHolder));
+        }
+        return result;
+    }
+
+    private static Method getMethod(Class<?> methodHolder) {
+        try {
+            return methodHolder.getMethod("m");
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static class ObjectM {
+        public Object m() { return null; }
+    }
+
+    public static class CollectionM {
+        public Collection<?> m() { return null; }
+    }
+
+    public static class ListM {
+        public List<?> m() { return null; }
+    }
+
+    public static class SerializableM {
+        public Serializable m() { return null; }
+    }
+
+    public static class StringM {
+        public String m() { return null; }
+    }
+
+    public static class ReturnType1M {
+        public ReturnType1 m() { return null; }
+    }
+
+    public static class ReturnType2M {
+        public ReturnType2 m() { return null; }
+    }
+
+    public static class ReturnType3M {
+        public ReturnType3 m() { return null; }
+    }
+
+    public static class IntM {
+        public int m() { return 0; }
+    }
+
+    public static class LongM {
+        public long m() { return 0L; }
+    }
+
+    public static class VoidM {
+        public void m() { }
+    }
+
+    public static class ReturnType1 { }
+    public static class ReturnType2 extends ReturnType1 implements Serializable { }
+    public static class ReturnType3 extends ReturnType2 { }
+
+}
\ No newline at end of file
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index 586bd98..9bf0609 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -339,12 +339,14 @@
         
         try {
             new Configuration(new Version(999, 1, 2));
+            fail();
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("upgrade"));
         }
         
         try {
             new Configuration(new Version(2, 2, 2));
+            fail();
         } catch (IllegalArgumentException e) {
             assertThat(e.getMessage(), containsString("2.3.0"));
         }
diff --git a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
index 3334301..b973358 100644
--- a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
+++ b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
@@ -99,6 +99,7 @@
         expected.add(Configuration.VERSION_2_3_27); // no non-BC change in 2.3.28
         expected.add(Configuration.VERSION_2_3_27); // no non-BC change in 2.3.29
         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
 
         List<Version> actual = new ArrayList<>();
         for (int i = _TemplateAPI.VERSION_INT_2_3_0; i <= Configuration.getVersion().intValue(); i++) {