diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java
@@ -33,6 +33,12 @@
 import com.sun.tools.javac.resources.CompilerProperties.Notes;
 import com.sun.tools.javac.tree.JCTree;
 import com.sun.tools.javac.tree.JCTree.JCTypeCast;
+
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCNewClass;
+import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+
 import com.sun.tools.javac.tree.TreeInfo;
 import com.sun.tools.javac.util.*;
 import com.sun.tools.javac.util.GraphUtils.DottableNode;
@@ -81,6 +87,53 @@
 public class Infer {
     protected static final Context.Key<Infer> inferKey = new Context.Key<>();
 
+    final TreeDiffer similar = new TreeDiffer(List.nil(), List.nil()) {
+
+        @Override
+        public boolean scan(JCTree tree, JCTree parameter) {
+            if (tree != null && parameter != null
+                    && tree.type != null && parameter.type != null
+                    && !tree.type.hasTag(DEFERRED) && !parameter.type.hasTag(DEFERRED)
+                    && types.isSameType(tree.type, parameter.type)) {
+                return true; // Don't scan trees if their types are known and identical.
+            }
+            return super.scan(tree, parameter);
+        }
+
+        @Override
+        public void visitIdent(JCIdent tree) {
+            JCIdent that = (JCIdent) parameter;
+            result = tree.name == that.name; // Method symbols might be unresolved yet.
+        }
+
+        @Override
+        public void visitSelect(JCFieldAccess tree) {
+            JCFieldAccess that = (JCFieldAccess) parameter;
+            result = scan(tree.selected, that.selected) && tree.name == that.name; // Don't use sym that may be null.
+        }
+
+        @Override
+        public void visitApply(JCMethodInvocation tree) {
+            JCMethodInvocation that = (JCMethodInvocation) parameter;
+            // Don't scan polyKind that may be unset yet.
+            result = scan(tree.typeargs, that.typeargs) && scan(tree.meth, that.meth) && scan(tree.args, that.args);
+        }
+
+        @Override
+        public void visitNewClass(JCNewClass tree) {
+            JCNewClass that = (JCNewClass) parameter;
+            result = scan(tree.encl, that.encl)
+                    && scan(tree.typeargs, that.typeargs)
+                    && scan(tree.clazz, that.clazz)
+                    && scan(tree.args, that.args)
+                    && tree.def==null && that.def==null; // Anonymous classes have different types.
+        }
+    };
+
+    boolean varArgsFactoEnabled(JCTree tree) {
+        return tree.hasTag(JCTree.Tag.APPLY) || tree.hasTag(JCTree.Tag.NEWCLASS) && ((JCNewClass)tree).def == null; // Same as above.
+    }
+
     Resolve rs;
     Check chk;
     Symtab syms;
@@ -173,7 +226,10 @@
                             Resolve.MethodResolutionContext resolveContext,
                             Warner warn) throws InferenceException {
         //-System.err.println("instantiateMethod(" + tvars + ", " + mt + ", " + argtypes + ")"); //DEBUG
+
         final InferenceContext inferenceContext = new InferenceContext(this, tvars);  //B0
+        inferenceContext.methSym = msym;
+
         try {
             DeferredAttr.DeferredAttrContext deferredAttrContext =
                         resolveContext.deferredAttrContext(msym, inferenceContext, resultInfo, warn);
@@ -186,7 +242,6 @@
                 //we are inside method attribution - just return a partially inferred type
                 return new PartiallyInferredMethodType(mt, inferenceContext, env, warn);
             } else if (allowGraphInference && resultInfo != null) {
-
                 //inject return constraints earlier
                 doIncorporation(inferenceContext, warn); //propagation
 
@@ -299,6 +354,9 @@
         /** The warner. */
         final Warner warn;
 
+        /** The already checked type of a similar argument. */
+        Type result=null;
+
         @Override
         public boolean isPartial() {
             return true;
@@ -306,9 +364,29 @@
 
         /**
          * Checks this type against a target; this means generating return type constraints, solve
-         * and then roll back the results (to avoid poolluting the context).
+         * and then roll back the results (to avoid polluting the context).
+         *
+         * OVERLOAD RESOLUTION SPECIFIC FOR VARARGS FACTORIZATION: check once similar varargs.
          */
         Type check(Attr.ResultInfo resultInfo) {
+            InferenceContext callCtx = resultInfo.checkContext.inferenceContext();
+
+            if (varArgsFactoEnabled(env.tree) && callCtx.factoVarArg()) {
+                InferenceContext.Clone clone = callCtx.new Clone(inferenceContext.methSym, env.tree);
+                if (!callCtx.clones.containsKey(clone)) {
+                    clone.partType = this;
+                    callCtx.clones.put(clone, clone);
+
+                    result = check(resultInfo); // check it once for similar trees!
+                    return result;
+                }
+                else {
+                    clone = callCtx.clones.get(clone);
+                    if (clone.partType != this)
+                       return clone.partType.result; // return the type of an already checked argument with a similar tree.
+                }
+            }
+
             Warner noWarnings = new Warner(null);
             List<Type> saved_undet = null;
             try {
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/InferenceContext.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/InferenceContext.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/InferenceContext.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/InferenceContext.java
@@ -55,6 +55,10 @@
 import com.sun.tools.javac.util.ListBuffer;
 import com.sun.tools.javac.util.Warner;
 
+import com.sun.tools.javac.util.Name;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+
 /**
  * An inference context keeps track of the set of variables that are free
  * in the current context. It provides utility methods for opening/closing
@@ -82,6 +86,50 @@
 
     Map<FreeTypeListener, List<Type>> freeTypeListeners = new LinkedHashMap<>();
 
+    /** inferred method symbol */
+    MethodSymbol methSym;
+
+    /** currently checked argument */
+    int argCount=0;
+
+    boolean factoVarArg() {
+        return methSym != null && methSym.isVarArgs() && argCount >= methSym.params().size()-1;
+    }
+
+    /**
+     * VarArg to factor.
+     */
+    class Clone {
+        /** applied method symbol. */
+        Symbol sym;
+
+        /** vararg tree. */
+        JCTree tree=null;
+
+        /** OVERLOAD RESOLUTION SPECIFIC FOR VARARGS FACTORIZATION: used to check once similar varargs. */
+        Infer.PartiallyInferredMethodType partType=null;
+
+        /** METHOD TYPE-INFERENCE SPECIFIC FOR VARARGS FACTORIZATION: TypeVars of similar varargs to recycle. */
+        List<Type> typeVars=null;
+
+        Clone(Symbol sym, JCTree tree) {
+            this.sym = sym;
+            this.tree = tree;
+        }
+
+        @Override
+        public int hashCode() {
+            return sym.name.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object that) {
+            return that instanceof Clone && infer.similar.scan(tree, ((Clone)that).tree);
+        }
+    }
+
+    Map<Clone, Clone> clones = new HashMap<>();
+
     Types types;
     Infer infer;
 
@@ -326,7 +374,15 @@
     void dupTo(final InferenceContext that, boolean clone) {
         that.inferencevars = that.inferencevars.appendList(inferencevars.diff(that.inferencevars));
         List<Type> undetsToPropagate = clone ? save() : undetvars;
-        that.undetvars = that.undetvars.appendList(undetsToPropagate.diff(that.undetvars)); //propagate cloned undet!!
+
+        that.undetvars = that.undetvars.appendList(undetsToPropagate.stream().filter(a -> {
+            for (Type b: that.undetvars) {
+                if (a == b || ((UndetVar)a).qtype == ((UndetVar)b).qtype) // ensure not to propagate duplicated UndetVars.
+                    return false;
+            }
+            return true;
+        }).collect(List.collector()));
+
         //set up listeners to notify original inference contexts as
         //propagated vars are inferred in new context
         for (Type t : inferencevars) {
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java
@@ -594,6 +594,24 @@
         } else if (mt.hasTag(FORALL)) {
             ForAll pmt = (ForAll) mt;
             List<Type> tvars1 = types.newInstances(pmt.tvars);
+
+            // METHOD TYPE-INFERENCE SPECIFIC FOR VARARGS FACTORIZATION: TypeVars of similar varargs are recycled.
+            if (resultInfo != null) {
+                InferenceContext callCtx = resultInfo.checkContext.inferenceContext();
+
+                if (infer.varArgsFactoEnabled(env.tree) && callCtx.factoVarArg()) {
+                    InferenceContext.Clone clone = callCtx.new Clone(m, env.tree);
+                    if (!callCtx.clones.containsKey(clone)) {
+                        clone.typeVars = tvars1;
+                        callCtx.clones.put(clone, clone);
+                    }
+                    else {
+                        clone = callCtx.clones.get(clone);
+                        tvars1 = clone.typeVars;
+                    }
+                }
+            }
+
             tvars = tvars.appendList(tvars1);
             mt = types.subst(pmt.qtype, pmt.tvars, tvars1);
         }
@@ -890,6 +908,8 @@
         void checkArg(DiagnosticPosition pos, boolean varargs, Type actual, Type formal, DeferredAttrContext deferredAttrContext, Warner warn) {
             ResultInfo mresult = methodCheckResult(varargs, formal, deferredAttrContext, warn);
             mresult.check(pos, actual);
+
+            deferredAttrContext.inferenceContext.argCount++;
         }
 
         @Override
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TreeDiffer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TreeDiffer.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TreeDiffer.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TreeDiffer.java
@@ -117,8 +117,8 @@
         return result;
     }
 
-    private JCTree parameter;
-    private boolean result;
+    protected JCTree parameter;
+    protected boolean result;
     private Map<Symbol, Symbol> equiv = new HashMap<>();
 
     public boolean scan(JCTree tree, JCTree parameter) {
@@ -148,7 +148,7 @@
         }
     }
 
-    private boolean scan(Iterable<? extends JCTree> xs, Iterable<? extends JCTree> ys) {
+    protected boolean scan(Iterable<? extends JCTree> xs, Iterable<? extends JCTree> ys) {
         if (xs == null || ys == null) {
             return xs == null && ys == null;
         }
