Added ?with_args_last(args). Also some cleanup in code related to ?with_args.
diff --git a/src/main/java/freemarker/core/BuiltIn.java b/src/main/java/freemarker/core/BuiltIn.java
index da9ffc6..36e74b7 100644
--- a/src/main/java/freemarker/core/BuiltIn.java
+++ b/src/main/java/freemarker/core/BuiltIn.java
@@ -84,11 +84,13 @@
 
     static final Set<String> CAMEL_CASE_NAMES = new TreeSet<String>();
     static final Set<String> SNAKE_CASE_NAMES = new TreeSet<String>();
-    static final int NUMBER_OF_BIS = 287;
+    static final int NUMBER_OF_BIS = 289;
     static final HashMap<String, BuiltIn> BUILT_INS_BY_NAME = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
 
     static final String BI_NAME_SNAKE_CASE_WITH_ARGS = "with_args";
     static final String BI_NAME_CAMEL_CASE_WITH_ARGS = "withArgs";
+    static final String BI_NAME_SNAKE_CASE_WITH_ARGS_LAST = "with_args_last";
+    static final String BI_NAME_CAMEL_CASE_WITH_ARGS_LAST = "withArgsLast";
 
     static {
         // Note that you must update NUMBER_OF_BIS if you add new items here!
@@ -300,7 +302,10 @@
         putBI("url_path", "urlPath", new BuiltInsForStringsEncoding.urlPathBI());
         putBI("values", new BuiltInsForHashes.valuesBI());
         putBI("web_safe", "webSafe", BUILT_INS_BY_NAME.get("html"));  // deprecated; use ?html instead
-        putBI(BI_NAME_SNAKE_CASE_WITH_ARGS, BI_NAME_CAMEL_CASE_WITH_ARGS, new BuiltInsForCallables.with_argsBI());
+        putBI(BI_NAME_SNAKE_CASE_WITH_ARGS, BI_NAME_CAMEL_CASE_WITH_ARGS,
+                new BuiltInsForCallables.with_argsBI());
+        putBI(BI_NAME_SNAKE_CASE_WITH_ARGS_LAST, BI_NAME_CAMEL_CASE_WITH_ARGS_LAST,
+                new BuiltInsForCallables.with_args_lastBI());
         putBI("word_list", "wordList", new BuiltInsForStringsBasic.word_listBI());
         putBI("xhtml", new BuiltInsForStringsEncoding.xhtmlBI());
         putBI("xml", new BuiltInsForStringsEncoding.xmlBI());
diff --git a/src/main/java/freemarker/core/BuiltInsForCallables.java b/src/main/java/freemarker/core/BuiltInsForCallables.java
index acc4245..87d6647 100644
--- a/src/main/java/freemarker/core/BuiltInsForCallables.java
+++ b/src/main/java/freemarker/core/BuiltInsForCallables.java
@@ -40,7 +40,9 @@
 
 class BuiltInsForCallables {
 
-    static class with_argsBI extends BuiltIn {
+    static abstract class AbstractWithArgsBI extends BuiltIn {
+
+        protected abstract boolean isOrderLast();
 
         TemplateModel _eval(Environment env) throws TemplateException {
             TemplateModel model = target.eval(env);
@@ -73,13 +75,13 @@
 
                 Macro.WithArgs withArgs;
                 if (argTM instanceof TemplateSequenceModel) {
-                    withArgs = new Macro.WithArgs((TemplateSequenceModel) argTM);
+                    withArgs = new Macro.WithArgs((TemplateSequenceModel) argTM, isOrderLast());
                 } else if (argTM instanceof TemplateHashModelEx) {
                     if (macroOrFunction.isFunction()) {
                         throw new _TemplateModelException("When applied on a function, ?",  key,
                                 " can't have a hash argument. Use a sequence argument.");
                     }
-                    withArgs = new Macro.WithArgs((TemplateHashModelEx) argTM);
+                    withArgs = new Macro.WithArgs((TemplateHashModelEx) argTM, isOrderLast());
                 } else {
                     throw _MessageUtil.newMethodArgMustBeExtendedHashOrSequnceException("?" + key, 0, argTM);
                 }
@@ -110,11 +112,15 @@
                                 List<TemplateModel> newArgs = new ArrayList<TemplateModel>(
                                         withArgsSize + origArgs.size());
 
+                                if (isOrderLast()) {
+                                    newArgs.addAll(origArgs);
+                                }
                                 for (int i = 0; i < withArgsSize; i++) {
                                     newArgs.add(withArgs.get(i));
                                 }
-
-                                newArgs.addAll(origArgs);
+                                if (!isOrderLast()) {
+                                    newArgs.addAll(origArgs);
+                                }
 
                                 return method.exec(newArgs);
                             }
@@ -126,12 +132,16 @@
                                 List<String> newArgs = new ArrayList<String>(
                                         withArgsSize + origArgs.size());
 
+                                if (isOrderLast()) {
+                                    newArgs.addAll(origArgs);
+                                }
                                 for (int i = 0; i < withArgsSize; i++) {
                                     TemplateModel argVal = withArgs.get(i);
                                     newArgs.add(argValueToString(argVal));
                                 }
-
-                                newArgs.addAll(origArgs);
+                                if (!isOrderLast()) {
+                                    newArgs.addAll(origArgs);
+                                }
 
                                 return method.exec(newArgs);
                             }
@@ -187,33 +197,48 @@
                         public void execute(Environment env, Map origArgs, TemplateModel[] loopVars,
                                 TemplateDirectiveBody body) throws TemplateException, IOException {
                             int withArgsSize = withArgs.size();
+                            // This is unnecessarily big if there are overridden arguments, but we care more about
+                            // avoiding rehashing.
                             Map<String, TemplateModel> newArgs = new LinkedHashMap<String, TemplateModel>(
                                     (withArgsSize + origArgs.size()) * 4 / 3, 1f);
 
                             TemplateHashModelEx2.KeyValuePairIterator withArgsIter =
                                     TemplateModelUtils.getKeyValuePairIterator(withArgs);
-                            while (withArgsIter.hasNext()) {
-                                TemplateHashModelEx2.KeyValuePair spreadArgKVP = withArgsIter.next();
-
-                                TemplateModel argNameTM = spreadArgKVP.getKey();
-                                if (!(argNameTM instanceof TemplateScalarModel)) {
-                                    throw new _TemplateModelException(
-                                            "Expected string keys in the spread args hash, but one of the keys was ",
-                                            new _DelayedAOrAn(new _DelayedFTLTypeDescription(argNameTM)), ".");
+                            if (isOrderLast()) {
+                                newArgs.putAll(origArgs);
+                                while (withArgsIter.hasNext()) {
+                                    TemplateHashModelEx2.KeyValuePair withArgsKVP = withArgsIter.next();
+                                    String argName = getArgumentName(withArgsKVP);
+                                    if (!newArgs.containsKey(argName)) {
+                                        newArgs.put(argName, withArgsKVP.getValue());
+                                    }
                                 }
-                                String argName = EvalUtil.modelToString((TemplateScalarModel) argNameTM, null, null);
-
-                                newArgs.put(argName, spreadArgKVP.getValue());
+                            } else {
+                                while (withArgsIter.hasNext()) {
+                                    TemplateHashModelEx2.KeyValuePair withArgsKVP = withArgsIter.next();
+                                    newArgs.put(getArgumentName(withArgsKVP), withArgsKVP.getValue());
+                                }
+                                newArgs.putAll(origArgs);
                             }
 
-                            newArgs.putAll(origArgs); // TODO Should null replace non-null?
-
                             directive.execute(env, newArgs, loopVars, body);
                         }
+
+                        private String getArgumentName(TemplateHashModelEx2.KeyValuePair withArgsKVP) throws
+                                TemplateModelException {
+                            TemplateModel argNameTM = withArgsKVP.getKey();
+                            if (!(argNameTM instanceof TemplateScalarModel)) {
+                                throw new _TemplateModelException(
+                                        "Expected string keys in the ?", key, "(...) arguments, " +
+                                        "but one of the keys was ",
+                                        new _DelayedAOrAn(new _DelayedFTLTypeDescription(argNameTM)), ".");
+                            }
+                            return EvalUtil.modelToString((TemplateScalarModel) argNameTM, null, null);
+                        }
                     };
                 } else if (argTM instanceof TemplateSequenceModel) {
                     throw new _TemplateModelException("When applied on a directive, ?",  key,
-                            " can't have a sequence argument. Use a hash argument.");
+                            "(...) can't have a sequence argument. Use a hash argument.");
                 } else {
                     throw _MessageUtil.newMethodArgMustBeExtendedHashOrSequnceException("?" + key, 0, argTM);
                 }
@@ -223,4 +248,18 @@
 
     }
 
+    static final class with_argsBI extends AbstractWithArgsBI {
+        @Override
+        protected boolean isOrderLast() {
+            return false;
+        }
+    }
+
+    static final class with_args_lastBI extends AbstractWithArgsBI {
+        @Override
+        protected boolean isOrderLast() {
+            return true;
+        }
+    }
+
 }
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index 8ca29b1..fcce497 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -904,32 +904,31 @@
         int nextPositionalArgToAssignIdx = 0;
 
         // Used for ?with_args(...):
-        Macro.WithArgs withArgs = macro.getWithArgs();
-        if (withArgs != null) {
-            TemplateHashModelEx byNameWithArgs = withArgs.getByName();
-            TemplateSequenceModel byPositionWithArgs = withArgs.getByPosition();
+        WithArgsState withArgsState = getWithArgState(macro);
+        if (withArgsState != null) {
+            TemplateHashModelEx byNameWithArgs = withArgsState.byName;
+            TemplateSequenceModel byPositionWithArgs = withArgsState.byPosition;
 
             if (byNameWithArgs != null) {
-                new HashMap<String, TemplateModel>(byNameWithArgs.size() * 4 / 3, 1f);
-                TemplateHashModelEx2.KeyValuePairIterator namedParamValueOverridesIter =
-                        TemplateModelUtils.getKeyValuePairIterator(byNameWithArgs);
-                while (namedParamValueOverridesIter.hasNext()) {
-                    TemplateHashModelEx2.KeyValuePair defaultArgHashKVP = namedParamValueOverridesIter.next();
+                TemplateHashModelEx2.KeyValuePairIterator withArgsKVPIter
+                        = TemplateModelUtils.getKeyValuePairIterator(byNameWithArgs);
+                while (withArgsKVPIter.hasNext()) {
+                    TemplateHashModelEx2.KeyValuePair withArgKVP = withArgsKVPIter.next();
 
                     String argName;
                     {
-                        TemplateModel argNameTM = defaultArgHashKVP.getKey();
+                        TemplateModel argNameTM = withArgKVP.getKey();
                         if (!(argNameTM instanceof TemplateScalarModel)) {
                             throw new _TemplateModelException(
-                                    "Expected string keys in the spread args hash, but one of the keys was ",
+                                    "Expected string keys in the \"with args\" hash, but one of the keys was ",
                                     new _DelayedAOrAn(new _DelayedFTLTypeDescription(argNameTM)), ".");
                         }
                         argName = EvalUtil.modelToString((TemplateScalarModel) argNameTM, null, null);
                     }
 
-                    TemplateModel argValue = defaultArgHashKVP.getValue();
+                    TemplateModel argValue = withArgKVP.getValue();
                     // What if argValue is null? It still has to occur in the named catch-all parameter, to be similar
-                    // to <@macroWithCatchAll a=null b=null />, that will also add the keys to the catch-all hash.
+                    // to <@macroWithCatchAll a=null b=null />, which will also add the keys to the catch-all hash.
                     // Similarly, we also still fail if the name is not declared.
                     final boolean isArgNameDeclared = macro.hasArgNamed(argName);
                     if (isArgNameDeclared) {
@@ -938,39 +937,67 @@
                         if (namedCatchAllParamValue == null) {
                             namedCatchAllParamValue = initNamedCatchAllParameter(macroCtx, catchAllParamName);
                         }
-                        namedCatchAllParamValue.put(argName, argValue);
+                        if (!withArgsState.orderLast) {
+                            namedCatchAllParamValue.put(argName, argValue);
+                        } else {
+                            List<NameValuePair> orderLastByNameCatchAll = withArgsState.orderLastByNameCatchAll;
+                            if (orderLastByNameCatchAll == null) {
+                                orderLastByNameCatchAll = new ArrayList<NameValuePair>();
+                                withArgsState.orderLastByNameCatchAll = orderLastByNameCatchAll;
+                            }
+                            orderLastByNameCatchAll.add(new NameValuePair(argName, argValue));
+                        }
                     } else {
                         throw newUndeclaredParamNameException(macro, argName);
                     }
-                }
+                } // while (withArgsKVPIter.hasNext())
             } else if (byPositionWithArgs != null) {
-                String[] argNames = macro.getArgumentNamesInternal();
-                final int argsCnt = byPositionWithArgs.size();
-                if (argNames.length < argsCnt && catchAllParamName == null) {
-                    throw newTooManyArgumentsException(macro, argNames, argsCnt);
-                }
-                for (int i = 0; i < argsCnt; i++) {
-                    TemplateModel argValue = byPositionWithArgs.get(i);
-                    try {
-                        if (nextPositionalArgToAssignIdx < argNames.length) {
-                            String argName = argNames[nextPositionalArgToAssignIdx++];
-                            macroCtx.setLocalVar(argName, argValue);
-                        } else {
-                            if (positionalCatchAllParamValue == null) {
-                                positionalCatchAllParamValue = initPositionalCatchAllParameter(macroCtx, catchAllParamName);
+                if (!withArgsState.orderLast) { // ?withArgs
+                    String[] argNames = macro.getArgumentNamesNoCopy();
+                    final int argsCnt = byPositionWithArgs.size();
+                    if (argNames.length < argsCnt && catchAllParamName == null) {
+                        throw newTooManyArgumentsException(macro, argNames, argsCnt);
+                    }
+                    for (int argIdx = 0; argIdx < argsCnt; argIdx++) {
+                        TemplateModel argValue = byPositionWithArgs.get(argIdx);
+                        try {
+                            if (nextPositionalArgToAssignIdx < argNames.length) {
+                                String argName = argNames[nextPositionalArgToAssignIdx++];
+                                macroCtx.setLocalVar(argName, argValue);
+                            } else {
+                                if (positionalCatchAllParamValue == null) {
+                                    positionalCatchAllParamValue = initPositionalCatchAllParameter(macroCtx, catchAllParamName);
+                                }
+                                positionalCatchAllParamValue.add(argValue);
                             }
-                            positionalCatchAllParamValue.add(argValue);
+                        } catch (RuntimeException re) {
+                            throw new _MiscTemplateException(re, this);
                         }
-                    } catch (RuntimeException re) {
-                        throw new _MiscTemplateException(re, this);
+                    }
+                } else { // ?withArgsLast
+                    if (namedArgs != null && !namedArgs.isEmpty() && byPositionWithArgs.size() != 0) {
+                        // Unlike with ?withArgs, here we can't know in general which argument byPositionWithArgs[0]
+                        // meant to refer to, as the named arguments have already taken some indexes.
+                        throw new _MiscTemplateException("Call can't pass parameters by name, as there's " +
+                                "\"with args last\" in effect that specifies parameters by position.");
+                    }
+                    if (catchAllParamName == null) {
+                        // To fail before Expression-s for some normal arguments are evaluated:
+                        int totalPositionalArgCnt =
+                                (positionalArgs != null ? positionalArgs.size() : 0) + byPositionWithArgs.size();
+                        if (totalPositionalArgCnt > macro.getArgumentNamesNoCopy().length) {
+                            throw newTooManyArgumentsException(macro, macro.getArgumentNamesNoCopy(), totalPositionalArgCnt);
+                        }
                     }
                 }
             }
-        }
+        } // if (withArgsState != null)
 
         if (namedArgs != null) {
             if (catchAllParamName != null && namedCatchAllParamValue == null && positionalCatchAllParamValue == null) {
-                if (namedArgs.isEmpty() && withArgs != null && withArgs.getByPosition() != null) {
+                // If a macro call has no argument (like <@m />), before 2.3.30 we assumed it's a by-name call. But now
+                // if we have ?with_args(args), its argument type decides if the call is by-name or by-position.
+                if (namedArgs.isEmpty() && withArgsState != null && withArgsState.byPosition != null) {
                     positionalCatchAllParamValue = initPositionalCatchAllParameter(macroCtx, catchAllParamName);
                 } else {
                     namedCatchAllParamValue = initNamedCatchAllParameter(macroCtx, catchAllParamName);
@@ -998,14 +1025,14 @@
             }
         } else if (positionalArgs != null) {
             if (catchAllParamName != null && positionalCatchAllParamValue == null && namedCatchAllParamValue == null) {
-                if (positionalArgs.isEmpty() && withArgs != null && withArgs.getByName() != null) {
+                if (positionalArgs.isEmpty() && withArgsState != null && withArgsState.byName != null) {
                     namedCatchAllParamValue = initNamedCatchAllParameter(macroCtx, catchAllParamName);
                 } else {
                     positionalCatchAllParamValue = initPositionalCatchAllParameter(macroCtx, catchAllParamName);
                 }
             }
 
-            String[] argNames = macro.getArgumentNamesInternal();
+            String[] argNames = macro.getArgumentNamesNoCopy();
             final int argsCnt = positionalArgs.size();
             final int argsWithWithArgsCnt = argsCnt + nextPositionalArgToAssignIdx;
             if (argNames.length < argsWithWithArgsCnt && positionalCatchAllParamValue == null) {
@@ -1017,18 +1044,72 @@
             }
             for (int srcPosArgIdx = 0; srcPosArgIdx < argsCnt; srcPosArgIdx++) {
                 Expression argValueExp = positionalArgs.get(srcPosArgIdx);
-                TemplateModel argValue = argValueExp.eval(this);
+                TemplateModel argValue;
                 try {
-                    if (nextPositionalArgToAssignIdx < argNames.length) {
-                        String argName = argNames[nextPositionalArgToAssignIdx++];
-                        macroCtx.setLocalVar(argName, argValue);
-                    } else {
-                        positionalCatchAllParamValue.add(argValue);
-                    }
-                } catch (RuntimeException re) {
-                    throw new _MiscTemplateException(re, this);
+                    argValue = argValueExp.eval(this);
+                } catch (RuntimeException e) {
+                    throw new _MiscTemplateException(e, this);
+                }
+                if (nextPositionalArgToAssignIdx < argNames.length) {
+                    String argName = argNames[nextPositionalArgToAssignIdx++];
+                    macroCtx.setLocalVar(argName, argValue);
+                } else {
+                    positionalCatchAllParamValue.add(argValue);
                 }
             }
+        } // else if (positionalArgs != null)
+
+        if (withArgsState != null && withArgsState.orderLast) {
+            if (withArgsState.orderLastByNameCatchAll != null) {
+                for (NameValuePair nameValuePair : withArgsState.orderLastByNameCatchAll) {
+                    if (!namedCatchAllParamValue.containsKey(nameValuePair.name)) {
+                        namedCatchAllParamValue.put(nameValuePair.name, nameValuePair.value);
+                    }
+                }
+            } else if (withArgsState.byPosition != null) {
+                TemplateSequenceModel byPosition = withArgsState.byPosition;
+                int withArgCnt = byPosition.size();
+                String[] argNames = macro.getArgumentNamesNoCopy();
+                for (int withArgIdx = 0; withArgIdx < withArgCnt; withArgIdx++) {
+                    TemplateModel withArgValue = byPosition.get(withArgIdx);
+                    if (nextPositionalArgToAssignIdx < argNames.length) {
+                        String argName = argNames[nextPositionalArgToAssignIdx++];
+                        macroCtx.setLocalVar(argName, withArgValue);
+                    } else {
+                        // It was checked much earlier that we don't have too many arguments, so this must work:
+                        positionalCatchAllParamValue.add(withArgValue);
+                    }
+                }
+            }
+        }
+    }
+
+    private static WithArgsState getWithArgState(Macro macro) {
+        Macro.WithArgs withArgs = macro.getWithArgs();
+        return withArgs == null ? null : new WithArgsState(withArgs.getByName(), withArgs.getByPosition(),
+                withArgs.isOrderLast());
+    }
+
+    private static final class WithArgsState {
+        private final TemplateHashModelEx byName;
+        private final TemplateSequenceModel byPosition;
+        private final boolean orderLast;
+        private List<NameValuePair> orderLastByNameCatchAll;
+
+        public WithArgsState(TemplateHashModelEx byName, TemplateSequenceModel byPosition, boolean orderLast) {
+            this.byName = byName;
+            this.byPosition = byPosition;
+            this.orderLast = orderLast;
+        }
+    }
+
+    private static final class NameValuePair {
+        private final String name;
+        private final TemplateModel value;
+
+        public NameValuePair(String name, TemplateModel value) {
+            this.name = name;
+            this.value = value;
         }
     }
 
@@ -1057,7 +1138,7 @@
         return new _MiscTemplateException(this,
                 (macro.isFunction() ? "Function " : "Macro "), new _DelayedJQuote(macro.getName()),
                 " has no parameter with name ", new _DelayedJQuote(argName), ". Valid parameter names are: "
-                , new _DelayedJoinWithComma(macro.getArgumentNames()));
+                , new _DelayedJoinWithComma(macro.getArgumentNamesNoCopy()));
     }
 
     private _MiscTemplateException newBothNamedAndPositionalCatchAllParamsException(Macro macro) {
diff --git a/src/main/java/freemarker/core/Macro.java b/src/main/java/freemarker/core/Macro.java
index d1a81d0..4e564ef 100644
--- a/src/main/java/freemarker/core/Macro.java
+++ b/src/main/java/freemarker/core/Macro.java
@@ -115,16 +115,24 @@
     public String getCatchAll() {
         return catchAllParamName;
     }
-    
+
+    /**
+     * Returns a new copy of the array that stored the names of arguments declared in this macro or function.
+     */
     public String[] getArgumentNames() {
         return paramNames.clone();
     }
 
-    String[] getArgumentNamesInternal() {
+    String[] getArgumentNamesNoCopy() {
         return paramNames;
     }
 
-    boolean hasArgNamed(String name) {
+    /**
+     * Returns if the macro or function has a parameter called as the argument.
+     *
+     * @since 2.3.30
+     */
+    public boolean hasArgNamed(String name) {
         return paramNamesWithDefault.containsKey(name);
     }
     
@@ -480,15 +488,18 @@
     static final class WithArgs {
         private final TemplateHashModelEx byName;
         private final TemplateSequenceModel byPosition;
+        private final boolean orderLast;
 
-        WithArgs(TemplateHashModelEx byName) {
+        WithArgs(TemplateHashModelEx byName, boolean orderLast) {
             this.byName = byName;
             this.byPosition = null;
+            this.orderLast = orderLast;
         }
 
-        WithArgs(TemplateSequenceModel byPosition) {
+        WithArgs(TemplateSequenceModel byPosition, boolean orderLast) {
             this.byName = null;
             this.byPosition = byPosition;
+            this.orderLast = orderLast;
         }
 
         public TemplateHashModelEx getByName() {
@@ -498,6 +509,10 @@
         public TemplateSequenceModel getByPosition() {
             return byPosition;
         }
+
+        public boolean isOrderLast() {
+            return orderLast;
+        }
     }
     
 }
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index dc47af5..d9717e1 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -13183,6 +13183,11 @@
 
           <listitem>
             <para><link
+            linkend="ref_builtin_with_args_last">with_args_last</link></para>
+          </listitem>
+
+          <listitem>
+            <para><link
             linkend="ref_builtin_word_list">word_list</link></para>
           </listitem>
 
@@ -19877,6 +19882,152 @@
           there:</para>
 
           <programlisting role="template">&lt;@myMacro?with_args({'a': 1})&gt;...&lt;/<emphasis>@myMacro</emphasis>&gt;</programlisting>
+
+          <para>Note that as far as the order of arguments is concerned,
+          arguments coming from
+          <literal>with_args(<replaceable>...</replaceable>)</literal> are
+          added before the arguments specified in the call to the returned
+          directive/function/method. In some use cases it's more desirable to
+          add them at the end instead, in which case use the <link
+          linkend="ref_builtin_with_args_last"><literal>with_args_last</literal>
+          built-in</link>.</para>
+        </section>
+
+        <section xml:id="ref_builtin_with_args_last">
+          <title>with_args_last</title>
+
+          <note>
+            <para>This built-in is available since 2.3.30</para>
+          </note>
+
+          <para>Same as <link
+          linkend="ref_builtin_with_args"><literal>with_args</literal></link>,
+          but if the order of the arguments in resulting final argument list
+          may differs (but not the values in it). This only matters if you
+          pass parameters by position (typically, when calling functions or
+          methods), or when there's catch-all argument.</para>
+
+          <para>A typical example with positional arguments is when you want
+          to add the dynamic argument to the end of the parameter list:</para>
+
+          <programlisting role="template">&lt;#function f a b c d&gt;
+  &lt;#return "a=${a}, b=${b}, c=${c}, d=${d}"&gt;
+&lt;/#function&gt;
+
+&lt;#assign dynamicArgs=[3, 4]&gt;
+
+with_args:
+${f?with_args(dynamicArgs)(1, 2)}
+
+with_args_last:
+${f?with_args_last(dynamicArgs)(1, 2)}</programlisting>
+
+          <programlisting role="output">with_args:
+a=3, b=4, c=1, d=2
+
+with_args_last:
+a=1, b=2, c=3, d=4</programlisting>
+
+          <para>In the case of name arguments, while the primary mean of
+          identifying an argument is the its name, catch-all arguments
+          (<literal>others...</literal> below) still have an order:</para>
+
+          <programlisting role="template">&lt;#macro m a b others...&gt;
+  a=${a}
+  b=${b}
+  others:
+  &lt;#list others as k, v&gt;
+    ${k} = ${v}
+  &lt;/#list&gt;
+&lt;/#macro&gt;
+
+&lt;#assign dynamicArgs={'e': 5, 'f': 6}&gt;
+
+with_args:
+&lt;@m?with_args(dynamicArgs) a=1 b=2 c=3 d=4 /&gt;
+
+with_args_last:
+&lt;@m?with_args_last(dynamicArgs) a=1 b=2 c=3 d=4 /&gt;</programlisting>
+
+          <programlisting role="output">with_args:
+  a=1
+  b=2
+  others:
+    e = 5
+    f = 6
+    c = 3
+    d = 4
+
+with_args_last:
+  a=1
+  b=2
+  others:
+    c = 3
+    d = 4
+    e = 5
+    f = 6</programlisting>
+
+          <para>If you specify a named parameter that are not catch-all, so
+          they are declared in the <literal>macro</literal> tag (as
+          <literal>a</literal> and <literal>b</literal> below), then
+          <literal>with_args</literal> and <literal>with_args_last</literal>
+          are no different, since the argument order is specified by the macro
+          definition, not the macro call:</para>
+
+          <programlisting role="template">&lt;#macro m a=0 b=0&gt;
+  &lt;#-- We use .args to demonstrate the ordering of a and b: --&gt;
+  &lt;#list .args as k, v&gt;
+    ${k} = ${v}
+  &lt;/#list&gt;
+&lt;/#macro&gt;
+
+&lt;#assign dynamicArgs={'b': 1}&gt;
+
+with_args:
+&lt;@m?with_args(dynamicArgs) a=1 /&gt;
+
+with_args_last:
+&lt;@m?with_args_last(dynamicArgs) a=1 /&gt;</programlisting>
+
+          <programlisting role="output">with_args:
+    a = 1
+    b = 1
+
+with_args_last:
+    a = 1
+    b = 1</programlisting>
+
+          <para>If both the macro or directive call, and the
+          <literal>with_args_last</literal> argument specifies named catch-all
+          argument with the same name (like <literal>b</literal> below), then
+          the placement of those parameters is decide by the macro/directive
+          call:</para>
+
+          <programlisting role="template">&lt;#macro m others...&gt;
+  &lt;#list others as k, v&gt;
+    ${k} = ${v}
+  &lt;/#list&gt;
+&lt;/#macro&gt;
+
+&lt;#assign dynamicArgs={'b': 0, 'd': 4}&gt;
+
+with_args:
+&lt;@m?with_args(dynamicArgs) a=1 b=2 c=3 /&gt;
+
+with_args_last:
+&lt;@m?with_args_last(dynamicArgs) a=1 b=2 c=3 /&gt;</programlisting>
+
+          <programlisting role="output">with_args:
+<emphasis>    b = 2
+    d = 4
+</emphasis>    a = 1
+    c = 3
+
+with_args_last:
+    a = 1
+<emphasis>    b = 2
+</emphasis>    c = 3
+<emphasis>    d = 4</emphasis></programlisting>
         </section>
       </section>
     </chapter>
@@ -28988,8 +29139,10 @@
               xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-107">FREEMARKER-107</link>:
               Added
               <literal>?<replaceable>with_args</replaceable>(dynamicArguments)</literal>
+              and
+              <literal>?<replaceable>with_args_last</replaceable>(dynamicArguments)</literal>
               to add parameters dynamically to directive (like macro),
-              function and method calls. Actually, this built-in returns
+              function and method calls. Actually, this built-in returns a
               directive or macro or function that has different parameter
               defaults. <link linkend="ref_builtin_with_args">See more
               here...</link></para>
diff --git a/src/test/java/freemarker/core/WithArgsBuiltInTest.java b/src/test/java/freemarker/core/WithArgsBuiltInTest.java
index 3712509..71366e6 100644
--- a/src/test/java/freemarker/core/WithArgsBuiltInTest.java
+++ b/src/test/java/freemarker/core/WithArgsBuiltInTest.java
@@ -20,6 +20,7 @@
 package freemarker.core;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -42,7 +43,7 @@
 
 public class WithArgsBuiltInTest extends TemplateTest {
 
-    private static final String PRINT_O = "o=<#if o?isSequence>[${o?join(', ')}]" +
+    private static final String PRINT_O = "o=<#if o?isSequence>[<#list o as v>${v!'null'}<#sep>, </#list>]" +
             "<#else>{<#list o as k,v>${k}=${v!'null'}<#sep>, </#list>}" +
             "</#if>";
 
@@ -179,10 +180,10 @@
     @Test
     public void testNullsWithMacroWithPositionalWithArgs() throws Exception {
         // Null-s in ?withArgs should behave similarly as if they were given directly as argument.
-        assertOutput("<@mCAO 1 null null 4 />", "o=[1, 4]"); // [FM3] Should be: 1, null, null, 4
+        assertOutput("<@mCAO 1 null null 4 />", "o=[1, null, null, 4]");
         addToDataModel("args", Arrays.asList(1, null, null, 4));
-        assertOutput("<@mCAO?withArgs(args) />", "o=[1, 4]"); // [FM3] See above
-        assertOutput("<@mCAO?withArgs(args) null 5 6 />", "o=[1, 4, 5, 6]"); // [FM3] See above
+        assertOutput("<@mCAO?withArgs(args) />", "o=[1, null, null, 4]");
+        assertOutput("<@mCAO?withArgs(args) null 5 6 />", "o=[1, null, null, 4, null, 5, 6]");
     }
 
     @Test
@@ -216,10 +217,10 @@
     @Test
     public void testNullsWithFunction() throws Exception {
         // Null-s in ?withArgs should behave similarly as if they were given directly as argument.
-        assertOutput("${fCAO(1, null, null, 4)}", "o=[1, 4]"); // [FM3] Should be: 1, null, null, 4
+        assertOutput("${fCAO(1, null, null, 4)}", "o=[1, null, null, 4]");
         addToDataModel("args", Arrays.asList(1, null, null, 4));
-        assertOutput("${fCAO?withArgs(args)()}", "o=[1, 4]"); // [FM3] See above
-        assertOutput("${fCAO?withArgs(args)(null, 5, 6)}", "o=[1, 4, 5, 6]"); // [FM3] See above
+        assertOutput("${fCAO?withArgs(args)()}", "o=[1, null, null, 4]");
+        assertOutput("${fCAO?withArgs(args)(null, 5, 6)}", "o=[1, null, null, 4, null, 5, 6]");
     }
 
     @Test
@@ -322,6 +323,18 @@
         assertOutput("${obj.mNullable?withArgs(args)()}", "null, 2, null");
     }
 
+    @Test
+    public void testMethodWithArgsLast() throws IOException, TemplateException {
+        addToDataModel("obj", new MethodHolder());
+        assertOutput("${obj.m3p?withArgsLast([1, 2, 3])()}", "1, 2, 3");
+        assertOutput("${obj.m3p?withArgsLast([1, 2])(3)}", "3, 1, 2");
+        assertOutput("${obj.m3p?withArgsLast([1])(2, 3)}", "2, 3, 1");
+        assertOutput("${obj.m3p?withArgsLast([])(1, 2, 3)}", "1, 2, 3");
+
+        addToDataModel("args", Arrays.asList(null, 2));
+        assertOutput("${obj.mNullable?withArgsLast(args)(1)}", "1, null, 2");
+    }
+
     public static class MethodHolder {
         public String m3p(int a, int b, int c) {
             return a + ", " + b + ", " + c;
@@ -400,6 +413,126 @@
                 "{a=null, b=22, c=null, e=6, d=55}{}");
     }
 
+    @Test
+    public void testTemplateDirectiveModelWithArgsLast() throws IOException, TemplateException {
+        addToDataModel("directive", new TestTemplateDirectiveModel());
+
+        Map<String, Integer> args = new LinkedHashMap<String, Integer>();
+        args.put("a", null);
+        args.put("b", 2);
+        args.put("c", 3);
+        args.put("e", 6);
+        args.put("f", 7);
+        args.put("g", null);
+        addToDataModel("args", args);
+
+        assertOutput("<@directive?withArgsLast(args) b=22 c=null d=55 />",
+                "{b=22, c=null, d=55, a=null, e=6, f=7, g=null}{}");
+
+        assertOutput("<@directive?withArgsLast({}) b=22 c=null d=55 />",
+                "{b=22, c=null, d=55}{}");
+
+        assertOutput("<@directive?withArgsLast(args) />",
+                "{a=null, b=2, c=3, e=6, f=7, g=null}{}");
+    }
+
+    @Test
+    public void testMacroWithArgsLastNamed() throws IOException, TemplateException {
+        assertOutput("<@m?withArgsLast({'a': 1, 'b': 2}) />", "a=1; b=2; c=d3");
+        assertOutput("<@m?withArgsLast({'b': 2}) a=1 />", "a=1; b=2; c=d3");
+        assertOutput("<@m?withArgsLast({}) a=1 b=2 />", "a=1; b=2; c=d3");
+
+        assertOutput("<@m?withArgsLast({'a': 1, 'b': 2, 'c': 3}) />", "a=1; b=2; c=3");
+        assertOutput("<@m?withArgsLast({'b': 2}) a=1 c=3 />", "a=1; b=2; c=3");
+        assertOutput("<@m?withArgsLast({'c': 3}) a=1 b=2 />", "a=1; b=2; c=3");
+        assertOutput("<@m?withArgsLast({}) a=1 b=2 c=3 />", "a=1; b=2; c=3");
+
+        assertOutput("<@m?withArgsLast({'b': 2}) 1 />", "a=1; b=2; c=d3");
+        assertOutput("<@m?withArgsLast({'c': 3}) 1 2 />", "a=1; b=2; c=3");
+        assertOutput("<@m?withArgsLast({'b': 22, 'c': 3}) 1 2 />", "a=1; b=2; c=3");
+
+        assertOutput("<@mCA?withArgsLast({'a': 1, 'b': 2, 'c': 3, 'd': 4}) />", "a=1; b=2; o={c=3, d=4}");
+        assertOutput("<@mCA?withArgsLast({'b': 2, 'c': 3, 'd': 4}) a=1 />", "a=1; b=2; o={c=3, d=4}");
+        assertOutput("<@mCA?withArgsLast({'c': 3, 'd': 4}) a=1 b=2 />", "a=1; b=2; o={c=3, d=4}");
+        assertOutput("<@mCA?withArgsLast({'d': 4}) a=1 b=2 c=3 />", "a=1; b=2; o={c=3, d=4}");
+        assertOutput("<@mCA?withArgsLast({}) a=1 b=2 c=3 d=4 />", "a=1; b=2; o={c=3, d=4}");
+
+        assertOutput("<@mCA?withArgsLast({'a': 11}) 1 2 />", "a=1; b=2; o=[]");
+        assertOutput("<@mCA?withArgsLast({'a': 11, 'c': 3}) 1 2 />", "a=1; b=2; o={c=3}");
+        assertErrorContains("<@mCA?withArgsLast({'a': 11, 'c': 3}) 1 2 3 />", "both named and positional", "catch-all");
+        assertOutput("<@mCA?withArgsLast({'a': 11, 'b': 22}) 1 2 3 />", "a=1; b=2; o=[3]");
+
+        assertOutput("<@mCAO?withArgsLast({'a': 1, 'b': 2}) />", "o={a=1, b=2}");
+        assertOutput("<@mCAO?withArgsLast({'b': 2}) a=1 />", "o={a=1, b=2}");
+        assertOutput("<@mCAO?withArgsLast({}) a=1 b=2 />", "o={a=1, b=2}");
+
+        assertOutput("<@mCAO?withArgsLast({}) />", "o={}");
+
+        // Ordering of "real" args win:
+        assertOutput("<@mCA?withArgsLast({'c': 3, 'd': 4}) a=1 b=2 />", "a=1; b=2; o={c=3, d=4}");
+        assertOutput("<@mCA?withArgsLast({'c': 3, 'd': 4}) a=1 d=44 b=2 />", "a=1; b=2; o={d=44, c=3}");
+    }
+
+    @Test
+    public void testMacroWithArgsLastNamedNullArgs() throws IOException, TemplateException {
+        assertOutput("<@mCA?withArgsLast({'c': 3, 'd': 4}) a=1 d=null b=2 />", "a=1; b=2; o={d=null, c=3}");
+        Map<String, Integer> cAndDNull = new LinkedHashMap<String, Integer>();
+        cAndDNull.put("c", 3);
+        cAndDNull.put("d", null);
+        addToDataModel("cAndDNull", cAndDNull);
+        assertOutput("<@mCA?withArgsLast(cAndDNull) a=1 b=2 />", "a=1; b=2; o={c=3, d=null}");
+        assertOutput("<@mCA?withArgsLast(cAndDNull) a=1 d=null b=2 />", "a=1; b=2; o={d=null, c=3}");
+    }
+
+    @Test
+    public void testMacroWithArgsLastPositional() throws IOException, TemplateException {
+        assertOutput("<@m?withArgsLast([1, 2, 3]) />", "a=1; b=2; c=3");
+        assertOutput("<@m?withArgsLast([2, 3]) 1 />", "a=1; b=2; c=3");
+        assertOutput("<@m?withArgsLast([3]) 1 2 />", "a=1; b=2; c=3");
+        assertOutput("<@m?withArgsLast([]) 1 2 3 />", "a=1; b=2; c=3");
+
+        assertOutput("<@m?withArgsLast([]) a=1 b=2 />", "a=1; b=2; c=d3");
+        assertErrorContains("<@m?withArgsLast([3]) a=1 b=2 />", "by name", "by position", "last");
+
+        assertOutput("<@m?withArgsLast([1, 2]) />", "a=1; b=2; c=d3");
+        assertOutput("<@m?withArgsLast([2]) 1 />", "a=1; b=2; c=d3");
+        assertOutput("<@m?withArgsLast([]) 1 2 />", "a=1; b=2; c=d3");
+
+        assertOutput("<@mCA?withArgsLast([1, 2, 3, 4]) />", "a=1; b=2; o=[3, 4]");
+        assertOutput("<@mCA?withArgsLast([2, 3, 4]) 1 />", "a=1; b=2; o=[3, 4]");
+        assertOutput("<@mCA?withArgsLast([3, 4]) 1 2 />", "a=1; b=2; o=[3, 4]");
+        assertOutput("<@mCA?withArgsLast([4]) 1 2 3 />", "a=1; b=2; o=[3, 4]");
+        assertOutput("<@mCA?withArgsLast([]) 1 2 3 4 />", "a=1; b=2; o=[3, 4]");
+
+        assertOutput("<@mCAO?withArgsLast([1, 2, 3, 4]) />", "o=[1, 2, 3, 4]");
+        assertOutput("<@mCAO?withArgsLast([3, 4]) 1 2 />", "o=[1, 2, 3, 4]");
+        assertOutput("<@mCAO?withArgsLast([]) 1 2 3 4 />", "o=[1, 2, 3, 4]");
+
+        assertOutput("<@mCAO?withArgsLast([]) a=1 b=2 />", "o={a=1, b=2}");
+        assertErrorContains("<@mCAO?withArgsLast([3]) a=1 b=2 />", "by name", "by position", "last");
+
+        assertOutput("<@mCAO?withArgsLast([]) />", "o=[]");
+
+        assertErrorContains("<@m?withArgsLast([0, 0, 0, 0]) />", "3", "4", "parameter");
+        assertErrorContains("<@m?withArgsLast([0, 0, 0]) 0 />", "3", "4", "parameter");
+        assertErrorContains("<@m?withArgsLast([]) 0 0 0 0 />", "3", "4", "parameter");
+    }
+
+    @Test
+    public void testMacroWithArgsLastPositionalNullArgs() throws IOException, TemplateException {
+        ArrayList<Object> twoAndNull = new ArrayList<Object>();
+        twoAndNull.add(2);
+        twoAndNull.add(null);
+        addToDataModel("twoAndNull", twoAndNull);
+
+        assertOutput("<@m?withArgsLast(twoAndNull) 1 />", "a=1; b=2; c=d3");
+        assertErrorContains("<@m?withArgsLast([3]) null 2 />", "\"a\"", "null");
+        assertOutput("<@m?withArgsLast([]) 1 2 null />", "a=1; b=2; c=d3");
+
+        assertOutput("<@mCAO?withArgsLast(twoAndNull) 1 />", "o=[1, 2, null]");
+        assertOutput("<@mCAO?withArgsLast([3]) null 2 />", "o=[null, 2, 3]");
+    }
+
     private static class TestTemplateDirectiveModel implements TemplateDirectiveModel {
 
         public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws
diff --git a/src/test/java/freemarker/manual/WithArgsLastExamples.java b/src/test/java/freemarker/manual/WithArgsLastExamples.java
new file mode 100644
index 0000000..b59dca8
--- /dev/null
+++ b/src/test/java/freemarker/manual/WithArgsLastExamples.java
@@ -0,0 +1,35 @@
+/*
+ * 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.manual;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import freemarker.template.TemplateException;
+
+public class WithArgsLastExamples extends ExamplesTest {
+
+    @Test
+    public void usingWithArgsSpecialVariable() throws IOException, TemplateException {
+        assertOutputForNamed("WithArgsLastExamples.ftl");
+    }
+
+}
diff --git a/src/test/resources/freemarker/manual/WithArgsLastExamples.ftl b/src/test/resources/freemarker/manual/WithArgsLastExamples.ftl
new file mode 100644
index 0000000..4494b13
--- /dev/null
+++ b/src/test/resources/freemarker/manual/WithArgsLastExamples.ftl
@@ -0,0 +1,25 @@
+<#function f a b c d>
+  <#return "a=${a}, b=${b}, c=${c}, d=${d}">
+</#function>
+
+${f?with_args([2, 3])(1, 2)}
+${f?with_args_last([2, 3])(1, 2)}
+
+<#macro m a b others...>
+  a=${a}
+  b=${b}
+  others:
+  <#list others as k, v>
+    ${k} = ${v}
+  </#list>
+</#macro>
+<@m?with_args({'e': 5, 'f': 6}) a=1 b=2 c=3 d=4 />
+<@m?with_args_last({'e': 5, 'f': 6}) a=1 b=2 c=3 d=4 />
+
+<#macro m a b others...>
+  <#list .args as k, v>
+    ${k} = ${v}
+  </#list>
+</#macro>
+<@m?with_args({'e': 5, 'f': 6}) a=1 b=2 c=3 d=4 />
+<@m?with_args_last({'e': 5, 'f': 6}) a=1 b=2 c=3 d=4 />