diff --git a/doc/user/stored-procedures.xml b/doc/user/stored-procedures.xml
index 4653a3f2e4775062f6ba4efcf0951225726bc96a..8438499ae1e07ca6e6067f2f4b6085df93c979ff 100644
--- a/doc/user/stored-procedures.xml
+++ b/doc/user/stored-procedures.xml
@@ -891,6 +891,36 @@ lua box.dostring('local f = function(key) t=box.select(0, 0, key); if t ~= nil t
             </programlisting>
         </listitem>
     </varlistentry>
+    <varlistentry>
+        <term>
+            <emphasis role="lua">box.raise(errcode, errtext)</emphasis>
+        </term>
+        <listitem>
+            <para>
+                Raises a client error. The difference between this function
+                and the built-in <code>error()</code> function in Lua
+                is that when the error reaches the client, it's error code
+                is preserved, whereas every Lua error is presented to the
+                client as <constant>ER_PROC_LUA</constant>. This function
+                makes it possible to emulate any kind of native exception,
+                such as a unique constraint violation, no such space/index,
+                etc. A complete list of errors is present in <link xlink:href="https://github.com/mailru/tarantool/blob/master/include/errcode.h">errcode.h</link>
+                file in the source tree.
+                Lua constants which correspond to Tarantool/Box errors
+                are defined in <code>box.error</code> module. The error
+                message can be arbitrary.
+                Throws client error. Lua procedure can emulate any
+                request errors (for example: unique key exception).
+            </para>
+            <bridgehead renderas="sect4">Example</bridgehead>
+            <programlisting>
+                lua box.raise(box.error.ER_WAL_IO, 'Wal I/O error')
+                ---
+                error: 'Wal I/O error'
+                ...
+            </programlisting>
+        </listitem>
+    </varlistentry>
     <varlistentry>
         <term>
             <emphasis role="lua">box.auto_increment(space_no, ...)</emphasis>
diff --git a/include/exception.h b/include/exception.h
index f226f6b8709416586e059dcb3ce647be15469838..7f23523e543b21ef8955f4ccf7018b6d8e40616a 100644
--- a/include/exception.h
+++ b/include/exception.h
@@ -75,6 +75,7 @@
 }
 
 - (id) init: (uint32_t)errcode_, ...;
+- (id) init: (uint32_t)errcode_  :(const char *)msg;
 - (id) init: (uint32_t)errcode_ args :(va_list)ap;
 - (void) log;
 - (const char *) errmsg;
diff --git a/src/box/box_lua.m b/src/box/box_lua.m
index 9e9b45547dc8265d717916b14cc0d7f674329e8c..d0ae1e17f983d79fd035f9ec2445910afb1fe932 100644
--- a/src/box/box_lua.m
+++ b/src/box/box_lua.m
@@ -1147,8 +1147,22 @@ lbox_process(lua_State *L)
 	return lua_gettop(L) - top;
 }
 
+static int
+lbox_raise(lua_State *L)
+{
+	if (lua_gettop(L) != 2)
+		luaL_error(L, "box.raise(): bad arguments");
+	uint32_t code = lua_tointeger(L, 1);
+	if (!code)
+		luaL_error(L, "box.raise(): unknown error code");
+	const char *str = lua_tostring(L, 2);
+	tnt_raise(ClientError, :code :str);
+	return 0;
+}
+
 static const struct luaL_reg boxlib[] = {
 	{"process", lbox_process},
+	{"raise", lbox_raise},
 	{NULL, NULL}
 };
 
diff --git a/src/exception.m b/src/exception.m
index b65ff6e9467f80a0ff61a760c6c038561d876e6a..a09b6d39037a4938ca9404bf445534a80c2a1a06 100644
--- a/src/exception.m
+++ b/src/exception.m
@@ -113,6 +113,14 @@
 	return self;
 }
 
+- (id) init: (uint32_t)errcode_ :(const char *)msg
+{
+	[super init];
+	errcode = errcode_;
+	snprintf(errmsg, sizeof(errmsg), "%s", msg);
+	return self;
+}
+
 - (void) log
 {
 	say_error("%s at %s:%d, %s", object_getClassName(self),
diff --git a/test/box/lua.result b/test/box/lua.result
index 77fab6ea919ef47c01d41d8690cad03b60e768f5..5a632589e0604caabec4db3bb329f3a884024274 100644
--- a/test/box/lua.result
+++ b/test/box/lua.result
@@ -16,32 +16,33 @@ lua for n in pairs(box) do print('  - box.', n) end
   - box.space
   - box.cfg
   - box.on_reload_configuration
+  - box.bless_space
   - box.time64
   - box.uuid
   - box.ipc
   - box.delete
-  - box.bless_space
-  - box.replace
   - box.counter
+  - box.replace
   - box.auto_increment
+  - box.update
   - box.time
   - box.select_range
   - box.insert
-  - box.update
   - box.select_reverse_range
+  - box.select
   - box.info
   - box.session
   - box.uuid_hex
-  - box.select
+  - box.dostring
   - box.slab
   - box.process
-  - box.dostring
   - box.select_limit
-  - box.stat
   - box.flags
+  - box.stat
+  - box.index
   - box.unpack
   - box.pack
-  - box.index
+  - box.raise
   - box.socket
 ...
 lua box.pack()
diff --git a/test/box/lua_misc.result b/test/box/lua_misc.result
new file mode 100644
index 0000000000000000000000000000000000000000..769a15530bfd45868ced886e2109205ba23ce8d4
--- /dev/null
+++ b/test/box/lua_misc.result
@@ -0,0 +1,94 @@
+
+#
+# box.raise
+#
+
+lua 1 + 1
+---
+ - 2
+...
+lua box.raise(123, 'test')
+---
+error: 'test'
+...
+lua box.raise(0, 'the other test')
+---
+error: 'box.raise(): unknown error code'
+...
+lua box.raise(12, 345)
+---
+error: '345'
+...
+
+#
+# box.stat
+#
+
+lua for k, v in pairs(box.stat()) do print(k) end
+---
+DELETE
+SELECT
+REPLACE
+CALL
+UPDATE
+DELETE_1_3
+...
+lua for k, v in pairs(box.stat().DELETE) do print(k) end
+---
+total
+rps
+...
+lua for k, v in pairs(box.stat.DELETE) do print(k) end
+---
+total
+rps
+...
+
+#
+# box.space
+#
+
+lua type(box)
+---
+ - table
+...
+lua type(box.space)
+---
+ - table
+...
+lua box.cfg.memcached_space
+---
+ - 23
+...
+lua for i, v in pairs(box.space[0].index[0].key_field[0]) do print(i, ': ', v) end
+---
+type: NUM
+fieldno: 0
+...
+
+#
+# box.space
+#
+
+lua string.match(tostring(box.slab), '^table:') ~= nil
+---
+ - true
+...
+lua box.slab.arena_used >= 0
+---
+ - true
+...
+lua box.slab.arena_size > 0
+---
+ - true
+...
+lua string.match(tostring(box.slab.slabs), '^table:') ~= nil
+---
+ - true
+...
+lua for k, v in pairs(box.slab()) do print(k) end
+---
+slabs
+arena_size
+arena_used
+...
diff --git a/test/box/lua_misc.test b/test/box/lua_misc.test
new file mode 100644
index 0000000000000000000000000000000000000000..1aff48928406df26ba827975bb379fd114ce9ab4
--- /dev/null
+++ b/test/box/lua_misc.test
@@ -0,0 +1,43 @@
+# encoding: tarantool
+print """
+#
+# box.raise
+#
+"""
+exec admin "lua 1 + 1"
+exec admin "lua box.raise(123, 'test')"
+exec admin "lua box.raise(0, 'the other test')"
+exec admin "lua box.raise(12, 345)"
+
+print """
+#
+# box.stat
+#
+"""
+
+exec admin "lua for k, v in pairs(box.stat()) do print(k) end"
+exec admin "lua for k, v in pairs(box.stat().DELETE) do print(k) end"
+exec admin "lua for k, v in pairs(box.stat.DELETE) do print(k) end"
+
+print """
+#
+# box.space
+#
+"""
+
+exec admin "lua type(box)"
+exec admin "lua type(box.space)"
+exec admin "lua box.cfg.memcached_space"
+exec admin "lua for i, v in pairs(box.space[0].index[0].key_field[0]) do print(i, ': ', v) end"
+
+print """
+#
+# box.space
+#
+"""
+
+exec admin "lua string.match(tostring(box.slab), '^table:') ~= nil"
+exec admin "lua box.slab.arena_used >= 0"
+exec admin "lua box.slab.arena_size > 0"
+exec admin "lua string.match(tostring(box.slab.slabs), '^table:') ~= nil"
+exec admin "lua for k, v in pairs(box.slab()) do print(k) end"
diff --git a/test/box/slab.result b/test/box/slab.result
deleted file mode 100644
index 07a80527be565c3bba6a7f64d601674db3c02fb7..0000000000000000000000000000000000000000
--- a/test/box/slab.result
+++ /dev/null
@@ -1,22 +0,0 @@
-lua string.match(tostring(box.slab), '^table:') ~= nil
----
- - true
-...
-lua box.slab.arena_used >= 0
----
- - true
-...
-lua box.slab.arena_size > 0
----
- - true
-...
-lua string.match(tostring(box.slab.slabs), '^table:') ~= nil
----
- - true
-...
-lua for k, v in pairs(box.slab()) do print(k) end
----
-slabs
-arena_size
-arena_used
-...
diff --git a/test/box/slab.test b/test/box/slab.test
deleted file mode 100644
index 9adf3dcd7577c42a43429e63c22f7a2cc7118e5e..0000000000000000000000000000000000000000
--- a/test/box/slab.test
+++ /dev/null
@@ -1,9 +0,0 @@
-# encoding: tarantool
-# 
-import sys
-
-exec admin "lua string.match(tostring(box.slab), '^table:') ~= nil"
-exec admin "lua box.slab.arena_used >= 0"
-exec admin "lua box.slab.arena_size > 0"
-exec admin "lua string.match(tostring(box.slab.slabs), '^table:') ~= nil"
-exec admin "lua for k, v in pairs(box.slab()) do print(k) end"
diff --git a/test/box/space.result b/test/box/space.result
deleted file mode 100644
index 4d48f37e129d6f7a4a64187a4f186a7952535470..0000000000000000000000000000000000000000
--- a/test/box/space.result
+++ /dev/null
@@ -1,17 +0,0 @@
-lua type(box)
----
- - table
-...
-lua type(box.space)
----
- - table
-...
-lua box.cfg.memcached_space
----
- - 23
-...
-lua for i, v in pairs(box.space[0].index[0].key_field[0]) do print(i, ': ', v) end
----
-type: NUM
-fieldno: 0
-...
diff --git a/test/box/space.test b/test/box/space.test
deleted file mode 100644
index a421d2685268a19bfd5d8c34231f3bd7a7512afb..0000000000000000000000000000000000000000
--- a/test/box/space.test
+++ /dev/null
@@ -1,5 +0,0 @@
-# encoding: tarantool
-exec admin "lua type(box)"
-exec admin "lua type(box.space)"
-exec admin "lua box.cfg.memcached_space"
-exec admin "lua for i, v in pairs(box.space[0].index[0].key_field[0]) do print(i, ': ', v) end"
diff --git a/test/box/stat_lua.result b/test/box/stat_lua.result
deleted file mode 100644
index 0d400c3ef1a0a7401deef8bd5b3cca3a8764c3df..0000000000000000000000000000000000000000
--- a/test/box/stat_lua.result
+++ /dev/null
@@ -1,19 +0,0 @@
-lua for k, v in pairs(box.stat()) do print(k) end
----
-DELETE
-SELECT
-REPLACE
-CALL
-UPDATE
-DELETE_1_3
-...
-lua for k, v in pairs(box.stat().DELETE) do print(k) end
----
-total
-rps
-...
-lua for k, v in pairs(box.stat.DELETE) do print(k) end
----
-total
-rps
-...
diff --git a/test/box/stat_lua.test b/test/box/stat_lua.test
deleted file mode 100644
index 39c73e0d4f2a479e2d0b6672f2aec25d7b131c1a..0000000000000000000000000000000000000000
--- a/test/box/stat_lua.test
+++ /dev/null
@@ -1,7 +0,0 @@
-# encoding: tarantool
-import os
-import sys
-
-exec admin "lua for k, v in pairs(box.stat()) do print(k) end"
-exec admin "lua for k, v in pairs(box.stat().DELETE) do print(k) end"
-exec admin "lua for k, v in pairs(box.stat.DELETE) do print(k) end"