Exception Handling

MorphX has a built-in exception-handling feature, enabling you to catch errors during program execution and control how the program reacts following an error.

To use exception handling, specify which statements are to be controlled by the use of try or throw statements. The try statement has a catch list; thrown exceptions are caught. You can nest try statements.

If an error is thrown and the first try block does not catch the exception, it is propagated to the next try block, and so on. The outermost level is typically where errors are handled in X++.

If you don't specify your own exception handling, thrown errors are handled by the system exception handler.

NoteNote

At the time of publication, portions of this topic were intentionally left blank.


The try statement can be described as follows:

TryStatement = try statement catchlist

Catchlist = catch ( expression ) statement { catchlist }

A retry statement can be included in a catch statement. The retry statement goes to the top of the try statement and performs it again. This is useful when a precondition for success (for example, release of locks) might be changed in the catch statement (enabling completion of retry). The catchlist can be a compound statement.

NoteNote

Use retry carefully because it can result in an infinite loop. Ensure that you reset variables held in memory.


You might need a general catchlist at the bottom of the other catch lists to catch the remaining exceptions. For example, if an exception was thrown when a file had been opened, you might want to close it, regardless of what caused the exception. Close it by using an empty catch statement. To prevent a compiler warning about an empty compound statement, use the following code.

catch

{

exceptionTextFallThrough();

}

The exceptionTextFallThrough method is on the Global class, which does nothing. Use this method to acknowledge that the text in the exception should fall through and eventually be caught by Infolog.

The throw statement is used to create your own exceptions.

The throw statement takes the form:

throw Exception ;

where Exception is a value in the Exception system enum. However, it is a best practice to use the error method on the Global application class:

throw error( LabelReference , [ HelpURL , Action ]);

where:

  • LabelReference is a label that contains the error message text.

  • HelpURL is a reference to the location of a Help topic in the Application Object Tree (AOT), for example, KernDoc:\\\\Functions\\substr.

  • Action is an activity that fixes the error. An action is created by extending the SysInfoAction class.

For more information, see Infolog best practices.

You can use the throw statement everywhere in the program. If an exception is thrown, it will be found by the innermost try block. When no try statements are in effect, the exceptions are caught by the standard exception handling in X++.

When an exception is thrown inside a transaction, the transaction is automatically aborted (a ttsabort operation occurs). This applies both for exceptions thrown manually and for exceptions thrown by the system.

If an exception is thrown inside a ttsBegin/ttsCommit block, it will be caught by the first matching catchlist that is outside the transaction block. If there is a catch block within the ttsBegin/ttsCommit, it will be ignored (see Example 3).

Example 1 shows a simple exception handling mechanism where the throw in the try statement throws an error that is caught by the catch list.

try

{

// Statements.

throw error("@SYS53065");

// Statements.

}

catch (exception::Error)

{

// React on the error exception.

}

Example 2 shows that the try statement throws two exceptions. Only one of them is caught by this catch list. The exception::Error error is thrown to the next level.

try

{

// Statements.

if (/*Condition*/)

{

throw deadlock("DeadlockErrorText"); //Text should be a label.

}

else

{

throw error("ErrorText "); // Text should be a label.

}

// Statements.

}

catch (exception::Deadlock)

{

// Statements.

retry;

}

Example 3 shows how an exception in a ttsBegin/ttsCommit block is caught by the first catchlist outside the block rather than by the catch within the ttsBegin/ttsCommit block. If you run this code, "outside tts" will be printed before an Infolog displays "Message".

try
{
    ttsbegin;
    try
    {
        throw error("Message"); 
    }
    catch
    {
        print "not here";
        pause;
    }
    ttscommit;
}
catch
{
    print "outside tts";
    pause;
}

The exception literals shown in the following table are built into the development system (the Exception system enum).

Exception literal

Description

info

Holds a message for the user.

Do not throw an info exception.

warning

Indicates that something exceptional has happened. The user might have to take action, but the event is not fatal.

Do not throw a warning exception.

deadlock

Indicates that there is a database deadlock because several transactions are waiting for each other.

error

Indicates that a fatal error has occurred. The transaction has been stopped.

internal

Indicates an internal error in the development system.

break

Indicates that the user has pressed BREAK or CTRL+C.

ddeerror

Indicates that an error occurred in the use of the DDE system class.

sequence

TBD

numeric

Indicates that an error has occurred during the use of the str2int, str2int64, or str2num functions.

CLRError

Indicates that an error has occurred during the use of the common language runtime (CLR) functionality.

CodeAccessSecurity

Indicates that an error has occurred during the use of the CodeAccessPermission.demand method. For more information, see Code Access Security.

UpdateConflict

Indicates that an error has occurred in a transaction that is using Optimistic Concurrency Control. The transaction will be retried (use a retry statement in the catch block).

UpdateConflictNotRecovered

Indicates that an error has occurred in a transaction that is using Optimistic Concurrency Control. The code will not be retried.

NoteNote
This exception cannot be caught within a transaction.

Check for error conditions at the beginning of a method by using an if statement followed by a throw statement. This avoids the use of a redundant else statement as shown in the following examples.

Correct

int getValue(int _valueIndex)
{
    if (_valueIndex < 1 || _valueIndex > totalValues)
    {    
        throw (strfmt("@SYS28970"));
    }
    return valueList[_valueIndex];
}

Incorrect

int getValue(int _valueIndex)
{
    if (valueIndex>0 && valueIndex <= totalValues)
    {
        return valueList[_valueIndex];
    }
    else
    {   
        throw (strfmt("@SYS28970"));
    }
}

Community Additions

ADD
Show: