diff --git a/async-method-invocation/pom.xml b/async-method-invocation/pom.xml
new file mode 100644
index 000000000..be932bca5
--- /dev/null
+++ b/async-method-invocation/pom.xml
@@ -0,0 +1,18 @@
+
+
+  4.0.0
+  
+    com.iluwatar
+    java-design-patterns
+    1.3.0
+  
+  async-method-invocation
+  
+    
+      junit
+      junit
+      test
+    
+  
+
diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java
new file mode 100644
index 000000000..f59f831b2
--- /dev/null
+++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java
@@ -0,0 +1,50 @@
+package com.iluwatar.async.method.invocation;
+
+import java.util.concurrent.Callable;
+
+public class App {
+
+	public static void main(String[] args) throws Exception {
+		AsyncExecutor executor = new ThreadAsyncExecutor();
+		AsyncResult asyncResult1 = executor.startProcess(lazyval(10, 500));
+		AsyncResult asyncResult2 = executor.startProcess(lazyval("test", 300));
+		AsyncResult asyncResult3 = executor.startProcess(lazyval(50L, 700));
+		AsyncResult asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4"));
+		AsyncResult asyncResult5 = executor.startProcess(lazyval("callback", 600), callback("Callback result 5"));
+
+		Thread.sleep(350); // Oh boy I'm working hard here
+		log("Some hard work done");
+
+		Integer result1 = executor.endProcess(asyncResult1);
+		String result2 = executor.endProcess(asyncResult2);
+		Long result3 = executor.endProcess(asyncResult3);
+		asyncResult4.await();
+		asyncResult5.await();
+
+		log("Result 1: " + result1);
+		log("Result 2: " + result2);
+		log("Result 3: " + result3);
+	}
+
+	private static  Callable lazyval(T value, long delayMillis) {
+		return () -> {
+			Thread.sleep(delayMillis);
+			log("Task completed with: " + value);
+			return value;
+		};
+	}
+
+	private static  AsyncCallback callback(String name) {
+		return (value, ex) -> {
+			if (ex.isPresent()) {
+				log(name + " failed: " + ex.map(Exception::getMessage).orElse(""));
+			} else {
+				log(name + ": " + value);
+			}
+		};
+	}
+
+	private static void log(String msg) {
+		System.out.println(String.format("[%1$-10s] - %2$s", Thread.currentThread().getName(), msg));
+	}
+}
diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java
new file mode 100644
index 000000000..0477f70e1
--- /dev/null
+++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java
@@ -0,0 +1,9 @@
+package com.iluwatar.async.method.invocation;
+
+import java.util.Optional;
+
+public interface AsyncCallback {
+
+	void onComplete(T value, Optional ex);
+
+}
diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java
new file mode 100644
index 000000000..dd23a8bfb
--- /dev/null
+++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java
@@ -0,0 +1,14 @@
+package com.iluwatar.async.method.invocation;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
+public interface AsyncExecutor {
+
+	 AsyncResult startProcess(Callable task);
+
+	 AsyncResult startProcess(Callable task, AsyncCallback callback);
+
+	 T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException;
+
+}
diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java
new file mode 100644
index 000000000..5bf6145b8
--- /dev/null
+++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java
@@ -0,0 +1,12 @@
+package com.iluwatar.async.method.invocation;
+
+import java.util.concurrent.ExecutionException;
+
+public interface AsyncResult {
+
+	boolean isCompleted();
+
+	T getValue() throws ExecutionException;
+
+	void await() throws InterruptedException;
+}
diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java
new file mode 100644
index 000000000..b368e284d
--- /dev/null
+++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java
@@ -0,0 +1,101 @@
+package com.iluwatar.async.method.invocation;
+
+import java.util.Optional;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ThreadAsyncExecutor implements AsyncExecutor {
+
+	private final AtomicInteger idx = new AtomicInteger(0);
+
+	@Override
+	public  AsyncResult startProcess(Callable task) {
+		return startProcess(task, null);
+	}
+
+	@Override
+	public  AsyncResult startProcess(Callable task, AsyncCallback callback) {
+		CompletableResult result = new CompletableResult<>(callback);
+		new Thread(() -> {
+			try {
+				result.setValue(task.call());
+			} catch (Exception ex) {
+				result.setException(ex);
+			}
+		}, "executor-" + idx.incrementAndGet()).start();
+		return result;
+	}
+
+	@Override
+	public  T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException {
+		if (asyncResult.isCompleted()) {
+			return asyncResult.getValue();
+		} else {
+			asyncResult.await();
+			return asyncResult.getValue();
+		}
+	}
+
+	private static class CompletableResult implements AsyncResult {
+
+		static final int RUNNING = 1;
+		static final int FAILED = 2;
+		static final int COMPLETED = 3;
+
+		final Object lock;
+		final Optional> callback;
+
+		volatile int state = RUNNING;
+		T value;
+		Exception exception;
+
+		CompletableResult(AsyncCallback callback) {
+			this.lock = new Object();
+			this.callback = Optional.ofNullable(callback);
+		}
+
+		void setValue(T value) {
+			this.value = value;
+			this.state = COMPLETED;
+			this.callback.ifPresent(ac -> ac.onComplete(value, Optional.empty()));
+			synchronized (lock) {
+				lock.notifyAll();
+			}
+		}
+
+		void setException(Exception exception) {
+			this.exception = exception;
+			this.state = FAILED;
+			this.callback.ifPresent(ac -> ac.onComplete(null, Optional.of(exception)));
+			synchronized (lock) {
+				lock.notifyAll();
+			}
+		}
+
+		@Override
+		public boolean isCompleted() {
+			return (state > RUNNING);
+		}
+
+		@Override
+		public T getValue() throws ExecutionException {
+			if (state == COMPLETED) {
+				return value;
+			} else if (state == FAILED) {
+				throw new ExecutionException(exception);
+			} else {
+				throw new IllegalStateException("Execution not completed yet");
+			}
+		}
+
+		@Override
+		public void await() throws InterruptedException {
+			synchronized (lock) {
+				if (!isCompleted()) {
+					lock.wait();
+				}
+			}
+		}
+	}
+}
diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java
new file mode 100644
index 000000000..989fa6d1d
--- /dev/null
+++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java
@@ -0,0 +1,13 @@
+package com.iluwatar.async.method.invocation;
+
+import org.junit.Test;
+
+public class AppTest {
+
+	@Test
+	public void test() throws Exception {
+		String[] args = {};
+		App.main(args);
+	}
+
+}
diff --git a/pom.xml b/pom.xml
index aa484f15a..93bb137b4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,6 +71,7 @@
 		naked-objects
 		front-controller
 		repository
+		async-method-invocation