Exceptions

The core API uses the class DominoException as its main lower-level-API error representation, though it’s rare that you will instantiate this class directly. It is a subclass of RuntimeException and so should not be declared in the throws portion of a method signature - this allows JNX code to work more smoothly in modern Java conventions like streams.

JDK-Friendly Exceptions

When an exception case is something that it handled purely on the Java side and can be represented well as a “stock” exception, a normal java.* should be preferred.

For example, if a method has a parameter that must not be null, the best way to handle it is:

public void someMethod(Object foo) {
	Objects.requireNonNull(foo, "foo cannot be null");
	// ...
}

Objects.requireNonNull will throw a NullPointerException in this case.

Similar should be done for other “normal” cases, like a number value that must be non-negative:

public void someMethod(int bar) {
	if(bar < 0) {
		throw new IllegalArgumentException("bar must be non-negative");
	}
}

DominoExceptions and STATUS values

The most common error condition we will run into is having a non-zero STATUS value returned by the Notes C API. For this case, the com.hcl.domino.commons.util.NotesErrorUtils class contains a checkResult method. This can be used directly on the return value of any STATUS-returning method. For example:

NotesErrorUtils.checkResult(
	NotesCAPI.get().NSFFormulaDecompile(valueDataPtr, isSelectionFormula,
		rethFormulaText, retFormulaTextLength)
);

If the STATUS value (with ERR_MASK applied) resolves to 0, then this method does nothing. If it is non-zero, then it calls OSLoadString to find the corresponding error message and throws a new DominoException with that message and a stack trace pointing to the calling code.

Since that method throws the exception itself, it is important to handle any local native resources in a try/finally block. For example, if a call happened to take an ID table as a parameter that was created just for it, it should be destroyed in a finally block:

DHANDLE.ByValue hTable = somethingThatCallsIDCreateTable();
try {
	NotesErrorUtils.checkResult(somethingThatUsesHTable());
} finally {
	NotesCAPI.get().IDDestroyTable(hTable);
}

Subclasses and Special Handling

This method additionally has knowledge of several specialized DominoException subclasses, such as ItemNotFoundException. These exceptions can be caught the same way, but are useful for signaling to the programmer for specific situations without having them check the getId() value of the DominoException. New exception classes can be added to the toNotesError(short result, String message) method in NotesErrorUtils and the code calling checkResult does not need to know about them.

There are a handful of situations where it is useful to retrieve the STATUS value first before passing it to checkResult. For example, JNADatabase#getDocumentById returns an empty Optional when the result is ERR_NOT_FOUND and then otherwise passes the value along to checkResult:

short result = LockUtil.lockHandle(allocations.getDBHandle(), (dbHandleByVal) ->
	NotesCAPI.get().NSFNoteOpenExt(dbHandleByVal, noteId, openOptions, rethNote)
);

if ((result & NotesConstants.ERR_MASK)==INotesErrorConstants.ERR_NOT_FOUND) {
	return Optional.empty();
}
NotesErrorUtils.checkResult(result);

Non-STATUS-based and Other DominoExceptions

There are a few cases where exceptions are handled differently from above.

Two such cases are FormulaCompilationException and LotusScriptCompilationException, which are DominoException subclasses but carry extra information related to the specific cause of the failure. These exceptions are created manually based on code that checks the initial STATUS return value and then reads the detail “out” parameters to construct the exception.

In addition, there may be situations where a problem is reasonably represented in the DominoException category but isn’t caused by a Notes API STATUS case. This is the situation for ObjectDisposedException, which arises when a JNX object has had its backend representation closed (e.g. via dispose() calling IDDestroyTable) but then the user tries to access it again. Guard code should check for this and throw a new ObjectDisposedException, though there is no STATUS code to go along with it.

Documenting Exceptions

As mentioned above, these exceptions should not be declared in method signatures, since they are RuntimeException subclasses. However, it is good form to document the potential for known common exception cases in Javadoc for the method. For example, from DominoClient:

/**
 * Creates a new database on the target server with a given file path.
 * 
 * (snip)
 * @throws DominoException if the database already exists and {@code forceCreation} is {@code false}
 */
Database createDatabase(String serverName, String filePath, boolean forceCreation, boolean initDesign,
		Encryption encryption, DatabaseClass dbClass);

It is also good to document cases where a known subclass may be thrown, to signal to a downstream programmer that they might catch those more explicitly. From Database:

/**
 * Opens a document in the database
 * 
 * (snip)
 * @throws DocumentDeletedException if the document has been deleted
 */
Optional<Document> getDocumentById(int noteId);

It’s not important to cover every potential non-zero STATUS case, or even mention that such a thing might occur, since it’s such a pervasive possibility. For example, SERVER_NOT_RESPONDING may arise in basically any remote-server case, but it’s not important to document that in Document#get.