diff --git a/src/main/java/com/kms/handler/CacheHandler.java b/src/main/java/com/kms/handler/CacheHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..9ffb74ee88c10d58ae3c2eed0b524cec935f4e4c
--- /dev/null
+++ b/src/main/java/com/kms/handler/CacheHandler.java
@@ -0,0 +1,91 @@
+package com.kms.handler;
+
+import com.kms.handler.helper.SoapFaultHelper;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+
+import javax.xml.namespace.QName;
+import javax.xml.soap.MessageFactory;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPMessage;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.ws.handler.MessageContext;
+import javax.xml.ws.handler.soap.SOAPHandler;
+import javax.xml.ws.handler.soap.SOAPMessageContext;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Set;
+
+public class CacheHandler implements SOAPHandler<SOAPMessageContext> {
+
+    private static final JedisPool jedisPool = new JedisPool("localhost", 6379);
+    private static final int DEFAULT_CACHE_TTL = 300;
+
+    @Override
+    public Set<QName> getHeaders() {
+        return null;
+    }
+
+    @Override
+    public boolean handleMessage(SOAPMessageContext context) {
+        final boolean messageHandled =
+                (boolean) context.getOrDefault("messageHandled", false);
+
+        if (!messageHandled)
+            return true;
+
+        final boolean cacheResponse =
+                (boolean) context.getOrDefault("cacheResponse", false);
+        if (cacheResponse) {
+            final String cacheKey = (String) context.getOrDefault("cacheKey", null);
+            if (cacheKey == null)
+                return true;
+
+            try (Jedis jedis = jedisPool.getResource()) {
+                OutputStream responseTextOStream = new ByteArrayOutputStream();
+                context.getMessage().writeTo(responseTextOStream);
+
+                jedis.set(cacheKey, responseTextOStream.toString());
+                jedis.expire(cacheKey, DEFAULT_CACHE_TTL);
+
+                System.out.println("Response cached with key: " + cacheKey);
+            } catch (SOAPException | IOException ignored) {
+            }
+
+            return true;
+        }
+
+        final boolean returnFromCache =
+                (boolean) context.getOrDefault("returnFromCache", false);
+        if (!returnFromCache)
+            return true;
+
+        final String cacheKey = (String) context.getOrDefault("cacheKey", null);
+        if (cacheKey == null) {
+            SoapFaultHelper.throwFault("Error", "Unexpected error has happened.");
+        }
+        try (Jedis jedis = jedisPool.getResource()) {
+            String response = jedis.get(cacheKey);
+            SOAPMessage soapMessage = MessageFactory.newInstance().createMessage();
+            soapMessage.getSOAPPart().setContent(new StreamSource(new java.io.StringReader(response)));
+
+            context.setMessage(soapMessage);
+
+            System.out.println("Return from cache with key: "+cacheKey);
+        } catch (SOAPException ignored) {
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean handleFault(SOAPMessageContext context) {
+        return false;
+    }
+
+    @Override
+    public void close(MessageContext context) {
+
+    }
+}
diff --git a/src/main/java/com/kms/service/PaymentHistoryService.java b/src/main/java/com/kms/service/PaymentHistoryService.java
index b29db99e5aa23c9586618e0e2e15ed6788a6f180..54c79cdb0d64ef765e5de07565fe5da6c29a81f6 100644
--- a/src/main/java/com/kms/service/PaymentHistoryService.java
+++ b/src/main/java/com/kms/service/PaymentHistoryService.java
@@ -2,25 +2,32 @@ package com.kms.service;
 
 import com.j256.ormlite.dao.Dao;
 import com.kms.dto.EmailReq;
-import com.kms.dto.PaymentHistoryResp;
+import com.kms.dto.payment.PaymentHistoryResp;
 
 import com.kms.model.Payment;
+import com.kms.service.helper.PaymentHistoryCacheHelper;
+import com.kms.util.PdfUtil;
 import freemarker.template.Configuration;
 import freemarker.template.Template;
 import freemarker.template.TemplateException;
 import freemarker.template.TemplateExceptionHandler;
 import lombok.RequiredArgsConstructor;
 
+import javax.annotation.Resource;
 import javax.jws.HandlerChain;
 import javax.jws.WebMethod;
 import javax.jws.WebParam;
 import javax.jws.WebService;
+import javax.xml.ws.WebServiceContext;
+import javax.xml.ws.handler.MessageContext;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.sql.SQLException;
+import java.sql.Timestamp;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 @WebService
@@ -28,6 +35,9 @@ import java.util.stream.Collectors;
 @HandlerChain(file = "handlers.xml")
 public class PaymentHistoryService {
 
+    @Resource
+    private WebServiceContext context;
+
     private final Dao<Payment,Integer> paymentDao;
 
     private static final Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
@@ -38,10 +48,24 @@ public class PaymentHistoryService {
         cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
     }
 
+
     @WebMethod
     public List<PaymentHistoryResp> getPaymentHistory(@WebParam(name = "initiatorId") String initiatorId,
                                                       @WebParam(name = "sendMail") boolean sendMail,
                                                       @WebParam(name = "mailData") EmailReq emailReq) {
+        MessageContext messageContext = context.getMessageContext();
+
+        String cacheKey = PaymentHistoryCacheHelper.checkForHistoryCacheAvailability(initiatorId);
+        if (!cacheKey.isEmpty()) {
+            messageContext.put("returnFromCache", true);
+            messageContext.put("cacheKey", cacheKey);
+
+            if (sendMail && emailReq != null)
+                EmailService.send(emailReq.getRecipient(), emailReq.getCc(),
+                        "Cache: KMS Payment History", "sda");
+            return null;
+        }
+
         try {
             Payment filter = Payment.builder()
                     .initiatorId(initiatorId)
@@ -50,21 +74,35 @@ public class PaymentHistoryService {
             List<Payment> payments = paymentDao.queryForMatching(filter);
 
             if (sendMail && emailReq != null) {
-                EmailService.send(emailReq.getRecipient(), emailReq.getCc(),
-                        "KMS Payment History", formHistoryMailBody(payments));
+                String html = constructHistoryHtml(payments);
 
+                EmailService.send(emailReq.getRecipient(), emailReq.getCc(),
+                        "KMS Payment History", html);
             }
 
+            cacheKey = PaymentHistoryCacheHelper.formHistoryCacheKey(initiatorId);
+            messageContext.put("cacheResponse", true);
+            messageContext.put("cacheKey", cacheKey);
+
             return payments.stream()
-                    .map(el -> new PaymentHistoryResp(el.getPaymentInitTime().toString(), el.getPaymentPaidTime().toString(),
-                                    el.getAmount(), el.getDescription()))
+                    .filter(el -> !(el.getPaymentInitTime() == null || el.getPaymentPaidTime() == null))
+                    .map(el -> {
+                        String initTime = Optional.of(el.getPaymentInitTime())
+                                .orElseGet(() -> new Timestamp(0))
+                                .toString(),
+                            paidTime = Optional.of(el.getPaymentPaidTime())
+                                .orElseGet(() -> new Timestamp(0))
+                                .toString();
+
+                        return new PaymentHistoryResp(initTime, paidTime, el.getAmount(), el.getDescription());
+                    })
                     .collect(Collectors.toList());
         } catch (SQLException | TemplateException | IOException e) {
-            throw new RuntimeException(e);
+            return null;
         }
     }
 
-    private String formHistoryMailBody(List<Payment> payments) throws IOException, TemplateException {
+    private String constructHistoryHtml(List<Payment> payments) throws IOException, TemplateException {
         Template template = cfg.getTemplate("history.ftl");
         Map<String, Object> dataModel = new HashMap<>();
         dataModel.put("payments", payments);
diff --git a/src/main/java/com/kms/service/helper/PaymentHistoryCacheHelper.java b/src/main/java/com/kms/service/helper/PaymentHistoryCacheHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..f58013a74f4a6b38bf222cc9cdc759f2c8546479
--- /dev/null
+++ b/src/main/java/com/kms/service/helper/PaymentHistoryCacheHelper.java
@@ -0,0 +1,35 @@
+package com.kms.service.helper;
+
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+
+public class PaymentHistoryCacheHelper {
+
+    private static final JedisPool jedisPool = new JedisPool("localhost", 6379);
+
+    /**
+     *
+     * @param initiatorId initiator id for cache key
+     * @return cache key, empty if cache is not available
+     */
+    public static String checkForHistoryCacheAvailability(String initiatorId) {
+        String cacheKey = formHistoryCacheKey(initiatorId);
+        try (Jedis jedis = jedisPool.getResource()) {
+            boolean keyExists = jedis.exists(cacheKey);
+            if (!keyExists)
+                return "";
+
+            long ttlSeconds = jedis.ttl(cacheKey);
+            if (ttlSeconds < 3) {
+                jedis.expire(cacheKey, 3);
+            }
+            return cacheKey;
+        }
+    }
+
+    public static String formHistoryCacheKey(String initiatorId) {
+        System.out.println("CACHE KEY: " + "payment-history:"+initiatorId);
+        return "payment-history:"+initiatorId;
+    }
+
+}