Error Handling
Exception Handling
General Information
When working with the Java client, you may encounter the following categories of errors:
| Tarantool-side Error | Error Representation in Java Client | Description |
|---|---|---|
| Exceptional situations occurring during Lua code execution on the Tarantool application server. Exceptions are thrown via box.error(...) or error(...) | Errors are transmitted to the Java client at the IProto protocol level using a
special
package. In the Java client, such errors are wrapped in an exception of type BoxError |
You can get this type of error in the Java client in the following cases:
|
| Errors returned as structures within a multivalue tuple without throwing exceptions (similar to Go) on the Tarantool side | The response from Tarantool is converted according to general deserialization rules | This situation is not considered exceptional by the Java client.
The response with an error object is converted to Java objects according to general deserialization rules. When working
with TarantoolCrudClient, the tuple response is converted to an object of type
CrudResponse
|
| - | Exceptions are generated on the Java client side | These exceptions are generated by the Java client itself depending on various situations. See the full list of exceptions |
Exceptional Situations on Tarantool Side
TarantoolBoxClient API
When using TarantoolBoxClient, you can get an exception of type BoxError in case
of incorrect API usage.
Example 1: Re-inserting a record with the same identifier
Create a space named person:
person_space = box.schema.space.create('person', {
if_not_exists = true,
format = {
{ 'id', type = 'uuid' },
{ 'value', type = 'string', is_nullable = true }
}
})
person_space:create_index('pk', { parts = { 'id' } })
Create an instance of TarantoolBoxClient and insert a record (the record should appear in Tarantool):
final TarantoolBoxClient boxClient = TarantoolFactory.box()
.host("paste-host-name")
.port(3302)
.user("paste-username")
.password("paste-username-password")
.build();
final TarantoolBoxSpace space = boxClient.space("person");
final List<?> tuple = Arrays.asList(UUID.randomUUID(), "some_value");
final Tuple<List<?>> insertedTuple = space.insert(tuple);
Re-inserting a record will cause a CompletionException to be thrown with a reference (caused by)
to BoxError:
final List<?> tupleWithNewValue = Arrays.asList(tuple.get(0), "some_value_2");
// stacktrace:
// java.util.concurrent.CompletionException: io.tarantool.core.exceptions.BoxError:
// BoxError{code=3, message='Duplicate key exists in unique index "pk" in space "tt" with
// old tuple - [bdf657fc-5779-46d6-aea6-402e4a5eee38, "some_value"] and new tuple -
// [bdf657fc-5779-46d6-aea6-402e4a5eee38, "some_value"]',
// stack=[BoxErrorStackItem{type='ClientError', line=1133, file='./src/box/memtx_tree.cc',
// message='Duplicate key exists in unique index "pk" in space "tt" with old tuple -
// [bdf657fc-5779-46d6-aea6-402e4a5eee38, "some_value"] and new tuple -
// [bdf657fc-5779-46d6-aea6-402e4a5eee38, "some_value"]', errno=0, code=3, details=null}]}
final Tuple<List<?>> insertedTuple = space.insert(tuple).join();
Example 2: Deleting a record with an incorrect key type
We will use the person space created in Example 1. Delete
a record by passing an incorrect key (represented as a string):
final TarantoolBoxSpace space = boxClient.space("person");
final String uuidAsString = tuple.get(0).toString();
// stacktrace:
// io.tarantool.core.exceptions.BoxError: BoxError{code=18, message='Supplied key
// type of part 0 does not match index part type: expected uuid',
// stack=[BoxErrorStackItem{type='ClientError', line=850, file='./src/box/key_def.h',
// message='Supplied key type of part 0 does not match index part type: expected uuid',
// errno=0, code=18, details=null}]}
final Tuple<List<?>> deletedTuple = space.delete(uuidAsString).join();
Important
To learn more about the box module, refer to documentation
Exceptions on Tarantool Side During Call to TarantoolClient#call(...)
Create stored procedures that check the type of the passed parameter:
function check_parameter_with_error(param)
if type(param) ~= "string" then
error("Parameter must be a string")
end
return "Parameter is valid"
end
box.schema.func.create('check_parameter_with_error')
function check_parameter_with_box_error(param)
if type(param) ~= "string" then
box.error(box.error.PROC_LUA, "Parameter must be a string")
end
return "Parameter is valid"
end
box.schema.func.create('check_parameter_with_box_error')
Call the stored procedures from Java:
import java.util.Collections;
import java.util.List;
import java.util.UUID;
final List<String> stringParam = Collections.singletonList("some_value");
// OK
// TarantoolResponse(data = [Parameter is valid], formats = {})
final TarantoolResponse<List<?>> result =
boxClient.call("check_parameter_with_error", stringParam).join();
final List<UUID> param = Collections.singletonList(UUID.randomUUID());
// Stacktrace:
// java.util.concurrent.CompletionException: io.tarantool.core.exceptions.BoxError:
// BoxError{code=32, message='Parameter must be a string',
// stack=[BoxErrorStackItem{type='ClientError', line=3,
// file='[string "function check_parameter_with_box_error(param..."]',
// message='Parameter must be a string', errno=0, code=32, details=null}]}
final TarantoolResponse<List<?>> exceptionallyResult =
boxClient.call("check_parameter_with_error", param).join();
import java.util.Collections;
import java.util.List;
import java.util.UUID;
final List<String> stringParam = Collections.singletonList("some_value");
// OK
// TarantoolResponse(data = [Parameter is valid], formats = {})
final TarantoolResponse<List<?>> result =
boxClient.call("check_parameter_with_box_error", stringParam).join();
final List<UUID> param = Collections.singletonList(UUID.randomUUID());
// Stacktrace:
// io.tarantool.core.exceptions.BoxError: BoxError{code=32, message='Parameter must be a
// string', stack=[BoxErrorStackItem{type='ClientError', line=3,
// file='[string "function check_parameter_with_box_error(param..."]',
// message='Parameter must be a string', errno=0, code=32, details=null}]}
final TarantoolResponse<List<?>> exceptionallyResult =
boxClient.call("check_parameter_with_box_error", param).join();
Exceptions on Tarantool Side During Call to TarantoolClient#eval(...)
Execute the following Lua code through the Java client:
if not box.space[space_name] then
box.error(box.error.NO_SUCH_SPACE, string.format("Space does not exist: %s", space_name))
end
return string.format("Space '%s' exists", space_name)
final String luaCode = "if not box.space[space_name] then "
+ "box.error(box.error.NO_SUCH_SPACE, string.format(\"Space does not exist: %s\", space_name)) "
+ "end return string.format(\"Space '%s' exists\", space_name)";
// OK
// TarantoolResponse(data = [Space 'person' exists], formats = {})
final TarantoolResponse<List<?>> result = client.eval(luaCode, "person").join();
// Stacktrace:
// java.util.concurrent.CompletionException: io.tarantool.core.exceptions.BoxError:
// BoxError{code=36, message='Space 'Space does not exist: unknown_space' does not exist',
// stack=[BoxErrorStackItem{type='ClientError', line=1, file='eval', message='Space 'Space does
// not exist: unknown_space' does not exist', errno=0, code=36, details=null}]}
final TarantoolResponse<List<?>> exceptionallyResult = client.eval(luaCode, "person").join();
Go-like Errors
TarantoolCrudClient
TarantoolCrudClient is a wrapper around the crud module API.
Most API methods return a result as a tuple (res, err), where err is an
error object. In the Java client
the result of a crud API call is wrapped in an object of type CrudResponse. From the perspective
of the IProto protocol, returning a tuple with an error object is not an exceptional situation,
but in case of an error (err != nil), TarantoolCrudClient itself throws an exception of type
CrudError (similar to TarantoolBoxClient, which throws BoxError) to signal that an error object was returned.
As an example, create a sharded space person on a vshard cluster with the following format:
format = {
{ 'id', type = 'uuid' },
{ 'value', type = 'string', is_nullable = true },
{ 'bucket_id', 'unsigned' },
}
Add records to the cluster:
import java.util.Arrays;
import java.util.UUID;
final TarantoolCrudClient crudClient = TarantoolFactory.crud()
.host("router-hostname")
.port(3301)
.user("username")
.password("username-password")
.build();
final TarantoolCrudSpace space = crudClient.space("person");
final List<?> tuple = Arrays.asList(UUID.randomUUID(), "some_value");
// OK
// Tuple(
// formatId = 55,
// data = [ca95b6c7-134d-476a-8545-1ce038cb3b18, some_value, 1208],
// format = [
// Field{
// name='id',
// type='uuid',
// isNullable=null,
// collation='null',
// constraint=null,
// foreignKey=null
// },
// Field{
// name='value',
// type='string',
// isNullable=null,
// collation='null',
// constraint=null,
// foreignKey=null
// },
// Field{
// name='bucket_id',
// type='unsigned',
// isNullable=null,
// collation='null',
// constraint=null,
// foreignKey=null
// }
// ]
// )
final Tuple<List<?>> insertedTuple = space.insert(tuple).join();
final List<?> tupleWithSameId = Arrays.asList(tuple.get(0), "second_value");
// Stacktrace:
// java.util.concurrent.CompletionException: io.tarantool.mapping.crud.CrudException:
// InsertError: Failed to insert: Duplicate key exists in unique index "pk" in space "person"
// with old tuple - [ca95b6c7-134d-476a-8545-1ce038cb3b18, "some_value", 1208] and
// new tuple - [ca95b6c7-134d-476a-8545-1ce038cb3b18, "second_value", 1208]
final Tuple<List<?>> exceptionallyTuple = space.insert(tupleWithSameId).join();
Returning Go-like Errors Through Stored Procedures
Tarantool allows returning Go-like tuples in the return value of stored procedures. In this case, the user must take care of proper conversion of lua and Java types, based on the rules of conversion.
Consider the following procedure:
function multiply_by_100(input)
if type(input) ~= "number" then
return nil, "Input is not a number"
end
local result = input * 100
return result, nil
end
box.schema.func.create('multiply_by_100')
final TarantoolClient client = TarantoolFactory.box()
.host("hostname")
.port(3301)
.user("username")
.password("username-password")
.build();
// OK
// TarantoolResponse(data = [10000, null], formats = {})
final TarantoolResponse<List<?>> result = client.call("multiply_by_100", Arrays.asList(100)).join();
// OK
// TarantoolResponse(data = [null, Input is not a number], formats = {})
final TarantoolResponse<List<?>> resultWithError =
client.call("multiply_by_100", Arrays.asList("100")).join();
Various lua types can be returned as values, for example, arrays:
function prime_factors(input)
if type(input) ~= "number" or input < 2 then
return nil, "Input is not a valid number (must be an integer >= 2)"
end
local factors = {}
local divisor = 2
while input >= divisor do
while (input % divisor) == 0 do
table.insert(factors, divisor)
input = input / divisor
end
divisor = divisor + 1
end
return factors, nil
end
box.schema.func.create('prime_factors')
import java.util.Arrays;
// OK
// TarantoolResponse(data = [[2, 2, 5, 5], null], formats = {})
final TarantoolResponse<List<?>> result = client.call("prime_factors", Arrays.asList(100)).join();
// OK
// TarantoolResponse(data = [null, Input is not a valid number (must be an integer >= 2)],
// formats = {})
final TarantoolResponse<List<?>> result =
client.call("prime_factors", Arrays.asList(100)).join();
Exceptions Generated by the Java Client
| Exception Type | Description |
|---|---|
| BadGreetingException | Thrown in case of failed greeting with the Tarantool node |
| BalancerException | Base exception thrown when the balancer is not working properly |
| NoAvailableClientsException | Subclass of BalancerException. Thrown when the balancer cannot find any "alive" connection in the connection pool |
| SchemaFetchingException | Subclass of ClientException. Thrown when an error occurs during loading of space schemas |
| ConnectionException | Base exception for exceptions related to low-level connections (Connection interface) |
| ConnectionClosedException | Subclass of ConnectionException. Thrown when a connection is closed abnormally by the Tarantool node or by the Java client, when the connection to the node was initiated but was interrupted before the actual connection was established |
| CrudException | Base class of exceptions when working with TarantoolCrudSpace API. Thrown when there is an error working with TarantoolCrudSpace API (for example, inserting a record with the same key) |
| ServerException | Base class of exceptions for server errors, such as outdated protocol version |
| JacksonMappingException | Base class of exceptions for serialization and deserialization type errors |
| NoSchemaException | Subclass of ClientException. Thrown when calling
TarantoolBoxClient#space(...) when a space with the passed identifier or name does not exist |
| ShutdownException | Subclass of ClientException. Class of exceptions for clients, such as graceful shutdown errors |
| PoolException | Base class of exceptions occurring during connection pool operation |
| PoolClosedException | Subclass of PoolException. Thrown when attempting to execute requests on a previously closed Java client |
| TimeoutException | Thrown when a client action exceeds the specified time (connection, greeting, request timeout) |