Error Handling Recommended Practices

The following guidelines are for the purposes of writing better code, better supportability, and better security.

General Guidelines for Exception Handling

  • Generally, it is acceptable to catch specific checked exceptions and exceptions originating from core code and ignoring them when required.
  • When calling core methods, never catch any Exception, RuntimeException, or Throwable. The exceptions could come directly from core or from the persistence layer.

For example, users can also think in terms of system errors vs. user errors. A 'ValidationError' is a user error and can therefore safely be ignored. Tablespace, Diskspace, OptimisticLockException, etc. are HIGHLY critical and should never be ignored

Note: For reference, 'specific' means to not ever catch generic exceptions like '(Exception e)' without re-throwing. Also, 'checked' means to not ever catch any RuntimeException or subclass thereof and ignore it. In the error-handling context, 'ignoring' means to not re-throw the exception, while sometimes, 'when required' means letting the exception to run is the recommended practice.

The above guidelines are not completely all encompassing; there might be specific core exception catches for advanced users.

Rationale for Ignoring Core Exceptions

The core code has a lot of functionality that will run in various orders. The operations may require some condition to be fulfilled, which would throw a RuntimeException signaling this error. Reasons for these operation failures are concurrent modifications or tablespace problems in the dataspace among other issues.

The RuntimeException Error will be caught by the Manager where applicable, or it will crash the system. Continuing with a caught exception can cause strange errors regarding the broken database constraints, data inconsistencies that are not found until later, and errors while processing data, such as exceptions in the persistence layer when manipulating objects that are left in an invalid state.

Continuing with Batch Operations with Failures

There are scenarios where the batch operation must complete, regardless of a failure. In some instances, this path is possible. With a ValidationException, the user may catch the checked exception, resolve the problem, and then continue with the operation without issue. However, should a RuntimeException trigger, then the user needs to stop the operation to resolve this issue or risk system problems.

Specifically, users may catch specifically checked exceptions that the core method can throw, and ignore them if the business case requires, or address the issues as needed.

Important: Exception, RuntimeException, Throwable, and any other 'catch-all' type of exceptions cannot be caught without rethrowing the exception since the exception may be important or require the system to crash to save data.

.printStackTrace() Function

It is advised to not use the .printStackTrace function. Often, an implementation of this function will look like:

try {
    someMethod();
catch(Exception e) {
    e.printStackTrace();
}

The issues with implementation are that it does not help supportability since the information does not add anything to the log, and it creates a security risk by exposing internal information about how STEP is built to a client.

For users desiring something like this, the following example is more encompassing.

try {
    someMethod();
} catch(RuntimeException re) {
    //Log information that cannot already be found in the exception. Like e.g., parameters given to the method that failed.
    throw e;
} catch(AKnownException e) {
    logger.log(LogLevel.SEVERE, "Some Message", e);
    // When you know the error there might be a way to recover or inform the user that the action could only be partially fulfilled. If so, that probably preferable. Ow
    throw new whatEverException(e);
} catch (exception e) {
    logger.log(LogLevel.SEVERE, "An unexpected error occurred", e);
    //Depending on the situation, a custom exception class could be better.
    throw new RuntimeException(e);
}

This example improves supportability since errors are logged with the log() functions. However, security is not improved since STEP package names, classes, etc. would be made visible to potential attackers, but since the logs are in the workbench, users already have access to this sensitive information.

Note: To ensure that the output in the log is useful, always use the pattern:

logger.log(LogLevel.SOMETHING, "Some error message", e);

Web UI Considerations

For most user cases, the Web UI will feature non-secure user and customer-facing activity. In order to properly ensure security, exceptions in the Web UI need to be translated to PortalExpecptions for client-side handling.

Since error handling will be limited to administrator users and these users will not be performing real data handling that can throw exceptions, except for dates and number formatting, client-side error-logging is not needed in the Web UI. Since null pointer exceptions on data structures mean that the data is not valid, a check is sufficient.

The example below will avoid all of the addressed pitfalls in this section.

try {
    someMethod();
} catch(Exception e) {
    //Either pop up a message telling the user that something went wrong (without a stacktrace!), 
    //or somehow return an error that will be handled further up the callchain that displays the error to the user
    //Make sure you do not leak any stacktraces to the user! Be as informative as possible.
    popupError("There was an error running someMethod. Some Data [product id] could not be handled");
}

Note: These examples are simplified and make it impossible to display a meaningful error in the Web UI case. Adapt the examples as needed and desired.

Non-Exception-Based Error Handling Approach

Recommended practices for Web UI logging is to not use exceptions at all. When preparing the code for unexpected errors, it is advised to handle any exceptions that can be handled and capture any others with a 'An Unexpected Error Occurred' catch-all.

Any RuntimeException errors thrown on the Web UI loading will kill the Web UI, thus never showing the Web UI to the user. Any asynchronous calls should use standard error- or success-handling tools to display informative messages to the user that puts the stack trace in the log. These messages may be localizable.

GlobalDispatcherFactory.getInstance().dispatch(new CategorySearchCommand(productHierarchyRootID, productObjectTypeID), new AsyncCallback<LovOptionSearchReturnValue>() {
     @Override
     public void onFailure(Throwable caught) {
     // Better practice - use the standard tools
         alertPort.showAlert(PortalAlert.createWarning("Headline!","An unexpected Error occurred"));
     // If there is no other impact then ignore the failure ow. handle it!
         OnFailure.onFailure(caught);    
    }
    @Override    
    public void onSuccess(LovOptionSearchReturnValue result) {
         if (result != null){
                // hasError is a Boolean on the customized TO - Not a standard feature!
        if(result.hasErrors()}
            alertPort.showAlert(PortalAlert.createWarning("Headline!","An unexpected Error occurred"));
        }
                else {
                   showData(result, id);    
        }
});

The same kind of exception handling can be used when loading the Web UI. To achieve this, create a label or alert that informs the user some issue has occurred. The parameters for these messages should tell the user why there was an issue, and what can be done.