Repository: mockito/mockito
Issue: Varargs methods cause `ClassCastException` in `AnswerFunctionalInterfaces`

Hi,

The other day, I discovered that this code does not work:

```java
        doAnswer(answerVoid((VoidAnswer2<String, Object[]>)
                logger::info
        )).when(mock)
                .info(any(), (Object[]) any());

        // This call will throw an exception
        mock.info("Some message with {} {} {}", "three", "parameters", "");
```

The exception details looks as follows:

```
class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')
java.lang.ClassCastException: class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')
	at org.mockito.internal.stubbing.answers.AnswerFunctionalInterfaces$4.answer(AnswerFunctionalInterfaces.java:97)
	at org.mockito.internal.stubbing.answers.AnswerFunctionalInterfaces$4.answer(AnswerFunctionalInterfaces.java:93)
	at org.mockito.internal.stubbing.StubbedInvocationMatcher.answer(StubbedInvocationMatcher.java:42)
	at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:103)
	at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
	at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:34)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:82)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:56)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptAbstract(MockMethodInterceptor.java:161)
	at org.slf4j.Logger$MockitoMock$f4Org7xs.info(Unknown Source)
	at LoggerMockingTest.logger_info_can_be_called_without_exceptions(LoggerMockingTest.java:34)
        [...]
```

The following _works_, but feels much less elegant:

```java
        doAnswer( invocation -> {
            String format = invocation.getArgument( 0 );
            Object[] allArguments = invocation.getArguments();
            Object[] arguments = Arrays.copyOfRange( allArguments, 1, allArguments.length );
            logger.info( format, arguments );
            return null;
        } ).when( mock )
                .info( anyString(), (Object[])any() );
```

Is this to be considered a bug or a feature? :slightly_smiling_face: I get the feeling that varargs (`Object[]` under the hood) parameters do not really work when used with the `answerVoid()`-based approach as described above.

### Full MCVE

https://github.com/perlun/mockito-slf4-logging-example

### Issue checks
 - [x] The mockito message in the stacktrace have useful information, but it didn't help
 - [x] The problematic code (if that's possible) is copied here;
       Note that some configuration are impossible to mock via Mockito
 - [x] Provide versions (mockito / jdk / os / any other relevant information): Reproduced with latest Mockito, 4.5.1
 - [x] Provide a [Short, Self Contained, Correct (Compilable), Example](http://sscce.org) of the issue
 - [x] Read the [contributing guide](https://github.com/mockito/mockito/blob/main/.github/CONTRIBUTING.md)

Issue comments:
I've had a bit of a dig and it looks like a bug to me.  `returnsArgAt` has similar issues.  I'm happy to have a look into this.
Thanks for the fix @big-andy-coates & @TimvdLippe, appreciated!
(Verified in our project that the fix indeed fixes the original problem. Thanks! :pray:)
Semi-related issue discovered today: https://github.com/mockito/mockito/pull/2664#issuecomment-1319816402

Pull request review comments:
Note the existing cast of `InvocationOnMock` to `Invocation` to access the `getRawArguments` method. 

What do you think to exposing `getRawArguments` on the `InvocationOnMock` interface, and thereby removing the need for  lots of casts to get to the `Invocation`?
When this is invoked, both `getArguments()` and `getRawArguments()` return the same, which is the number of _raw_ arguments.

I've renamed to `theoretical` because at this point in the code the actual parameters are not known, so we're only asking is its theoretically possible that the argument position is valid.
If I remember correctly, we do these casts in quite a few place. If that's indeed the case, then yes let's introduce that method. If this is the only place, let's keep the cast here.
Agreed.  When making it public would you also want me to make the method return a copy of any array parameters, so that we're not exposing mutable internal state?
Yes makes sense.
Actually, I notice that `getArguments` does not do an array copy, so I'll stick with the same pattern when exposing `getRawArguments`.
Nit: add `@since 4.7.0` here
Nit: put this in a separate test case. Since we are stubbing the same method twice, this could lead to strange error messages/unintended behavior when the first stubbing isn't properly cleaned up by Mockito. In that case, I think this test can pass, even though the underlying logic is wrong.

Patch that resolved the issue:
diff --git a/src/main/java/org/mockito/internal/stubbing/answers/AnswerFunctionalInterfaces.java b/src/main/java/org/mockito/internal/stubbing/answers/AnswerFunctionalInterfaces.java
index e4692f26d9..d9b3271d91 100644
--- a/src/main/java/org/mockito/internal/stubbing/answers/AnswerFunctionalInterfaces.java
+++ b/src/main/java/org/mockito/internal/stubbing/answers/AnswerFunctionalInterfaces.java
@@ -19,6 +19,8 @@
 import org.mockito.stubbing.VoidAnswer5;
 import org.mockito.stubbing.VoidAnswer6;
 
+import java.lang.reflect.Method;
+
 /**
  * Functional interfaces to make it easy to implement answers in Java 8
  *
@@ -38,11 +40,11 @@ private AnswerFunctionalInterfaces() {}
      * @return a new answer object
      */
     public static <T, A> Answer<T> toAnswer(final Answer1<T, A> answer) {
+        final Method answerMethod = findAnswerMethod(answer.getClass(), 1);
         return new Answer<T>() {
             @Override
-            @SuppressWarnings("unchecked")
             public T answer(InvocationOnMock invocation) throws Throwable {
-                return answer.answer((A) invocation.getArgument(0));
+                return answer.answer(lastParameter(invocation, answerMethod, 0));
             }
         };
     }
@@ -54,11 +56,11 @@ public T answer(InvocationOnMock invocation) throws Throwable {
      * @return a new answer object
      */
     public static <A> Answer<Void> toAnswer(final VoidAnswer1<A> answer) {
+        final Method answerMethod = findAnswerMethod(answer.getClass(), 1);
         return new Answer<Void>() {
             @Override
-            @SuppressWarnings("unchecked")
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                answer.answer((A) invocation.getArgument(0));
+                answer.answer(lastParameter(invocation, answerMethod, 0));
                 return null;
             }
         };
@@ -73,11 +75,13 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
      * @return a new answer object
      */
     public static <T, A, B> Answer<T> toAnswer(final Answer2<T, A, B> answer) {
+        final Method answerMethod = findAnswerMethod(answer.getClass(), 2);
         return new Answer<T>() {
             @Override
             @SuppressWarnings("unchecked")
             public T answer(InvocationOnMock invocation) throws Throwable {
-                return answer.answer((A) invocation.getArgument(0), (B) invocation.getArgument(1));
+                return answer.answer(
+                        (A) invocation.getArgument(0), lastParameter(invocation, answerMethod, 1));
             }
         };
     }
@@ -90,11 +94,13 @@ public T answer(InvocationOnMock invocation) throws Throwable {
      * @return a new answer object
      */
     public static <A, B> Answer<Void> toAnswer(final VoidAnswer2<A, B> answer) {
+        final Method answerMethod = findAnswerMethod(answer.getClass(), 2);
         return new Answer<Void>() {
             @Override
             @SuppressWarnings("unchecked")
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                answer.answer((A) invocation.getArgument(0), (B) invocation.getArgument(1));
+                answer.answer(
+                        (A) invocation.getArgument(0), lastParameter(invocation, answerMethod, 1));
                 return null;
             }
         };
@@ -110,6 +116,7 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
      * @return a new answer object
      */
     public static <T, A, B, C> Answer<T> toAnswer(final Answer3<T, A, B, C> answer) {
+        final Method answerMethod = findAnswerMethod(answer.getClass(), 3);
         return new Answer<T>() {
             @Override
             @SuppressWarnings("unchecked")
@@ -117,7 +124,7 @@ public T answer(InvocationOnMock invocation) throws Throwable {
                 return answer.answer(
                         (A) invocation.getArgument(0),
                         (B) invocation.getArgument(1),
-                        (C) invocation.getArgument(2));
+                        lastParameter(invocation, answerMethod, 2));
             }
         };
     }
@@ -131,6 +138,7 @@ public T answer(InvocationOnMock invocation) throws Throwable {
      * @return a new answer object
      */
     public static <A, B, C> Answer<Void> toAnswer(final VoidAnswer3<A, B, C> answer) {
+        final Method answerMethod = findAnswerMethod(answer.getClass(), 3);
         return new Answer<Void>() {
             @Override
             @SuppressWarnings("unchecked")
@@ -138,7 +146,7 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
                 answer.answer(
                         (A) invocation.getArgument(0),
                         (B) invocation.getArgument(1),
-                        (C) invocation.getArgument(2));
+                        lastParameter(invocation, answerMethod, 2));
                 return null;
             }
         };
@@ -155,6 +163,7 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
      * @return a new answer object
      */
     public static <T, A, B, C, D> Answer<T> toAnswer(final Answer4<T, A, B, C, D> answer) {
+        final Method answerMethod = findAnswerMethod(answer.getClass(), 4);
         return new Answer<T>() {
             @Override
             @SuppressWarnings("unchecked")
@@ -163,7 +172,7 @@ public T answer(InvocationOnMock invocation) throws Throwable {
                         (A) invocation.getArgument(0),
                         (B) invocation.getArgument(1),
                         (C) invocation.getArgument(2),
-                        (D) invocation.getArgument(3));
+                        lastParameter(invocation, answerMethod, 3));
             }
         };
     }
@@ -178,6 +187,7 @@ public T answer(InvocationOnMock invocation) throws Throwable {
      * @return a new answer object
      */
     public static <A, B, C, D> Answer<Void> toAnswer(final VoidAnswer4<A, B, C, D> answer) {
+        final Method answerMethod = findAnswerMethod(answer.getClass(), 4);
         return new Answer<Void>() {
             @Override
             @SuppressWarnings("unchecked")
@@ -186,7 +196,7 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
                         (A) invocation.getArgument(0),
                         (B) invocation.getArgument(1),
                         (C) invocation.getArgument(2),
-                        (D) invocation.getArgument(3));
+                        lastParameter(invocation, answerMethod, 3));
                 return null;
             }
         };
@@ -204,6 +214,7 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
      * @return a new answer object
      */
     public static <T, A, B, C, D, E> Answer<T> toAnswer(final Answer5<T, A, B, C, D, E> answer) {
+        final Method answerMethod = findAnswerMethod(answer.getClass(), 5);
         return new Answer<T>() {
             @Override
             @SuppressWarnings("unchecked")
@@ -213,7 +224,7 @@ public T answer(InvocationOnMock invocation) throws Throwable {
                         (B) invocation.getArgument(1),
                         (C) invocation.getArgument(2),
                         (D) invocation.getArgument(3),
-                        (E) invocation.getArgument(4));
+                        lastParameter(invocation, answerMethod, 4));
             }
         };
     }
@@ -229,6 +240,7 @@ public T answer(InvocationOnMock invocation) throws Throwable {
      * @return a new answer object
      */
     public static <A, B, C, D, E> Answer<Void> toAnswer(final VoidAnswer5<A, B, C, D, E> answer) {
+        final Method answerMethod = findAnswerMethod(answer.getClass(), 5);
         return new Answer<Void>() {
             @Override
             @SuppressWarnings("unchecked")
@@ -238,7 +250,7 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
                         (B) invocation.getArgument(1),
                         (C) invocation.getArgument(2),
                         (D) invocation.getArgument(3),
-                        (E) invocation.getArgument(4));
+                        lastParameter(invocation, answerMethod, 4));
                 return null;
             }
         };
@@ -259,6 +271,7 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
      */
     public static <T, A, B, C, D, E, F> Answer<T> toAnswer(
             final Answer6<T, A, B, C, D, E, F> answer) {
+        final Method answerMethod = findAnswerMethod(answer.getClass(), 6);
         return new Answer<T>() {
             @Override
             @SuppressWarnings("unchecked")
@@ -269,7 +282,7 @@ public T answer(InvocationOnMock invocation) throws Throwable {
                         (C) invocation.getArgument(2),
                         (D) invocation.getArgument(3),
                         (E) invocation.getArgument(4),
-                        (F) invocation.getArgument(5));
+                        lastParameter(invocation, answerMethod, 5));
             }
         };
     }
@@ -288,6 +301,7 @@ public T answer(InvocationOnMock invocation) throws Throwable {
      */
     public static <A, B, C, D, E, F> Answer<Void> toAnswer(
             final VoidAnswer6<A, B, C, D, E, F> answer) {
+        final Method answerMethod = findAnswerMethod(answer.getClass(), 6);
         return new Answer<Void>() {
             @Override
             @SuppressWarnings("unchecked")
@@ -298,9 +312,42 @@ public Void answer(InvocationOnMock invocation) throws Throwable {
                         (C) invocation.getArgument(2),
                         (D) invocation.getArgument(3),
                         (E) invocation.getArgument(4),
-                        (F) invocation.getArgument(5));
+                        lastParameter(invocation, answerMethod, 5));
                 return null;
             }
         };
     }
+
+    private static Method findAnswerMethod(final Class<?> type, final int numberOfParameters) {
+        for (final Method m : type.getDeclaredMethods()) {
+            if (!m.isBridge()
+                    && m.getName().equals("answer")
+                    && m.getParameterTypes().length == numberOfParameters) {
+                return m;
+            }
+        }
+        throw new IllegalStateException(
+                "Failed to find answer() method on the supplied class: "
+                        + type.getName()
+                        + ", with the supplied number of parameters: "
+                        + numberOfParameters);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <A> A lastParameter(
+            InvocationOnMock invocation, Method answerMethod, int argumentIndex) {
+        final Method invocationMethod = invocation.getMethod();
+
+        if (invocationMethod.isVarArgs()
+                && invocationMethod.getParameterTypes().length == (argumentIndex + 1)) {
+            final Class<?> invocationRawArgType =
+                    invocationMethod.getParameterTypes()[argumentIndex];
+            final Class<?> answerRawArgType = answerMethod.getParameterTypes()[argumentIndex];
+            if (answerRawArgType.isAssignableFrom(invocationRawArgType)) {
+                return (A) invocation.getRawArguments()[argumentIndex];
+            }
+        }
+
+        return invocation.getArgument(argumentIndex);
+    }
 }
diff --git a/src/main/java/org/mockito/internal/stubbing/answers/ReturnsArgumentAt.java b/src/main/java/org/mockito/internal/stubbing/answers/ReturnsArgumentAt.java
index cd661152c3..c3cabc1420 100644
--- a/src/main/java/org/mockito/internal/stubbing/answers/ReturnsArgumentAt.java
+++ b/src/main/java/org/mockito/internal/stubbing/answers/ReturnsArgumentAt.java
@@ -52,24 +52,24 @@ public ReturnsArgumentAt(int wantedArgumentPosition) {
 
     @Override
     public Object answer(InvocationOnMock invocation) throws Throwable {
-        int argumentPosition = inferWantedArgumentPosition(invocation);
-        validateIndexWithinInvocationRange(invocation, argumentPosition);
-
-        if (wantedArgIndexIsVarargAndSameTypeAsReturnType(
-                invocation.getMethod(), argumentPosition)) {
+        if (wantedArgIndexIsVarargAndSameTypeAsReturnType(invocation)) {
             // answer raw vararg array argument
-            return ((Invocation) invocation).getRawArguments()[argumentPosition];
+            return invocation.getRawArguments()[invocation.getRawArguments().length - 1];
         }
 
+        int argumentPosition = inferWantedArgumentPosition(invocation);
+        validateIndexWithinInvocationRange(invocation, argumentPosition);
+
         // answer expanded argument at wanted position
         return invocation.getArgument(argumentPosition);
     }
 
     @Override
-    public void validateFor(InvocationOnMock invocation) {
+    public void validateFor(InvocationOnMock invocationOnMock) {
+        Invocation invocation = (Invocation) invocationOnMock;
         int argumentPosition = inferWantedArgumentPosition(invocation);
-        validateIndexWithinInvocationRange(invocation, argumentPosition);
-        validateArgumentTypeCompatibility((Invocation) invocation, argumentPosition);
+        validateIndexWithinTheoreticalInvocationRange(invocation, argumentPosition);
+        validateArgumentTypeCompatibility(invocation, argumentPosition);
     }
 
     private int inferWantedArgumentPosition(InvocationOnMock invocation) {
@@ -80,9 +80,26 @@ private int inferWantedArgumentPosition(InvocationOnMock invocation) {
         return wantedArgumentPosition;
     }
 
+    private int inferWantedRawArgumentPosition(InvocationOnMock invocation) {
+        if (wantedArgumentPosition == LAST_ARGUMENT) {
+            return invocation.getRawArguments().length - 1;
+        }
+
+        return wantedArgumentPosition;
+    }
+
     private void validateIndexWithinInvocationRange(
             InvocationOnMock invocation, int argumentPosition) {
-        if (!wantedArgumentPositionIsValidForInvocation(invocation, argumentPosition)) {
+
+        if (invocation.getArguments().length <= argumentPosition) {
+            throw invalidArgumentPositionRangeAtInvocationTime(
+                    invocation, wantedArgumentPosition == LAST_ARGUMENT, wantedArgumentPosition);
+        }
+    }
+
+    private void validateIndexWithinTheoreticalInvocationRange(
+            InvocationOnMock invocation, int argumentPosition) {
+        if (!wantedArgumentPositionIsValidForTheoreticalInvocation(invocation, argumentPosition)) {
             throw invalidArgumentPositionRangeAtInvocationTime(
                     invocation, wantedArgumentPosition == LAST_ARGUMENT, wantedArgumentPosition);
         }
@@ -102,15 +119,16 @@ private void validateArgumentTypeCompatibility(Invocation invocation, int argume
         }
     }
 
-    private boolean wantedArgIndexIsVarargAndSameTypeAsReturnType(
-            Method method, int argumentPosition) {
+    private boolean wantedArgIndexIsVarargAndSameTypeAsReturnType(InvocationOnMock invocation) {
+        int rawArgumentPosition = inferWantedRawArgumentPosition(invocation);
+        Method method = invocation.getMethod();
         Class<?>[] parameterTypes = method.getParameterTypes();
         return method.isVarArgs()
-                && argumentPosition == /* vararg index */ parameterTypes.length - 1
-                && method.getReturnType().isAssignableFrom(parameterTypes[argumentPosition]);
+                && rawArgumentPosition == /* vararg index */ parameterTypes.length - 1
+                && method.getReturnType().isAssignableFrom(parameterTypes[rawArgumentPosition]);
     }
 
-    private boolean wantedArgumentPositionIsValidForInvocation(
+    private boolean wantedArgumentPositionIsValidForTheoreticalInvocation(
             InvocationOnMock invocation, int argumentPosition) {
         if (argumentPosition < 0) {
             return false;
@@ -145,7 +163,7 @@ private Class<?> inferArgumentType(Invocation invocation, int argumentIndex) {
             return parameterTypes[argumentIndex];
         }
         // if wanted argument is vararg
-        if (wantedArgIndexIsVarargAndSameTypeAsReturnType(invocation.getMethod(), argumentIndex)) {
+        if (wantedArgIndexIsVarargAndSameTypeAsReturnType(invocation)) {
             // return the vararg array if return type is compatible
             // because the user probably want to return the array itself if the return type is
             // compatible
diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ForwardsInvocations.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ForwardsInvocations.java
index 5f596b7da8..5d12ca03e6 100644
--- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ForwardsInvocations.java
+++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ForwardsInvocations.java
@@ -12,7 +12,6 @@
 import java.lang.reflect.Method;
 
 import org.mockito.internal.configuration.plugins.Plugins;
-import org.mockito.invocation.Invocation;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.plugins.MemberAccessor;
 import org.mockito.stubbing.Answer;
@@ -45,7 +44,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
             }
 
             MemberAccessor accessor = Plugins.getMemberAccessor();
-            Object[] rawArguments = ((Invocation) invocation).getRawArguments();
+            Object[] rawArguments = invocation.getRawArguments();
             return accessor.invoke(delegateMethod, delegatedObject, rawArguments);
         } catch (NoSuchMethodException e) {
             throw delegatedMethodDoesNotExistOnDelegate(
diff --git a/src/main/java/org/mockito/invocation/Invocation.java b/src/main/java/org/mockito/invocation/Invocation.java
index 7aeecce1c3..4742b34150 100644
--- a/src/main/java/org/mockito/invocation/Invocation.java
+++ b/src/main/java/org/mockito/invocation/Invocation.java
@@ -41,14 +41,6 @@ public interface Invocation extends InvocationOnMock, DescribedInvocation {
     @Override
     Location getLocation();
 
-    /**
-     * Returns unprocessed arguments whereas {@link #getArguments()} returns
-     * arguments already processed (e.g. varargs expended, etc.).
-     *
-     * @return unprocessed arguments, exactly as provided to this invocation.
-     */
-    Object[] getRawArguments();
-
     /**
      * Wraps each argument using {@link org.mockito.ArgumentMatchers#eq(Object)} or
      * {@link org.mockito.AdditionalMatchers#aryEq(Object[])}
@@ -64,6 +56,7 @@ public interface Invocation extends InvocationOnMock, DescribedInvocation {
      * arguments already processed (e.g. varargs expended, etc.).
      *
      * @return unprocessed arguments, exactly as provided to this invocation.
+     * @since 4.7.0
      */
     Class<?> getRawReturnType();
 
diff --git a/src/main/java/org/mockito/invocation/InvocationOnMock.java b/src/main/java/org/mockito/invocation/InvocationOnMock.java
index 269926ea6b..126c2b5999 100644
--- a/src/main/java/org/mockito/invocation/InvocationOnMock.java
+++ b/src/main/java/org/mockito/invocation/InvocationOnMock.java
@@ -32,6 +32,15 @@ public interface InvocationOnMock extends Serializable {
      */
     Method getMethod();
 
+    /**
+     * Returns unprocessed arguments whereas {@link #getArguments()} returns
+     * arguments already processed (e.g. varargs expended, etc.).
+     *
+     * @return unprocessed arguments, exactly as provided to this invocation.
+     * @since 4.7.0
+     */
+    Object[] getRawArguments();
+
     /**
      * Returns arguments passed to the method.
      *


Test patch:
diff --git a/src/test/java/org/mockitousage/IMethods.java b/src/test/java/org/mockitousage/IMethods.java
index 12897422da..06492f09cb 100644
--- a/src/test/java/org/mockitousage/IMethods.java
+++ b/src/test/java/org/mockitousage/IMethods.java
@@ -133,8 +133,26 @@ String simpleMethod(
 
     String threeArgumentMethodWithStrings(int valueOne, String valueTwo, String valueThree);
 
+    String threeArgumentVarArgsMethod(int valueOne, String valueTwo, String... valueThree);
+
     String fourArgumentMethod(int valueOne, String valueTwo, String valueThree, boolean[] array);
 
+    String fourArgumentVarArgsMethod(
+            int valueOne, String valueTwo, int valueThree, String... valueFour);
+
+    String fiveArgumentVarArgsMethod(
+            int valueOne, String valueTwo, int valueThree, String valueFour, String... valueFive);
+
+    String sixArgumentVarArgsMethod(
+            int valueOne,
+            String valueTwo,
+            int valueThree,
+            String valueFour,
+            String valueFive,
+            String... valueSix);
+
+    int arrayVarargsMethod(String[]... arrayVarArgs);
+
     void twoArgumentMethod(int one, int two);
 
     void arrayMethod(String[] strings);
@@ -235,6 +253,8 @@ String simpleMethod(
 
     Integer toIntWrapper(int i);
 
+    Integer toIntWrapperVarArgs(int i, Object... varargs);
+
     String forObject(Object object);
 
     <T> String genericToString(T arg);
diff --git a/src/test/java/org/mockitousage/MethodsImpl.java b/src/test/java/org/mockitousage/MethodsImpl.java
index bb69656712..e2d53ba7ba 100644
--- a/src/test/java/org/mockitousage/MethodsImpl.java
+++ b/src/test/java/org/mockitousage/MethodsImpl.java
@@ -254,11 +254,48 @@ public String threeArgumentMethodWithStrings(int valueOne, String valueTwo, Stri
         return null;
     }
 
+    public String threeArgumentVarArgsMethod(
+            final int valueOne, final String valueTwo, final String... valueThree) {
+        return null;
+    }
+
     public String fourArgumentMethod(
             int valueOne, String valueTwo, String valueThree, boolean[] array) {
         return null;
     }
 
+    public String fourArgumentVarArgsMethod(
+            final int valueOne,
+            final String valueTwo,
+            final int valueThree,
+            final String... valueFour) {
+        return null;
+    }
+
+    public String fiveArgumentVarArgsMethod(
+            final int valueOne,
+            final String valueTwo,
+            final int valueThree,
+            final String valueFour,
+            final String... valueFive) {
+        return null;
+    }
+
+    public String sixArgumentVarArgsMethod(
+            final int valueOne,
+            final String valueTwo,
+            final int valueThree,
+            final String valueFour,
+            final String valueFive,
+            final String... valueSix) {
+        return null;
+    }
+
+    @Override
+    public int arrayVarargsMethod(final String[]... arrayVarArgs) {
+        return 0;
+    }
+
     public void twoArgumentMethod(int one, int two) {}
 
     public void arrayMethod(String[] strings) {}
@@ -435,6 +472,11 @@ public Integer toIntWrapper(int i) {
         return null;
     }
 
+    @Override
+    public Integer toIntWrapperVarArgs(final int i, final Object... varargs) {
+        return null;
+    }
+
     public String forObject(Object object) {
         return null;
     }
diff --git a/src/test/java/org/mockitousage/stubbing/StubbingWithAdditionalAnswersTest.java b/src/test/java/org/mockitousage/stubbing/StubbingWithAdditionalAnswersTest.java
index 7dfed447a9..d037cfc0a0 100644
--- a/src/test/java/org/mockitousage/stubbing/StubbingWithAdditionalAnswersTest.java
+++ b/src/test/java/org/mockitousage/stubbing/StubbingWithAdditionalAnswersTest.java
@@ -5,6 +5,7 @@
 package org.mockitousage.stubbing;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.Assertions.within;
 import static org.mockito.AdditionalAnswers.answer;
 import static org.mockito.AdditionalAnswers.answerVoid;
@@ -22,11 +23,13 @@
 import static org.mockito.BDDMockito.times;
 import static org.mockito.BDDMockito.verify;
 
+import java.util.Arrays;
 import java.util.Date;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.exceptions.base.MockitoException;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer1;
 import org.mockito.stubbing.Answer2;
@@ -42,6 +45,7 @@
 import org.mockito.stubbing.VoidAnswer6;
 import org.mockitousage.IMethods;
 
+@SuppressWarnings({"Convert2Lambda", "Anonymous2MethodRef", "RedundantThrows"})
 @RunWith(MockitoJUnitRunner.class)
 public class StubbingWithAdditionalAnswersTest {
 
@@ -52,10 +56,42 @@ public void can_return_arguments_of_invocation() throws Exception {
         given(iMethods.objectArgMethod(any())).will(returnsFirstArg());
         given(iMethods.threeArgumentMethod(eq(0), any(), anyString())).will(returnsSecondArg());
         given(iMethods.threeArgumentMethod(eq(1), any(), anyString())).will(returnsLastArg());
+        given(iMethods.mixedVarargsReturningString(eq(1), any())).will(returnsArgAt(2));
 
         assertThat(iMethods.objectArgMethod("first")).isEqualTo("first");
         assertThat(iMethods.threeArgumentMethod(0, "second", "whatever")).isEqualTo("second");
         assertThat(iMethods.threeArgumentMethod(1, "whatever", "last")).isEqualTo("last");
+        assertThat(iMethods.mixedVarargsReturningString(1, "a", "b")).isEqualTo("b");
+    }
+
+    @Test
+    public void can_return_var_arguments_of_invocation() throws Exception {
+        given(iMethods.mixedVarargsReturningStringArray(eq(1), any())).will(returnsLastArg());
+        given(iMethods.mixedVarargsReturningObjectArray(eq(1), any())).will(returnsArgAt(1));
+
+        assertThat(iMethods.mixedVarargsReturningStringArray(1, "the", "var", "args"))
+                .containsExactlyInAnyOrder("the", "var", "args");
+        assertThat(iMethods.mixedVarargsReturningObjectArray(1, "the", "var", "args"))
+                .containsExactlyInAnyOrder("the", "var", "args");
+    }
+
+    @Test
+    public void returns_arg_at_throws_on_out_of_range_var_args() throws Exception {
+        given(iMethods.mixedVarargsReturningString(eq(1), any())).will(returnsArgAt(3));
+
+        assertThatThrownBy(() -> iMethods.mixedVarargsReturningString(1, "a", "b"))
+                .isInstanceOf(MockitoException.class)
+                .hasMessageContaining("Invalid argument index");
+    }
+
+    @Test
+    public void returns_arg_at_throws_on_out_of_range_array_var_args() throws Exception {
+        assertThatThrownBy(
+                        () ->
+                                given(iMethods.mixedVarargsReturningStringArray(eq(1), any()))
+                                        .will(returnsArgAt(3)))
+                .isInstanceOf(MockitoException.class)
+                .hasMessageContaining("The argument of type 'String' cannot be returned");
     }
 
     @Test
@@ -85,9 +121,11 @@ public void can_return_expanded_arguments_of_invocation() throws Exception {
     public void can_return_primitives_or_wrappers() throws Exception {
         given(iMethods.toIntPrimitive(anyInt())).will(returnsFirstArg());
         given(iMethods.toIntWrapper(anyInt())).will(returnsFirstArg());
+        given(iMethods.toIntWrapperVarArgs(anyInt(), any())).will(returnsFirstArg());
 
         assertThat(iMethods.toIntPrimitive(1)).isEqualTo(1);
         assertThat(iMethods.toIntWrapper(1)).isEqualTo(1);
+        assertThat(iMethods.toIntWrapperVarArgs(1, 10)).isEqualTo(1);
     }
 
     @Test
@@ -347,4 +385,340 @@ public void answer(
         // expect the answer to write correctly to "target"
         verify(target, times(1)).simpleMethod("hello", 1, 2, 3, 4, 5);
     }
+
+    @Test
+    public void can_return_based_on_strongly_types_one_parameter_var_args_function()
+            throws Exception {
+        given(iMethods.varargs(any()))
+                .will(
+                        answer(
+                                new Answer1<Integer, String[]>() {
+                                    public Integer answer(String[] strings) {
+                                        return strings.length;
+                                    }
+                                }));
+
+        assertThat(iMethods.varargs("some", "args")).isEqualTo(2);
+    }
+
+    @Test
+    public void will_execute_a_void_based_on_strongly_typed_one_parameter_var_args_function()
+            throws Exception {
+        final IMethods target = mock(IMethods.class);
+
+        given(iMethods.varargs(any()))
+                .will(
+                        answerVoid(
+                                new VoidAnswer1<String[]>() {
+                                    public void answer(String[] s) {
+                                        target.varargs(s);
+                                    }
+                                }));
+
+        // invoke on iMethods
+        iMethods.varargs("some", "args");
+
+        // expect the answer to write correctly to "target"
+        verify(target, times(1)).varargs("some", "args");
+    }
+
+    @Test
+    public void can_return_based_on_strongly_typed_two_parameter_var_args_function()
+            throws Exception {
+        given(iMethods.mixedVarargsReturningString(any(), any()))
+                .will(
+                        answer(
+                                new Answer2<String, Object, String[]>() {
+                                    public String answer(Object o, String[] s) {
+                                        return String.join("-", s);
+                                    }
+                                }));
+
+        assertThat(iMethods.mixedVarargsReturningString(1, "var", "args")).isEqualTo("var-args");
+    }
+
+    @Test
+    public void will_execute_a_void_based_on_strongly_typed_two_parameter_var_args_function()
+            throws Exception {
+        final IMethods target = mock(IMethods.class);
+
+        given(iMethods.mixedVarargsReturningString(any(), any()))
+                .will(
+                        answerVoid(
+                                new VoidAnswer2<Object, String[]>() {
+                                    public void answer(Object o, String[] s) {
+                                        target.mixedVarargsReturningString(o, s);
+                                    }
+                                }));
+
+        // invoke on iMethods
+        iMethods.mixedVarargsReturningString(1, "var", "args");
+
+        // expect the answer to write correctly to "target"
+        verify(target).mixedVarargsReturningString(1, "var", "args");
+    }
+
+    @Test
+    public void can_return_based_on_strongly_typed_three_parameter_var_args_function()
+            throws Exception {
+        final IMethods target = mock(IMethods.class);
+
+        given(iMethods.threeArgumentVarArgsMethod(anyInt(), any(), any()))
+                .will(
+                        answer(
+                                new Answer3<String, Integer, String, String[]>() {
+                                    public String answer(Integer i, String s1, String[] s2) {
+                                        target.threeArgumentVarArgsMethod(i, s1, s2);
+                                        return String.join("-", s2);
+                                    }
+                                }));
+
+        // invoke on iMethods
+        assertThat(iMethods.threeArgumentVarArgsMethod(1, "string1", "var", "args"))
+                .isEqualTo("var-args");
+
+        // expect the answer to write correctly to "target"
+        verify(target).threeArgumentVarArgsMethod(1, "string1", "var", "args");
+    }
+
+    @Test
+    public void will_execute_a_void_based_on_strongly_typed_three_parameter_var_args_function()
+            throws Exception {
+        final IMethods target = mock(IMethods.class);
+
+        given(iMethods.threeArgumentVarArgsMethod(anyInt(), any(), any()))
+                .will(
+                        answerVoid(
+                                new VoidAnswer3<Integer, String, String[]>() {
+                                    public void answer(Integer i, String s1, String[] s2) {
+                                        target.threeArgumentVarArgsMethod(i, s1, s2);
+                                    }
+                                }));
+
+        // invoke on iMethods
+        iMethods.threeArgumentVarArgsMethod(1, "string1", "var", "args");
+
+        // expect the answer to write correctly to "target"
+        verify(target, times(1)).threeArgumentVarArgsMethod(1, "string1", "var", "args");
+    }
+
+    @Test
+    public void can_return_based_on_strongly_typed_four_parameter_var_args_function()
+            throws Exception {
+        final IMethods target = mock(IMethods.class);
+        given(iMethods.fourArgumentVarArgsMethod(anyInt(), any(), anyInt(), any()))
+                .will(
+                        answer(
+                                new Answer4<String, Integer, String, Integer, String[]>() {
+                                    public String answer(
+                                            Integer i1, String s2, Integer i3, String[] s4) {
+                                        target.fourArgumentVarArgsMethod(i1, s2, i3, s4);
+                                        return String.join("-", s4);
+                                    }
+                                }));
+
+        // invoke on iMethods
+        String[] varargs = {"var", "args"};
+        assertThat(iMethods.fourArgumentVarArgsMethod(1, "string1", 3, varargs))
+                .isEqualTo("var-args");
+
+        // expect the answer to write correctly to "target"
+        verify(target, times(1)).fourArgumentVarArgsMethod(1, "string1", 3, varargs);
+    }
+
+    @Test
+    public void will_execute_a_void_based_on_strongly_typed_four_parameter_var_args_function()
+            throws Exception {
+        final IMethods target = mock(IMethods.class);
+
+        given(iMethods.fourArgumentVarArgsMethod(anyInt(), any(), anyInt(), any()))
+                .will(
+                        answerVoid(
+                                new VoidAnswer4<Integer, String, Integer, String[]>() {
+                                    public void answer(
+                                            Integer i, String s2, Integer i3, String[] s4) {
+                                        target.fourArgumentVarArgsMethod(i, s2, i3, s4);
+                                    }
+                                }));
+
+        // invoke on iMethods
+        iMethods.fourArgumentVarArgsMethod(1, "string1", 3, "var", "args");
+
+        // expect the answer to write correctly to "target"
+        verify(target, times(1)).fourArgumentVarArgsMethod(1, "string1", 3, "var", "args");
+    }
+
+    @Test
+    public void can_return_based_on_strongly_typed_five_parameter_var_args_function()
+            throws Exception {
+        final IMethods target = mock(IMethods.class);
+        given(iMethods.fiveArgumentVarArgsMethod(anyInt(), any(), anyInt(), any(), any()))
+                .will(
+                        answer(
+                                new Answer5<String, Integer, String, Integer, String, String[]>() {
+                                    public String answer(
+                                            Integer i1,
+                                            String s2,
+                                            Integer i3,
+                                            String s4,
+                                            String[] s5) {
+                                        target.fiveArgumentVarArgsMethod(i1, s2, i3, s4, s5);
+                                        return String.join("-", s5);
+                                    }
+                                }));
+
+        // invoke on iMethods
+        assertThat(iMethods.fiveArgumentVarArgsMethod(1, "two", 3, "four", "var", "args"))
+                .isEqualTo("var-args");
+
+        // expect the answer to write correctly to "target"
+        verify(target).fiveArgumentVarArgsMethod(1, "two", 3, "four", "var", "args");
+    }
+
+    @Test
+    public void will_execute_a_void_based_on_strongly_typed_five_parameter_var_args_function()
+            throws Exception {
+        final IMethods target = mock(IMethods.class);
+
+        given(iMethods.fiveArgumentVarArgsMethod(anyInt(), any(), anyInt(), any(), any()))
+                .will(
+                        answerVoid(
+                                new VoidAnswer5<Integer, String, Integer, String, String[]>() {
+                                    public void answer(
+                                            Integer i1,
+                                            String s2,
+                                            Integer i3,
+                                            String s4,
+                                            String[] s5) {
+                                        target.fiveArgumentVarArgsMethod(i1, s2, i3, s4, s5);
+                                    }
+                                }));
+
+        // invoke on iMethods
+        iMethods.fiveArgumentVarArgsMethod(1, "two", 3, "four", "var", "args");
+
+        // expect the answer to write correctly to "target"
+        verify(target).fiveArgumentVarArgsMethod(1, "two", 3, "four", "var", "args");
+    }
+
+    @Test
+    public void can_return_based_on_strongly_typed_six_parameter_var_args_function()
+            throws Exception {
+        final IMethods target = mock(IMethods.class);
+        given(iMethods.sixArgumentVarArgsMethod(anyInt(), any(), anyInt(), any(), any(), any()))
+                .will(
+                        answer(
+                                new Answer6<
+                                        String,
+                                        Integer,
+                                        String,
+                                        Integer,
+                                        String,
+                                        String,
+                                        String[]>() {
+                                    public String answer(
+                                            Integer i1,
+                                            String s2,
+                                            Integer i3,
+                                            String s4,
+                                            String s5,
+                                            String[] s6) {
+                                        target.sixArgumentVarArgsMethod(i1, s2, i3, s4, s5, s6);
+                                        return "answered";
+                                    }
+                                }));
+
+        // invoke on iMethods
+        assertThat(iMethods.sixArgumentVarArgsMethod(1, "two", 3, "four", "five", "var", "args"))
+                .isEqualTo("answered");
+
+        // expect the answer to write correctly to "target"
+        verify(target, times(1))
+                .sixArgumentVarArgsMethod(1, "two", 3, "four", "five", "var", "args");
+    }
+
+    @Test
+    public void will_execute_a_void_returning_strongly_typed_six_parameter_var_args_function()
+            throws Exception {
+        final IMethods target = mock(IMethods.class);
+        given(iMethods.sixArgumentVarArgsMethod(anyInt(), any(), anyInt(), any(), any(), any()))
+                .will(
+                        answerVoid(
+                                new VoidAnswer6<
+                                        Integer, String, Integer, String, String, String[]>() {
+                                    public void answer(
+                                            Integer i1,
+                                            String s2,
+                                            Integer i3,
+                                            String s4,
+                                            String s5,
+                                            String[] s6) {
+                                        target.sixArgumentVarArgsMethod(i1, s2, i3, s4, s5, s6);
+                                    }
+                                }));
+
+        // invoke on iMethods
+        iMethods.sixArgumentVarArgsMethod(1, "two", 3, "four", "five", "var", "args");
+
+        // expect the answer to write correctly to "target"
+        verify(target, times(1))
+                .sixArgumentVarArgsMethod(1, "two", 3, "four", "five", "var", "args");
+    }
+
+    @Test
+    public void can_accept_array_supertype_for_strongly_typed_var_args_function() throws Exception {
+        given(iMethods.varargs(any()))
+                .will(
+                        answer(
+                                new Answer1<Integer, Object[]>() {
+                                    public Integer answer(Object[] s) {
+                                        return s.length;
+                                    }
+                                }));
+
+        assertThat(iMethods.varargs("var", "args")).isEqualTo(2);
+    }
+
+    @Test
+    public void can_accept_non_vararg_answer_on_var_args_function() throws Exception {
+        given(iMethods.varargs(any()))
+                .will(
+                        answer(
+                                new Answer2<Integer, String, String>() {
+                                    public Integer answer(String s1, String s2) {
+                                        return s1.length() + s2.length();
+                                    }
+                                }));
+
+        assertThat(iMethods.varargs("var", "args")).isEqualTo(7);
+    }
+
+    @Test
+    public void should_work_with_var_args_with_no_elements() throws Exception {
+        given(iMethods.varargs(any()))
+                .will(
+                        answer(
+                                new Answer1<Integer, String[]>() {
+                                    public Integer answer(String[] s) {
+                                        return s.length;
+                                    }
+                                }));
+
+        assertThat(iMethods.varargs()).isEqualTo(0);
+    }
+
+    @Test
+    public void should_work_with_array_var_args() throws Exception {
+        given(iMethods.arrayVarargsMethod(any()))
+                .will(
+                        answer(
+                                new Answer1<Integer, String[][]>() {
+                                    public Integer answer(String[][] s) {
+                                        return Arrays.stream(s).mapToInt(e -> e.length).sum();
+                                    }
+                                }));
+
+        String[][] varArgs = {{}, {""}, {"", ""}};
+        assertThat(iMethods.arrayVarargsMethod(varArgs)).isEqualTo(3);
+    }
 }