Exception Handling
Since v6.2.0
Automatic exception catching and handling for service methods.
UltiTools provides declarative exception handling through the @ExceptionCatch annotation. Instead of wrapping service method calls in try-catch blocks, you simply annotate a method and the framework handles exceptions automatically based on your configuration.
@ExceptionCatch has no reader in v6.2.5
The aop package is connected to the rest of the framework only by two javadoc references in v6.2.5: no proxy is created, no advisor is registered and ExceptionInterceptor is never instantiated, so an annotated method throws exactly as it would without the annotation and silent, value, defaultValue and handler all stay inert. Wrap the call in an ordinary try-catch until the wiring ships: everything described on this page, including the handler lookup by name further down, depends on that one missing connection. The wiring is merged into the development branch but is not part of v6.2.5; it is tracked in issue #190.
Basic Usage
Add @ExceptionCatch to any method inside a managed bean (such as a @Service):
package com.ultikits.docs.exception;
import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class FileService {
@ExceptionCatch
// @ExceptionCatch is runtime AOP. It catches the exception when the method
// is invoked through the proxy, but javac still requires a checked exception
// to be declared, so `throws IOException` is not optional here.
public String readFile(String path) throws IOException {
// If any exception occurs, it will be caught and logged
// The method returns null
return new String(Files.readAllBytes(Paths.get(path)));
}
}By default:
- All
Exceptiontypes are caught - Exceptions are logged as warnings (unless
silent = true) - A default value (null for objects, 0 for primitives) is returned
Annotation Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
value | Class<? extends Throwable>[] | {Exception.class} | Exception types to catch. Subclasses are automatically included. |
silent | boolean | false | If true, exceptions are caught without logging. If false, caught exceptions are logged as warnings. Either way, the exception is also reported to the framework's ErrorReportCollector. |
handler | String | "" | Name of a custom exception handler bean. The bean must implement ExceptionHandler. |
defaultValue | String | "" | Expression specifying the return value when an exception is caught. |
Catching Specific Exceptions
Specify which exception types should be caught:
package com.ultikits.docs.exception;
import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class DataService {
@ExceptionCatch(IOException.class)
public String loadData() {
// Only IOException will be caught
// Other exceptions will propagate up
return readFromFile();
}
@ExceptionCatch({IOException.class, SQLException.class})
public List<User> fetchUsers() {
// Both IOException and SQLException will be caught
// Subclasses are also caught
return queryDatabase();
}
private String readFromFile() { return ""; }
private List<User> queryDatabase() { return new ArrayList<>(); }
}Exception Hierarchy
When you specify an exception type, the framework also catches its subclasses. For example, @ExceptionCatch(IOException.class) will catch FileNotFoundException, EOFException, and other subclasses of IOException.
Silent Mode
Suppress logging for expected or non-critical exceptions:
package com.ultikits.docs.exception;
import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class ConfigService {
@ExceptionCatch(silent = true)
public void saveOptionalConfig() {
// Any exception is caught and NOT logged
// Useful for non-critical background operations
writeConfigBackup();
}
@ExceptionCatch(value = FileNotFoundException.class, silent = true)
public boolean fileExists(String path) {
// FileNotFoundException is silently caught
// Other exceptions propagate up uncaught (not caught, not logged)
return checkFile(path);
}
private void writeConfigBackup() { }
private boolean checkFile(String path) { return true; }
}Use silent = true for:
- Non-critical operations (e.g., optional backups)
- Fallback logic (e.g., use default if file not found)
- Operations where exceptions are expected
Default Return Values
Control what value is returned when an exception is caught:
package com.ultikits.docs.exception;
import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class MoneyService {
@ExceptionCatch(defaultValue = "0")
public int getBalance(String accountId) {
// If exception occurs, returns 0 instead of null
return queryBalance(accountId);
}
@ExceptionCatch(defaultValue = "false")
public boolean isPlayerOnline(String playerName) {
// Returns false instead of null
return checkDatabase(playerName);
}
@ExceptionCatch(defaultValue = "empty")
public List<User> getAllUsers() {
// Returns empty list instead of null
return queryAllUsers();
}
private int queryBalance(String accountId) { return 0; }
private boolean checkDatabase(String playerName) { return false; }
private List<User> queryAllUsers() { return new ArrayList<>(); }
}Supported default value expressions:
"null"— returns null (default for objects)"true"/"false"— returns boolean- Numeric literals —
"0","100","-5","3.14"— returns the number "empty"— returns empty collection/array/string based on return type
If defaultValue is not specified, a type-appropriate default is used:
- Objects:
null - boolean:
false - int, long, etc.:
0 - String:
null - Collections:
null
defaultValue Type Matching
The defaultValue expression is parsed according to the method's return type. If you specify defaultValue = "0" on a String-returning method, it returns the string "0", not the number zero.
Custom Exception Handlers
Implement custom logic for exception handling by creating an ExceptionHandler bean:
package com.ultikits.docs.exception;
import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class LoggingExceptionHandler implements ExceptionHandler {
@Override
public Object handleException(Throwable exception, Object target, Method method, Object[] args) {
// Log detailed exception information
System.out.println("Exception in: " + method.getDeclaringClass().getSimpleName() + "." + method.getName());
System.out.println("Message: " + exception.getMessage());
exception.printStackTrace();
return null;
}
@Override
public boolean supports(Class<? extends Throwable> exceptionType) {
// This handler supports any exception
return true;
}
}Register the handler and reference it by name:
package com.ultikits.docs.exception;
import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class MyService {
@ExceptionCatch(handler = "loggingExceptionHandler")
public String processData() {
// If an exception occurs, LoggingExceptionHandler.handleException() is called
return getData();
}
private String getData() { return ""; }
}Handler Interface
Custom handlers implement the ExceptionHandler interface, whose handleException(Throwable, Object, Method, Object[]) method holds the main logic and can return a replacement value or re-throw. supports(Class) is optional and reports whether the handler covers a given exception type, defaulting to true for all types. getOrder() is optional too and sets priority, where lower values run first and the default is 0.
Method Requirements
@ExceptionCatch works only on methods in beans managed by the IoC container:
@Service
public class MyService {
@ExceptionCatch // CORRECT - method in a managed @Service bean
public void safeOperation() {
// ...
}
}
public class NonManagedClass {
@ExceptionCatch // WRONG - this class is not a bean
public void unsafeOperation() {
// The annotation has no effect
}
}Supported bean types:
@Service— services@Component— general-purpose beans- Any class registered manually in the IoC container
Complete Example
package com.ultikits.docs.exception;
import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.aop.ExceptionHandler;
import com.ultikits.ultitools.interfaces.DataOperator;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class UserDatabaseService {
@Autowired
private UltiToolsPlugin plugin;
// Safe read: returns null on any exception, with logging
@ExceptionCatch
public User findById(String userId) {
DataOperator<User> op = plugin.getDataOperator(User.class);
return op.query().where("id").eq(userId).first();
}
// Safe read with default: returns empty list if query fails
@ExceptionCatch(defaultValue = "empty")
public List<User> findByRole(String role) {
DataOperator<User> op = plugin.getDataOperator(User.class);
return op.query().where("role").eq(role).list();
}
// Safe with silent mode: no logging for file-not-found
@ExceptionCatch(value = FileNotFoundException.class, silent = true)
public String loadUserData(String filename) {
return readFile(filename);
}
// Safe with custom handler: detailed error reporting
@ExceptionCatch(
value = {SQLException.class, IOException.class},
handler = "detailedErrorHandler",
defaultValue = "null"
)
public String exportUsers() {
// If SQLException or IOException occurs, detailedErrorHandler is invoked
return performExport();
}
// Critical operation: no exception catching, propagates up
public void deleteUser(String userId) {
// No @ExceptionCatch - exceptions must be handled by caller
DataOperator<User> op = plugin.getDataOperator(User.class);
op.query().where("id").eq(userId).delete();
}
private String readFile(String filename) { return ""; }
private String performExport() { return ""; }
}Best Practices
Custom handlers work best for fault tolerance, catching exceptions in methods where failures are expected or non-critical. Specify exception types such as @ExceptionCatch(IOException.class) instead of catching everything, and keep silent = false unless you have a specific reason to suppress the log. Provide meaningful defaults, for example defaultValue = "empty" for collections and "0" for counts, and combine @ExceptionCatch with @Service beans designed for fault tolerance.
See Also
- IoC Container — How beans are managed and proxied
- Transactions — Declarative transaction management with
@Transactional - Scheduled Tasks — Automatic task scheduling with lifecycle management