diff --git a/src/query/interpreter.hpp b/src/query/interpreter.hpp
index f78efbedf..b847a0c1e 100644
--- a/src/query/interpreter.hpp
+++ b/src/query/interpreter.hpp
@@ -47,8 +47,12 @@ class Interpreter {
         }
 
         // stripped query -> AST
-        frontend::opencypher::Parser parser(query);
-        auto low_level_tree = parser.tree();
+        auto parser = [&] {
+          // Be careful about unlocking since parser can throw.
+          std::unique_lock<SpinLock>(antlr_lock_);
+          return std::make_unique<frontend::opencypher::Parser>(query);
+        }();
+        auto low_level_tree = parser->tree();
 
         // AST -> high level tree
         frontend::CypherMainVisitor visitor(ctx);
@@ -63,8 +67,13 @@ class Interpreter {
       auto it = ast_cache_accessor.find(stripped.hash());
       if (it == ast_cache_accessor.end()) {
         // stripped query -> AST
-        frontend::opencypher::Parser parser(stripped.query());
-        auto low_level_tree = parser.tree();
+        auto parser = [&] {
+          // Be careful about unlocking since parser can throw.
+          std::unique_lock<SpinLock>(antlr_lock_);
+          return std::make_unique<frontend::opencypher::Parser>(
+              stripped.query());
+        }();
+        auto low_level_tree = parser->tree();
 
         // AST -> high level tree
         frontend::CypherMainVisitor visitor(ctx);
@@ -185,6 +194,13 @@ class Interpreter {
 
  private:
   ConcurrentMap<HashType, CachedAst> ast_cache_;
+  // Antlr has singleton instance that is shared between threads. It is
+  // protected by locks inside of antlr. Unfortunately, they are not protected
+  // in a very good way. Once we have antlr version without race conditions we
+  // can remove this lock. This will probably never happen since antlr
+  // developers introduce more bugs in each version. Fortunately, we have cache
+  // so this lock probably won't impact performance much...
+  SpinLock antlr_lock_;
 };
 
 }  // namespace query