Backported overloaded method BeansWrapper pre-2.3.21-mode backward-compatibility tests, to see if the behavior they expect is really the same as of the real 2.3.20.
diff --git a/ivy.xml b/ivy.xml
index dbea293..994146d 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -125,7 +125,7 @@
     <!-- test -->
     
     <!-- Note: Ant doesn't contain junit.jar anymore, so we add it to conf "test" too. -->
-    <dependency org="junit" name="junit" rev="3.7" conf="build.test->default; test->default" />
+    <dependency org="junit" name="junit" rev="4.11" conf="build.test->default; test->default" />
     
     <!-- docs -->
     
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index f7a0f76..b1e7412 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -1227,7 +1227,7 @@
         if(exposureLevel < EXPOSE_PROPERTIES_ONLY)
         {
             MethodAppearanceDecision decision = new MethodAppearanceDecision();  
-            MethodDescriptor[] mda = beanInfo.getMethodDescriptors();
+            MethodDescriptor[] mda = shortMethodDescriptors(beanInfo.getMethodDescriptors());
             int mdaLength = mda != null ? mda.length : 0;  
             for(int i = mdaLength - 1; i >= 0; --i)
             {
@@ -1281,6 +1281,11 @@
             }
         } // end if(exposureLevel < EXPOSE_PROPERTIES_ONLY)
     }
+    
+    /** As of this writing, this is only used for testing if method order really doesn't mater. */
+    MethodDescriptor[] shortMethodDescriptors(MethodDescriptor[] methodDescriptors) {
+        return methodDescriptors; // do nothing;
+    }
 
     private void addPropertyDescriptorToClassIntrospectionData(PropertyDescriptor pd,
             Class clazz, Map accessibleMethods, Map classMap) {
diff --git a/src/test/java/freemarker/ext/beans/BeansWrapperDesc2003020.java b/src/test/java/freemarker/ext/beans/BeansWrapperDesc2003020.java
new file mode 100644
index 0000000..49b7a58
--- /dev/null
+++ b/src/test/java/freemarker/ext/beans/BeansWrapperDesc2003020.java
@@ -0,0 +1,10 @@
+package freemarker.ext.beans;
+
+
+public class BeansWrapperDesc2003020 extends BeansWrapperWithShortedMethods{
+
+    public BeansWrapperDesc2003020() {
+        super(true);
+    }
+    
+}
diff --git a/src/test/java/freemarker/ext/beans/BeansWrapperInc2003020.java b/src/test/java/freemarker/ext/beans/BeansWrapperInc2003020.java
new file mode 100644
index 0000000..540d327
--- /dev/null
+++ b/src/test/java/freemarker/ext/beans/BeansWrapperInc2003020.java
@@ -0,0 +1,10 @@
+package freemarker.ext.beans;
+
+
+public class BeansWrapperInc2003020 extends BeansWrapperWithShortedMethods {
+
+    public BeansWrapperInc2003020() {
+        super(false);
+    }
+
+}
diff --git a/src/test/java/freemarker/ext/beans/BeansWrapperWithShortedMethods.java b/src/test/java/freemarker/ext/beans/BeansWrapperWithShortedMethods.java
new file mode 100644
index 0000000..56b321a
--- /dev/null
+++ b/src/test/java/freemarker/ext/beans/BeansWrapperWithShortedMethods.java
@@ -0,0 +1,34 @@
+package freemarker.ext.beans;
+
+import java.beans.MethodDescriptor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+
+import freemarker.template.Version;
+
+/**
+ * Used so that the order in which the methods are added to the introspection cache is deterministic. 
+ */
+public abstract class BeansWrapperWithShortedMethods extends BeansWrapper {
+    
+    private final boolean desc;
+
+    public BeansWrapperWithShortedMethods(boolean desc) {
+        this.desc = desc;
+    }
+
+    @Override
+    MethodDescriptor[] shortMethodDescriptors(MethodDescriptor[] methodDescriptors) {
+        ArrayList<MethodDescriptor> ls = new ArrayList<MethodDescriptor>(Arrays.asList(methodDescriptors));
+        Collections.sort(ls, new Comparator<MethodDescriptor>() {
+            public int compare(MethodDescriptor o1, MethodDescriptor o2) {
+                int res = o1.getMethod().toString().compareTo(o2.getMethod().toString());
+                return desc ? -res : res;
+            }
+        });
+        return ls.toArray(new MethodDescriptor[ls.size()]);
+    }
+
+}
diff --git a/src/test/java/freemarker/ext/beans/RationalNumber.java b/src/test/java/freemarker/ext/beans/RationalNumber.java
new file mode 100644
index 0000000..b4aa63d
--- /dev/null
+++ b/src/test/java/freemarker/ext/beans/RationalNumber.java
@@ -0,0 +1,71 @@
+package freemarker.ext.beans;
+
+public final class RationalNumber extends Number {
+    
+    final int divident;
+    final int divisor;
+    
+    public RationalNumber(int divident, int divisor) {
+        this.divident = divident;
+        this.divisor = divisor;
+    }
+
+    @Override
+    public int intValue() {
+        return divident / divisor;
+    }
+
+    @Override
+    public long longValue() {
+        return divident / (long) divisor;
+    }
+
+    @Override
+    public float floatValue() {
+        return (float) (divident / (double) divisor);
+    }
+
+    @Override
+    public double doubleValue() {
+        return divident / (double) divisor;
+    }
+
+    public int getDivident() {
+        return divident;
+    }
+
+    public int getDivisor() {
+        return divisor;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + divident;
+        result = prime * result + divisor;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        RationalNumber other = (RationalNumber) obj;
+        if (divident != other.divident)
+            return false;
+        if (divisor != other.divisor)
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return divident + "/" + divisor;
+    }
+    
+}
\ No newline at end of file
diff --git a/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java b/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
index 01b4e5a..b9c6bab 100644
--- a/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
+++ b/src/test/java/freemarker/test/templatesuite/TemplateTestCase.java
@@ -101,6 +101,7 @@
 import freemarker.test.templatesuite.models.BooleanVsStringMethods;
 import freemarker.test.templatesuite.models.MultiModel1;
 import freemarker.test.templatesuite.models.OverloadedMethods;
+import freemarker.test.templatesuite.models.OverloadedMethods2;
 import freemarker.test.templatesuite.models.VarArgTestModel;
 import freemarker.test.utility.AssertDirective;
 import freemarker.test.utility.AssertEqualsDirective;
@@ -363,6 +364,10 @@
         else if (testName.equals("varargs")) {
           dataModel.put("m", new VarArgTestModel());
         }
+
+        else if (testName.startsWith("overloaded-methods-2-")) {
+            dataModel.put("obj", new OverloadedMethods2());
+        }
         
         else if (testName.startsWith("overloaded-methods-")) {
           dataModel.put("obj", new OverloadedMethods());
@@ -405,6 +410,7 @@
             dataModel.put("beanTrue", new BeansWrapper().wrap(Boolean.TRUE));
             dataModel.put("beanFalse", new BeansWrapper().wrap(Boolean.FALSE));
         }
+        
     }
     
     public void runTest() {
diff --git a/src/test/java/freemarker/test/templatesuite/models/NumberAndStringModel.java b/src/test/java/freemarker/test/templatesuite/models/NumberAndStringModel.java
new file mode 100644
index 0000000..4f7960b
--- /dev/null
+++ b/src/test/java/freemarker/test/templatesuite/models/NumberAndStringModel.java
@@ -0,0 +1,26 @@
+package freemarker.test.templatesuite.models;
+
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateNumberModel;
+import freemarker.template.TemplateScalarModel;
+
+public class NumberAndStringModel implements TemplateNumberModel,
+		TemplateScalarModel {
+	
+	private final String s;
+	
+	public NumberAndStringModel(String s) {
+		super();
+		this.s = s;
+	}
+
+	public String getAsString() throws TemplateModelException {
+		return s;
+	}
+
+	@SuppressWarnings("boxing")
+    public Number getAsNumber() throws TemplateModelException {
+		return s.length();
+	}
+
+}
diff --git a/src/test/java/freemarker/test/templatesuite/models/OverloadedMethods2.java b/src/test/java/freemarker/test/templatesuite/models/OverloadedMethods2.java
new file mode 100644
index 0000000..995828e
--- /dev/null
+++ b/src/test/java/freemarker/test/templatesuite/models/OverloadedMethods2.java
@@ -0,0 +1,580 @@
+package freemarker.test.templatesuite.models;
+
+import java.io.File;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import freemarker.ext.beans.RationalNumber;
+import freemarker.ext.util.WrapperTemplateModel;
+import freemarker.template.AdapterTemplateModel;
+import freemarker.template.TemplateBooleanModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateNumberModel;
+import freemarker.template.utility.StringUtil;
+
+public class OverloadedMethods2 {
+
+    public String mVarargs(String... a1) {
+        StringBuilder sb = new StringBuilder();
+        for (String s : a1) {
+            sb.append(s);
+        }
+        return "mVarargs(String... a1 = " + sb + ")";
+    }
+    
+    public BigInteger bigInteger(BigDecimal n) {
+        return n.toBigInteger();
+    }
+
+    public RationalNumber rational(int a, int b) {
+        return new RationalNumber(a, b);
+    }
+    
+    public String mVarargs(File a1, String... a2) {
+        return "mVarargs(File a1, String... a2)";
+    }
+
+    public NumberAndStringModel getNnS(String s) {
+        return new NumberAndStringModel(s);
+    }
+    
+    public String mNull1(String a1) {
+        return "mNull1(String a1 = " + a1 + ")";
+    }
+
+    public String mNull1(int a1) {
+        return "mNull1(int a1 = " + a1 + ")";
+    }
+    
+    public String mNull2(String a1) {
+        return "mNull2(String a1 = " + a1 + ")";
+    }
+    
+    public String mNull2(Object a1) {
+        return "mNull2(Object a1 = " + a1 + ")";
+    }
+    
+    public String mSpecificity(Object a1, String a2) {
+        return "mSpecificity(Object a1, String a2)";
+    }
+    
+    public String mSpecificity(String a1, Object a2) {
+        return "mSpecificity(String a1, Object a2)";
+    }
+    
+    public String mChar(char a1) {
+        return "mChar(char a1 = " + a1 + ")";
+    }
+    
+    public String mChar(Character a1) {
+        return "mChar(Character a1 = " + a1 + ")";
+    }
+    
+    public String mBoolean(boolean a1) {
+        return "mBoolean(boolean a1 = " + a1 + ")";
+    }
+    
+    public String mBoolean(Boolean a1) {
+        return "mBoolean(Boolean a1 = " + a1 + ")";
+    }
+
+    public int mIntNonOverloaded(int a1) {
+        return a1;
+    }
+
+    public String mIntPrimVSBoxed(int a1) {
+        return "mIntPrimVSBoxed(int a1 = " + a1 + ")";
+    }
+    
+    public String mIntPrimVSBoxed(Integer a1) {
+        return "mIntPrimVSBoxed(Integer a1 = " + a1 + ")";
+    }
+
+    public String mNumPrimVSPrim(short a1) {
+        return "mNumPrimVSPrim(short a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimVSPrim(long a1) {
+        return "mNumPrimVSPrim(long a1 = " + a1 + ")";
+    }
+
+    public String mNumBoxedVSBoxed(Short a1) {
+        return "mNumBoxedVSBoxed(Short a1 = " + a1 + ")";
+    }
+    
+    public String mNumBoxedVSBoxed(Long a1) {
+        return "mNumBoxedVSBoxed(Long a1 = " + a1 + ")";
+    }
+
+    public String mNumUnambigous(Short a1, boolean otherOverload) {
+        return "mmNumUnambigous won't be called";
+    }
+    
+    public String mNumUnambigous(Integer a1) {
+        return "mNumUnambigous(Integer a1 = " + a1 + ")";
+    }
+    
+    public String mNumBoxedAll(Byte a1) {
+        return "mNumBoxedAll(Byte a1 = " + a1 + ")";
+    }
+    
+    public String mNumBoxedAll(Short a1) {
+        return "mNumBoxedAll(Short a1 = " + a1 + ")";
+    }
+
+    public String mNumBoxedAll(Integer a1) {
+        return "mNumBoxedAll(Integer a1 = " + a1 + ")";
+    }
+    
+    public String mNumBoxedAll(Long a1) {
+        return "mNumBoxedAll(Long a1 = " + a1 + ")";
+    }
+    
+    public String mNumBoxedAll(Float a1) {
+        return "mNumBoxedAll(Float a1 = " + a1 + ")";
+    }
+    
+    public String mNumBoxedAll(Double a1) {
+        return "mNumBoxedAll(Double a1 = " + a1 + ")";
+    }
+    
+    public String mNumBoxedAll(BigInteger a1) {
+        return "mNumBoxedAll(BigInteger a1 = " + a1 + ")";
+    }
+    
+    public String mNumBoxedAll(BigDecimal a1) {
+        return "mNumBoxedAll(BigDecimal a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimAll(byte a1) {
+        return "mNumPrimAll(byte a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimAll(short a1) {
+        return "mNumPrimAll(short a1 = " + a1 + ")";
+    }
+
+    public String mNumPrimAll(int a1) {
+        return "mNumPrimAll(int a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimAll(long a1) {
+        return "mNumPrimAll(long a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimAll(float a1) {
+        return "mNumPrimAll(float a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimAll(double a1) {
+        return "mNumPrimAll(double a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimAll(BigInteger a1) {
+        return "mNumPrimAll(BigInteger a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimAll(BigDecimal a1) {
+        return "mNumPrimAll(BigDecimal a1 = " + a1 + ")";
+    }
+
+    
+    public String mNumBoxedAll2nd(Short a1) {
+        return "mNumBoxedAll2nd(Short a1 = " + a1 + ")";
+    }
+
+    public String mNumBoxedAll2nd(Long a1) {
+        return "mNumBoxedAll2nd(Long a1 = " + a1 + ")";
+    }
+    
+    public String mNumBoxedAll2nd(Double a1) {
+        return "mNumBoxedAll2nd(Double a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimAll2nd(short a1) {
+        return "mNumPrimAll2nd(short a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimAll2nd(long a1) {
+        return "mNumPrimAll2nd(long a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimAll2nd(double a1) {
+        return "mNumPrimAll2nd(double a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimFallbackToNumber(long a1) {
+        return "mNumPrimFallbackToNumber(long a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimFallbackToNumber(Number a1) {
+        return "mNumPrimFallbackToNumber(Number a1 = " + a1 + ")";
+    }
+    
+    public String mNumPrimFallbackToNumber(Object a1) {
+        return "mNumPrimFallbackToNumber(Object a1 = " + a1 + ")";
+    }
+    
+    public String mNumBoxedFallbackToNumber(Long a1) {
+        return "mNumBoxedFallbackToNumber(Long a1 = " + a1 + ")";
+    }
+    
+    public String mNumBoxedFallbackToNumber(Number a1) {
+        return "mNumBoxedFallbackToNumber(Number a1 = " + a1 + ")";
+    }
+    
+    public String mNumBoxedFallbackToNumber(Object a1) {
+        return "mNumBoxedFallbackToNumber(Object a1 = " + a1 + ")";
+    }
+
+    public String mDecimalLoss(int a1) {
+        return "mDecimalLoss(int a1 = " + a1 + ")";
+    }
+        
+    public String mDecimalLoss(double a1) {
+        return "mDecimalLoss(double a1 = " + a1 + ")";
+    }
+    
+    public String mNumConversionLoses1(byte i, Object o1, Object o2) {
+        return "byte " + i;
+    }
+    
+    public String mNumConversionLoses1(double i, Object o1, Object o2) {
+        return "double " + i;
+    }
+
+    public String mNumConversionLoses1(Number i, String o1, String o2) {
+        return "Number " + i + " " + i.getClass().getName();
+    }
+
+    public String mNumConversionLoses2(int i, Object o1, Object o2) {
+        return "int " + i;
+    }
+
+    public String mNumConversionLoses2(long i, Object o1, Object o2) {
+        return "long " + i;
+    }
+
+    public String mNumConversionLoses2(Number i, String o1, String o2) {
+        return "Number " + i + " " + i.getClass().getName();
+    }
+
+    public String mNumConversionLoses3(int i, Object o1, Object o2) {
+        return "int " + i;
+    }
+
+    public String mNumConversionLoses3(Serializable i, String o1, String o2) {
+        return "Serializable " + i + " " + i.getClass().getName();
+    }
+    
+    public String nIntAndLong(int i) {
+        return "nIntAndLong(int " + i + ")";
+    }
+    
+    public String nIntAndLong(long i) {
+        return "nIntAndLong(long " + i + ")";
+    }
+
+    public String nIntAndShort(int i) {
+        return "nIntAndShort(int " + i + ")";
+    }
+    
+    public String nIntAndShort(short i) {
+        return "nIntAndShort(short " + i + ")";
+    }
+
+    public String nLongAndShort(long i) {
+        return "nLongAndShort(long " + i + ")";
+    }
+    
+    public String nLongAndShort(short i) {
+        return "nLongAndShort(short " + i + ")";
+    }
+
+    public String varargs1(String s, int... xs) {
+        return "varargs1(String s = " + StringUtil.jQuote(s) + ", int... xs = [" + arrayToString(xs) + "])";
+    }
+
+    public String varargs1(String s, double... xs) {
+        return "varargs1(String s = " + StringUtil.jQuote(s) + ", double... xs = [" + arrayToString(xs) + "])";
+    }
+
+    public String varargs1(String s, Object... xs) {
+        return "varargs1(String s = " + StringUtil.jQuote(s) + ", Object... xs = [" + arrayToString(xs) + "])";
+    }
+
+    public String varargs1(Object s, Object... xs) {
+        return "varargs1(Object s = " + s + ", Object... xs = [" + arrayToString(xs) + "])";
+    }
+
+    public String varargs2(int... xs) {
+        return "varargs2(int... xs = [" + arrayToString(xs) + "])";
+    }
+
+    public String varargs2(double... xs) {
+        return "varargs2(double... xs = [" + arrayToString(xs) + "])";
+    }
+
+    public String varargs3(String... xs) {
+        return "varargs3(String... xs = [" + arrayToString(xs) + "])";
+    }
+
+    public String varargs3(Comparable... xs) {
+        return "varargs3(Comparable... xs = [" + arrayToString(xs) + "])";
+    }
+    
+    public String varargs3(Object... xs) {
+        return "varargs3(Object... xs = [" + arrayToString(xs) + "])";
+    }
+    
+    public String varargs4(Integer... xs) {
+        return "varargs4(Integer... xs = [" + arrayToString(xs) + "])";
+    }
+
+    public String varargs4(int... xs) {
+        return "varargs4(int... xs = [" + arrayToString(xs) + "])";
+    }
+
+    public String varargs5(int... xs) {
+        return "varargs5(int... xs = [" + arrayToString(xs) + "])";
+    }
+    
+    public String varargs5(int a1, int... xs) {
+        return "varargs5(int a1 = " + a1 + ", int... xs = [" + arrayToString(xs) + "])";
+    }
+    
+    public String varargs5(int a1, int a2, int... xs) {
+        return "varargs5(int a1 = " + a1 + ", int a2 = " + a2 + ", int... xs = [" + arrayToString(xs) + "])";
+    }
+
+    public String varargs5(int a1, int a2, int a3, int... xs) {
+        return "varargs5(int a1 = " + a1 + ", int a2 = " + a2 + ", int a3 = " + a3
+                + ", int... xs = [" + arrayToString(xs) + "])";
+    }
+
+    public String varargs6(String a1, int... xs) {
+        return "varargs6(String a1 = " + a1 + ", int... xs = [" + arrayToString(xs) + "])";
+    }
+    
+    public String varargs6(Object a1, int a2, int... xs) {
+        return "varargs6(Object a1 = " + a1 + ", int a2 = " + a2 + ", int... xs = [" + arrayToString(xs) + "])";
+    }
+    
+    public String varargs7(int... xs) {
+        return "varargs7(int... xs = [" + arrayToString(xs) + "])";
+    }
+    
+    public String varargs7(short a1, int... xs) {
+        return "varargs7(short a1 = " + a1 + ", int... xs = [" + arrayToString(xs) + "])";
+    }
+    
+    private String arrayToString(int[] xs) {
+        StringBuilder sb = new StringBuilder();
+        for (int x : xs) {
+            if (sb.length() != 0) sb.append(", ");
+            sb.append(x);
+        }
+        return sb.toString();
+    }
+
+    private String arrayToString(double[] xs) {
+        StringBuilder sb = new StringBuilder();
+        for (double x : xs) {
+            if (sb.length() != 0) sb.append(", ");
+            sb.append(x);
+        }
+        return sb.toString();
+    }
+
+    private String arrayToString(Object[] xs) {
+        StringBuilder sb = new StringBuilder();
+        for (Object x : xs) {
+            if (sb.length() != 0) sb.append(", ");
+            sb.append(x);
+        }
+        return sb.toString();
+    }
+    
+    public String mNullAmbiguous(String s) {
+        return "mNullAmbiguous(String s = " + s + ")";
+    }
+
+    public String mNullAmbiguous(int i) {
+        return "mNullAmbiguous(int i = " + i + ")";
+    }
+
+    public String mNullAmbiguous(File f) {
+        return "mNullAmbiguous(File f = " + f + ")";
+    }
+    
+    public String mNullAmbiguous2(String s) {
+        return "mNullNonAmbiguous(String s = " + s + ")";
+    }
+
+    public String mNullAmbiguous2(File f) {
+        return "mNullAmbiguous(File f = " + f + ")";
+    }
+
+    public String mNullAmbiguous2(Object o) {
+        return "mNullAmbiguous(Object o = " + o + ")";
+    }
+
+    public String mNullNonAmbiguous(String s) {
+        return "mNullNonAmbiguous(String s = " + s + ")";
+    }
+
+    public String mNullNonAmbiguous(int i) {
+        return "mNullNonAmbiguous(int i = " + i + ")";
+    }
+    
+    public String mVarargsIgnoredTail(int i, double... ds) {
+        return "mVarargsIgnoredTail(int i = " + i + ", double... ds = [" + arrayToString(ds) + "])"; 
+    }
+    
+    public String mVarargsIgnoredTail(int... is) {
+        return "mVarargsIgnoredTail(int... is = [" + arrayToString(is) + "])"; 
+    }
+    
+    public String mLowRankWins(int x, int y, Object o) {
+        return "mLowRankWins(int x = " + x + ", int y = " + y + ", Object o = " + o + ")";
+    }
+
+    public String mLowRankWins(Integer x, Integer y, String s) {
+        return "mLowRankWins(Integer x = " + x + ", Integer y = " + y + ", String s = " + s + ")";
+    }
+    
+    public String mRareWrappings(File f, double d1, Double d2, double d3, boolean b) {
+        return "mRareWrappings(File f = " + f + ", double d1 = " + d1 + ", Double d2 = " + d2
+                + ", double d3 = " + d3 + ", b = " + b + ")";
+    }
+
+    public String mRareWrappings(Object o, double d1, Double d2, Double d3, boolean b) {
+        return "mRareWrappings(Object o = " + o + ", double d1 = " + d1 + ", Double d2 = " + d2
+                + ", double d3 = " + d3 + ", b = " + b + ")";
+    }
+
+    public String mRareWrappings(String s, double d1, Double d2, Double d3, boolean b) {
+        return "mRareWrappings(String s = " + s + ", double d1 = " + d1 + ", Double d2 = " + d2
+                + ", double d3 = " + d3 + ", b = " + b + ")";
+    }
+
+    public String mRareWrappings2(String s) {
+        return "mRareWrappings2(String s = " + s + ")";
+    }
+    
+    public String mRareWrappings2(byte b) {
+        return "mRareWrappings2(byte b = " + b + ")";
+    }
+    
+    public File getFile() {
+        return new File("file");
+    }
+
+    public TemplateNumberModel getAdaptedNumber() {
+        return new MyAdapterNumberModel();
+    }
+
+    public TemplateNumberModel getWrapperNumber() {
+        return new MyWrapperNumberModel();
+    }
+
+    public TemplateBooleanModel getStringAdaptedToBoolean() {
+        return new MyStringAdaptedToBooleanModel();
+    }
+    
+    public TemplateBooleanModel getStringAdaptedToBoolean2() {
+        return new MyStringAdaptedToBooleanModel2();
+    }
+    
+    public TemplateBooleanModel getStringWrappedAsBoolean() {
+        return new MyStringWrapperAsBooleanModel();
+    }
+    
+    public TemplateBooleanModel getBooleanWrappedAsAnotherBoolean() {
+        return new MyBooleanWrapperAsAnotherBooleanModel(); 
+    }
+    
+    private static class MyAdapterNumberModel implements TemplateNumberModel, AdapterTemplateModel {
+
+        public Object getAdaptedObject(Class hint) {
+            if (hint == double.class) {
+                return Double.valueOf(123.0001);
+            } else if (hint == Double.class) {
+                return Double.valueOf(123.0002);
+            } else {
+                return Long.valueOf(124L);
+            }
+        }
+
+        public Number getAsNumber() throws TemplateModelException {
+            return Integer.valueOf(122);
+        }
+        
+    }
+    
+    private static class MyWrapperNumberModel implements TemplateNumberModel, WrapperTemplateModel {
+
+        public Number getAsNumber() throws TemplateModelException {
+            return Integer.valueOf(122);
+        }
+
+        public Object getWrappedObject() {
+            return Double.valueOf(123.0001);
+        }
+        
+    }
+    
+    private static class MyStringWrapperAsBooleanModel implements TemplateBooleanModel, WrapperTemplateModel {
+
+        public Object getWrappedObject() {
+            return "yes";
+        }
+
+        public boolean getAsBoolean() throws TemplateModelException {
+            return true;
+        }
+        
+    }
+
+    private static class MyBooleanWrapperAsAnotherBooleanModel implements TemplateBooleanModel, WrapperTemplateModel {
+
+        public Object getWrappedObject() {
+            return Boolean.TRUE;
+        }
+
+        public boolean getAsBoolean() throws TemplateModelException {
+            return false;
+        }
+        
+    }
+    
+    private static class MyStringAdaptedToBooleanModel implements TemplateBooleanModel, AdapterTemplateModel {
+
+        public Object getAdaptedObject(Class hint) {
+            if (hint != Boolean.class && hint != boolean.class) {
+                return "yes";
+            } else {
+                return Boolean.TRUE;
+            }
+        }
+
+        public boolean getAsBoolean() throws TemplateModelException {
+            return false;
+        }
+        
+    }
+
+    private static class MyStringAdaptedToBooleanModel2 implements TemplateBooleanModel, AdapterTemplateModel {
+
+        public Object getAdaptedObject(Class hint) {
+            return "yes";
+        }
+
+        public boolean getAsBoolean() throws TemplateModelException {
+            return true;
+        }
+        
+    }
+    
+}
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-desc-ici-2.3.20.ftl b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-desc-ici-2.3.20.ftl
new file mode 100644
index 0000000..3200723
--- /dev/null
+++ b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-desc-ici-2.3.20.ftl
@@ -0,0 +1,20 @@
+<@assertFails message="no compatible overloaded">${obj.mVarargs('a', obj.getNnS('b'), obj.getNnS('c'))}</@>
+<@assertFails message="no compatible overloaded">${obj.mChar('a')}</@>
+<@assertFails message="no compatible overloaded">${obj.mIntPrimVSBoxed(123?long)}</@>
+<@assertEquals actual=obj.mIntPrimVSBoxed(123?short) expected="mIntPrimVSBoxed(int a1 = 123)" />
+<@assertEquals actual=obj.mIntPrimVSBoxed(123) expected="mIntPrimVSBoxed(int a1 = 123)" />
+<@assertEquals actual=obj.varargs4(1, 2, 3) expected='varargs4(int... xs = [1, 2, 3])' />
+
+<@assertFails message="multiple compatible overloaded">${obj.mVarargsIgnoredTail(1, 2, 3)}</@>
+<@assertFails message="multiple compatible overloaded">${obj.mVarargsIgnoredTail(1, 2, 3.5)}</@>
+
+<@assertEquals actual=obj.mLowRankWins(1, 2, 'a') expected='mLowRankWins(int x = 1, int y = 2, Object o = a)' />
+
+<@assertEquals actual=obj.mRareWrappings(obj.file, obj.adaptedNumber, obj.adaptedNumber, obj.adaptedNumber, obj.stringWrappedAsBoolean)
+               expected='mRareWrappings(File f = file, double d1 = 123.0001, Double d2 = 123.0002, double d3 = 124.0, b = true)' />
+<@assertFails message="no compatible overloaded">${obj.mRareWrappings(obj.stringWrappedAsBoolean, obj.adaptedNumber, obj.adaptedNumber, obj.adaptedNumber, obj.stringAdaptedToBoolean)}</@>
+<@assertFails message="no compatible overloaded">${obj.mRareWrappings(obj.booleanWrappedAsAnotherBoolean, 0, 0, 0, obj.booleanWrappedAsAnotherBoolean)}</@>
+<@assertFails message="no compatible overloaded">${obj.mRareWrappings(obj.adaptedNumber, 0, 0, 0, !obj.booleanWrappedAsAnotherBoolean)}</@>
+<@assertFails message="no compatible overloaded">${obj.mRareWrappings(obj.booleanWrappedAsAnotherBoolean, 0, 0, 0, !obj.stringAdaptedToBoolean)}</@>
+
+<#include 'overloaded-methods-2-ici-2.3.20.ftl'>
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-ici-2.3.20.ftl b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-ici-2.3.20.ftl
new file mode 100644
index 0000000..2a1c34e
--- /dev/null
+++ b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-ici-2.3.20.ftl
@@ -0,0 +1,153 @@
+<#-- The parts of the IcI 2.3.20 tests that give the same result regardless of method introspection order -->
+
+<@assertFails message="no compatible overloaded">${obj.mNull1(null)}</@>
+<@assertEquals actual=obj.mNull1(123) expected="mNull1(int a1 = 123)" />
+<@assertEquals actual=obj.mNull2(null) expected="mNull2(Object a1 = null)" />
+<@assertFails message="no compatible overloaded">${obj.mVarargs('a', null)}</@>
+<@assertFails message="no compatible overloaded">${obj.mVarargs(null, 'a')}</@>
+<@assertFails message="multiple compatible overloaded">${obj.mSpecificity('a', 'b')}</@>
+<@assertFails message="multiple compatible overloaded">${obj.mBoolean(true)}</@>
+
+<@assertEquals actual=obj.mIntNonOverloaded(123?long) expected=123 />
+<@assertEquals actual=obj.mIntNonOverloaded(123) expected=123 />
+<@assertEquals actual=obj.mIntNonOverloaded(123.5) expected=123 />
+<@assertEquals actual=obj.mIntNonOverloaded(2147483648) expected=-2147483648 /> <#-- overflow -->
+<@assertFails message="no compatible overloaded">${obj.mNumBoxedVSBoxed(123.5)}</@>
+<@assertFails message="no compatible overloaded">${obj.mNumBoxedVSBoxed(123?int)}</@>
+<@assertEquals actual=obj.mNumBoxedVSBoxed(123?long) expected="mNumBoxedVSBoxed(Long a1 = 123)" />
+<@assertEquals actual=obj.mNumBoxedVSBoxed(123?short) expected="mNumBoxedVSBoxed(Short a1 = 123)" />
+<@assertEquals 
+    actual=obj.mNumUnambigous(2147483648) expected="mNumUnambigous(Integer a1 = -2147483648)" /> <#-- overflow -->
+
+<@assertFails message="multiple compatible overloaded">${obj.mIntPrimVSBoxed(123?int)}</@>
+
+<@assertEquals actual=obj.mNumPrimVSPrim(123?short) expected="mNumPrimVSPrim(short a1 = 123)" />
+<@assertEquals actual=obj.mNumPrimVSPrim(123?int) expected="mNumPrimVSPrim(long a1 = 123)" />
+<@assertEquals actual=obj.mNumPrimVSPrim(123?long) expected="mNumPrimVSPrim(long a1 = 123)" />
+<@assertFails message="no compatible overloaded">${obj.mNumPrimVSPrim(123?double)}</@>
+<@assertEquals actual=obj.mNumPrimVSPrim(123456) expected="mNumPrimVSPrim(short a1 = -7616)" /> <#-- overflow due to bad choice -->
+
+<@assertEquals actual=obj.mNumPrimAll(123?byte) expected="mNumPrimAll(byte a1 = 123)" />
+<@assertEquals actual=obj.mNumPrimAll(123?short) expected="mNumPrimAll(short a1 = 123)" />
+<@assertEquals actual=obj.mNumPrimAll(123?int) expected="mNumPrimAll(int a1 = 123)" />
+<@assertEquals actual=obj.mNumPrimAll(123?long) expected="mNumPrimAll(long a1 = 123)" />
+<@assertEquals actual=obj.mNumPrimAll(123?float) expected="mNumPrimAll(float a1 = 123.0)" />
+<@assertEquals actual=obj.mNumPrimAll(123?double) expected="mNumPrimAll(double a1 = 123.0)" />
+<@assertFails message="multiple compatible overloaded">${obj.mNumPrimAll(123)}</@>
+<@assertEquals actual=obj.mNumPrimAll(obj.bigInteger(123)) expected="mNumPrimAll(BigInteger a1 = 123)" />
+
+<@assertEquals actual=obj.mNumBoxedAll(123?byte) expected="mNumBoxedAll(Byte a1 = 123)" />
+<@assertEquals actual=obj.mNumBoxedAll(123?short) expected="mNumBoxedAll(Short a1 = 123)" />
+<@assertEquals actual=obj.mNumBoxedAll(123?int) expected="mNumBoxedAll(Integer a1 = 123)" />
+<@assertEquals actual=obj.mNumBoxedAll(123?long) expected="mNumBoxedAll(Long a1 = 123)" />
+<@assertEquals actual=obj.mNumBoxedAll(123?float) expected="mNumBoxedAll(Float a1 = 123.0)" />
+<@assertEquals actual=obj.mNumBoxedAll(123?double) expected="mNumBoxedAll(Double a1 = 123.0)" />
+<@assertEquals actual=obj.mNumBoxedAll(123) expected="mNumBoxedAll(BigDecimal a1 = 123)" />
+<@assertEquals actual=obj.mNumBoxedAll(obj.bigInteger(123)) expected="mNumBoxedAll(BigInteger a1 = 123)" />
+
+<@assertEquals actual=obj.mNumPrimAll2nd(123?byte) expected="mNumPrimAll2nd(short a1 = 123)" />
+<@assertEquals actual=obj.mNumPrimAll2nd(123?short) expected="mNumPrimAll2nd(short a1 = 123)" />
+<@assertEquals actual=obj.mNumPrimAll2nd(123?int) expected="mNumPrimAll2nd(long a1 = 123)" />
+<@assertEquals actual=obj.mNumPrimAll2nd(123?long) expected="mNumPrimAll2nd(long a1 = 123)" />
+<@assertEquals actual=obj.mNumPrimAll2nd(123?float) expected="mNumPrimAll2nd(double a1 = 123.0)" />
+<@assertEquals actual=obj.mNumPrimAll2nd(123?double) expected="mNumPrimAll2nd(double a1 = 123.0)" />
+
+<@assertFails message="no compatible overloaded">${obj.mNumBoxedAll2nd(123?byte)}</@>
+<@assertEquals actual=obj.mNumBoxedAll2nd(123?short) expected="mNumBoxedAll2nd(Short a1 = 123)" />
+<@assertFails message="no compatible overloaded">${obj.mNumBoxedAll2nd(123?int)}</@>
+<@assertEquals actual=obj.mNumBoxedAll2nd(123?long) expected="mNumBoxedAll2nd(Long a1 = 123)" />
+<@assertFails message="no compatible overloaded">${obj.mNumBoxedAll2nd(123?float)}</@>
+<@assertEquals actual=obj.mNumBoxedAll2nd(123?double) expected="mNumBoxedAll2nd(Double a1 = 123.0)" />
+
+<@assertFails message="multiple compatible overloaded">${obj.mNumPrimFallbackToNumber(123?int)}</@>
+<@assertFails message="multiple compatible overloaded">${obj.mNumPrimFallbackToNumber(123?long)}</@>
+<@assertEquals actual=obj.mNumPrimFallbackToNumber(123?double) expected="mNumPrimFallbackToNumber(Number a1 = 123.0)" />
+<@assertFails message="multiple compatible overloaded">${obj.mNumPrimFallbackToNumber(123)}</@>
+<@assertEquals actual=obj.mNumPrimFallbackToNumber(obj.bigInteger(123)) expected="mNumPrimFallbackToNumber(Number a1 = 123)" />
+<@assertEquals actual=obj.mNumPrimFallbackToNumber('x') expected="mNumPrimFallbackToNumber(Object a1 = x)" />
+
+<@assertEquals actual=obj.mNumBoxedFallbackToNumber(123?int) expected="mNumBoxedFallbackToNumber(Number a1 = 123)" />
+<@assertEquals actual=obj.mNumBoxedFallbackToNumber(123?long) expected="mNumBoxedFallbackToNumber(Long a1 = 123)" />
+<@assertEquals actual=obj.mNumBoxedFallbackToNumber(123?double) expected="mNumBoxedFallbackToNumber(Number a1 = 123.0)" />
+<@assertEquals actual=obj.mNumBoxedFallbackToNumber(123) expected="mNumBoxedFallbackToNumber(Number a1 = 123)" />
+<@assertEquals actual=obj.mNumBoxedFallbackToNumber(obj.bigInteger(123)) expected="mNumBoxedFallbackToNumber(Number a1 = 123)" />
+<@assertEquals actual=obj.mNumBoxedFallbackToNumber('x') expected="mNumBoxedFallbackToNumber(Object a1 = x)" />
+
+<@assertEquals actual=obj.mDecimalLoss(1.5) expected="mDecimalLoss(int a1 = 1)" /><#-- Yes, buggy... -->
+<@assertEquals actual=obj.mDecimalLoss(1.5?double) expected="mDecimalLoss(double a1 = 1.5)" />
+
+<#-- BigDecimal conversions chose the smallest target type before IcI 2.3.31, increasing the risk of overflows: -->
+<@assertEquals actual=obj.nIntAndLong(1) expected="nIntAndLong(int 1)" />
+<@assertEquals actual=obj.nIntAndLong(1?long) expected="nIntAndLong(long 1)" />
+<@assertEquals actual=obj.nIntAndShort(1) expected="nIntAndShort(short 1)" />
+<@assertEquals actual=obj.nIntAndShort(1?short) expected="nIntAndShort(short 1)" />
+<@assertEquals actual=obj.nIntAndShort(1?int) expected="nIntAndShort(int 1)" />
+<@assertEquals actual=obj.nLongAndShort(1) expected="nLongAndShort(short 1)" />
+<@assertEquals actual=obj.nLongAndShort(1?short) expected="nLongAndShort(short 1)" />
+<@assertEquals actual=obj.nLongAndShort(1?long) expected="nLongAndShort(long 1)" />
+
+<#-- Usual wrong choice on null: -->
+<@assertEquals actual=obj.varargs1(null, 1, 2, 3.5) expected='varargs1(Object s = null, Object... xs = [1, 2, 3.5])' />
+
+<#-- Some bugs that cause loosing of decimals will occur here... -->
+<@assertFails message="multiple compatible overloaded">${obj.varargs1('s', 1, 2, 3.5)}</@>
+<@assertEquals actual=obj.varargs1('s', 1, 2, 'c') expected='varargs1(String s = "s", Object... xs = [1, 2, c])' />
+<@assertEquals actual=obj.varargs1('s', 1, 'b', 3) expected='varargs1(String s = "s", Object... xs = [1, b, 3])' />
+<@assertEquals actual=obj.varargs1('s', 'a', 2, 3) expected='varargs1(String s = "s", Object... xs = [a, 2, 3])' />
+<@assertFails message="multiple compatible overloaded">${obj.varargs1('s', 1, 2, 3)}</@>
+<@assertFails message="multiple compatible overloaded">${obj.varargs1('s', 1.1, 2.1, 3.1)}</@>
+<@assertEquals actual=obj.varargs1('s', 'a', 'b', 'c') expected='varargs1(String s = "s", Object... xs = [a, b, c])' />
+<@assertFails message="multiple compatible overloaded"><@assertEquals actual=obj.varargs1('s', 1?double, 2?byte, 3?byte) expected='varargs1(String s = "s", int... xs = [1, 2, 3])' /></@>
+<@assertEquals actual=obj.varargs1(0, 1, 2, 3) expected='varargs1(Object s = 0, Object... xs = [1, 2, 3])' />
+<@assertFails message="multiple compatible overloaded">${obj.varargs1('s', 1?double, 2?double, 3?double)}</@>
+<@assertFails message="multiple compatible overloaded">${obj.varargs1('s')}</@>
+
+<@assertEquals actual=obj.varargs2(1, 2.5, 3) expected='varargs2(int... xs = [1, 2, 3])' />
+<@assertEquals actual=obj.varargs2(1, 2.5?double, 3) expected='varargs2(double... xs = [1.0, 2.5, 3.0])' />
+<@assertEquals actual=obj.varargs2(1?int, 2.5?double, 3) expected='varargs2(double... xs = [1.0, 2.5, 3.0])' />
+<@assertEquals actual=obj.varargs2(1?long, 2.5?double, 3) expected='varargs2(double... xs = [1.0, 2.5, 3.0])' />
+<@assertEquals actual=obj.varargs2(1?long, 2?double, 3) expected='varargs2(double... xs = [1.0, 2.0, 3.0])' />
+
+<@assertEquals actual=obj.varargs3(1, 2, 3) expected='varargs3(Comparable... xs = [1, 2, 3])' />
+<@assertEquals actual=obj.varargs3('a', 'b', 'c') expected='varargs3(String... xs = [a, b, c])' />
+<@assertEquals actual=obj.varargs3(1, 'b', 'c') expected='varargs3(Comparable... xs = [1, b, c])' />
+<@assertEquals actual=obj.varargs3('a', 'b', 3) expected='varargs3(Comparable... xs = [a, b, 3])' />
+<@assertEquals actual=obj.varargs3('a', [], 3) expected='varargs3(Object... xs = [a, [], 3])' />
+<@assertEquals actual=obj.varargs3(null, 'b', null) expected='varargs3(Object... xs = [null, b, null])' />
+<@assertEquals actual=obj.varargs3(null, 2, null) expected='varargs3(Object... xs = [null, 2, null])' />
+<@assertEquals actual=obj.varargs3(null, [], null) expected='varargs3(Object... xs = [null, [], null])' />
+<@assertEquals actual=obj.varargs3(null, null, null) expected='varargs3(Object... xs = [null, null, null])' />
+<@assertEquals actual=obj.varargs3() expected='varargs3(String... xs = [])' />
+
+<@assertFails message="no compatible overloaded">${obj.varargs4(null, null, null)}</@>
+
+<@assertFails message="multiple compatible overloaded">${obj.varargs5(1, 2, 3, 4, 5)}</@>
+<@assertFails message="multiple compatible overloaded">${obj.varargs5(1, 2, 3, 4)}</@>
+<@assertFails message="multiple compatible overloaded">${obj.varargs5(1, 2, 3)}</@>
+<@assertFails message="multiple compatible overloaded">${obj.varargs5(1, 2)}</@>
+<@assertFails message="multiple compatible overloaded">${obj.varargs5(1)}</@>
+<@assertEquals actual=obj.varargs5() expected='varargs5(int... xs = [])' />
+
+<@assertEquals actual=obj.varargs6('s', 2) expected='varargs6(String a1 = s, int... xs = [2])' />
+<@assertEquals actual=obj.varargs6('s') expected='varargs6(String a1 = s, int... xs = [])' />
+<@assertEquals actual=obj.varargs6(1, 2) expected='varargs6(Object a1 = 1, int a2 = 2, int... xs = [])' />
+<@assertFails message="no compatible overloaded">${obj.varargs6(1)}</@>
+
+<@assertEquals actual=obj.varargs7(1?int, 2?int) expected='varargs7(int... xs = [1, 2])' />
+<@assertEquals actual=obj.varargs7(1?short, 2?int) expected='varargs7(short a1 = 1, int... xs = [2])' />
+
+<@assertEquals actual=obj.mNullAmbiguous('a') expected='mNullAmbiguous(String s = a)' />
+<@assertEquals actual=obj.mNullAmbiguous(123) expected='mNullAmbiguous(int i = 123)' />
+<@assertEquals actual=obj.mNullAmbiguous(1.9) expected='mNullAmbiguous(int i = 1)' />
+<@assertFails message="no compatible overloaded">${obj.mNullAmbiguous(1?double)}</@>
+<@assertFails message="no compatible overloaded">${obj.mNullAmbiguous(1.9?double)}</@>
+<@assertFails message="no compatible overloaded">${obj.mNullAmbiguous(null)}</@>
+
+<@assertEquals actual=obj.mNullAmbiguous2(null) expected='mNullAmbiguous(Object o = null)' />
+
+<@assertFails message="no compatible overloaded">${obj.mNullNonAmbiguous(null)}</@>
+
+<@assertEquals actual=obj.mRareWrappings(obj.stringAdaptedToBoolean2, obj.wrapperNumber, obj.wrapperNumber, obj.wrapperNumber, obj.stringAdaptedToBoolean2)
+               expected='mRareWrappings(String s = yes, double d1 = 123.0001, Double d2 = 123.0001, double d3 = 123.0001, b = true)' />
+               
+<@assertFails message="no compatible overloaded">${obj.mRareWrappings2(obj.adaptedNumber)}</@>
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-inc-ici-2.3.20.ftl b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-inc-ici-2.3.20.ftl
new file mode 100644
index 0000000..f91d7d8
--- /dev/null
+++ b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-inc-ici-2.3.20.ftl
@@ -0,0 +1,24 @@
+<@assertEquals actual=obj.mVarargs('a', obj.getNnS('b'), obj.getNnS('c')) expected='mVarargs(String... a1 = abc)' />
+<@assertFails message="multiple compatible overload">${obj.mChar('a')}</@>
+<@assertFails message="multiple compatible overload">${obj.mIntPrimVSBoxed(123?long)}</@>
+<@assertFails message="multiple compatible overload">${obj.mIntPrimVSBoxed(123?short)}</@>
+<@assertFails message="multiple compatible overload">${obj.mIntPrimVSBoxed(123)}</@>
+<@assertFails message="multiple compatible overload">${obj.varargs4(1, 2, 3)}</@>
+
+<@assertEquals actual=obj.mVarargsIgnoredTail(1, 2, 3) expected='mVarargsIgnoredTail(int... is = [1, 2, 3])' />
+<@assertEquals actual=obj.mVarargsIgnoredTail(1, 2, 3.5) expected='mVarargsIgnoredTail(int... is = [1, 2, 3])' />
+
+<@assertEquals actual=obj.mLowRankWins(1, 2, 'a') expected='mLowRankWins(Integer x = 1, Integer y = 2, String s = a)' />
+
+<@assertEquals actual=obj.mRareWrappings(obj.file, obj.adaptedNumber, obj.adaptedNumber, obj.adaptedNumber, obj.stringWrappedAsBoolean)
+               expected='mRareWrappings(File f = file, double d1 = 123.0001, Double d2 = 123.0002, double d3 = 123.0002, b = true)' />
+<@assertEquals actual=obj.mRareWrappings(obj.stringWrappedAsBoolean, obj.adaptedNumber, obj.adaptedNumber, obj.adaptedNumber, obj.stringAdaptedToBoolean)
+               expected='mRareWrappings(String s = yes, double d1 = 123.0001, Double d2 = 123.0002, double d3 = 123.0002, b = false)' />
+<@assertEquals actual=obj.mRareWrappings(obj.booleanWrappedAsAnotherBoolean, 0, 0, 0, obj.booleanWrappedAsAnotherBoolean)
+               expected='mRareWrappings(Object o = true, double d1 = 0.0, Double d2 = 0.0, double d3 = 0.0, b = false)' />
+<@assertEquals actual=obj.mRareWrappings(obj.adaptedNumber, 0, 0, 0, !obj.booleanWrappedAsAnotherBoolean)
+               expected='mRareWrappings(Object o = 124, double d1 = 0.0, Double d2 = 0.0, double d3 = 0.0, b = true)' />
+<@assertEquals actual=obj.mRareWrappings(obj.booleanWrappedAsAnotherBoolean, 0, 0, 0, !obj.stringAdaptedToBoolean)
+               expected='mRareWrappings(Object o = true, double d1 = 0.0, Double d2 = 0.0, double d3 = 0.0, b = true)' />
+
+<#include 'overloaded-methods-2-ici-2.3.20.ftl'>
diff --git a/src/test/resources/freemarker/test/templatesuite/testcases.xml b/src/test/resources/freemarker/test/templatesuite/testcases.xml
index 4de5476..c34aa3e 100644
--- a/src/test/resources/freemarker/test/templatesuite/testcases.xml
+++ b/src/test/resources/freemarker/test/templatesuite/testcases.xml
@@ -175,4 +175,10 @@
    <testcase name="string-builtins-ici-2.3.19" nooutput="true">
       <config incompatible_improvements="2.3.19"/>
    </testcase>
+   <testcase name="overloaded-methods-2-inc-ici-2.3.20" nooutput="true">
+      <config object_wrapper="freemarker.ext.beans.BeansWrapperInc2003020"/>
+   </testcase>
+   <testcase name="overloaded-methods-2-desc-ici-2.3.20" nooutput="true">
+      <config object_wrapper="freemarker.ext.beans.BeansWrapperDesc2003020"/>
+   </testcase>
 </testcases>