Skip to content

MOVE_CAST_ERROR

A CAST operator failed because the object is not an instance of the target class.

The CAST operator tried to narrow an object reference to a more specific class, but the actual object at runtime is not an instance of that class (or any of its subclasses).

Symptom

ST22 shows:

Runtime Errors         MOVE_CAST_ERROR
Exception              CX_SY_MOVE_CAST_ERROR
Short text             Move with type conversion error (class cast).

The dump line is the CAST expression.

Cause

CAST is a downcast — it moves from a superclass reference to a subclass reference. If the actual runtime object is not of the target type, it dumps.

Common triggers:

  • Forgetting to check the actual runtime type before casting.
  • A factory method returning different subclass instances depending on configuration.
  • Reusing a reference variable that holds different object types in different code paths.

Reproduce

REPORT z_move_cast_error.

START-OF-SELECTION.

  " CX_SY_ZERODIVIDE is a subclass of CX_SY_ARITHMETIC, not of CX_SY_MOVE_CAST_ERROR.
  " Holding it as the base class (CX_ROOT) and casting to the wrong subclass dumps.
  DATA lo_base TYPE REF TO cx_root.
  lo_base = NEW cx_sy_zerodivide( ).

  " Cast to a completely unrelated exception class → MOVE_CAST_ERROR
  DATA(lo_wrong) = CAST cx_sy_move_cast_error( lo_base ).   " <-- MOVE_CAST_ERROR dump

Fix

All examples use the same setup as the reproduce example — paste directly into SE38.

REPORT z_move_cast_fix1.

START-OF-SELECTION.

  DATA lo_base TYPE REF TO cx_root.
  lo_base = NEW cx_sy_zerodivide( ).

  " Check the actual runtime type before narrowing — no dump risk
  IF lo_base IS INSTANCE OF cx_sy_move_cast_error.
    DATA(lo_cast) = CAST cx_sy_move_cast_error( lo_base ).
    WRITE: / 'Cast OK:', lo_cast->get_text( ).
  ELSE.
    DATA(lo_type) = cl_abap_typedescr=>describe_by_object_ref( lo_base ).
    WRITE: / 'Not the expected type. Actual:', lo_type->get_relative_name( ).
  ENDIF.
REPORT z_move_cast_fix2.

START-OF-SELECTION.

  DATA lo_base TYPE REF TO cx_root.
  lo_base = NEW cx_sy_zerodivide( ).

  TRY.
      DATA(lo_cast) = CAST cx_sy_move_cast_error( lo_base ).
      WRITE: / 'Cast succeeded:', lo_cast->get_text( ).
    CATCH cx_sy_move_cast_error INTO DATA(lx).
      WRITE: / 'Cast failed —', lx->get_text( ).
      WRITE: / 'Source type: ', lx->source_typename.
      WRITE: / 'Target type: ', lx->target_typename.
  ENDTRY.
REPORT z_move_cast_fix3.

START-OF-SELECTION.

  DATA lo_base TYPE REF TO cx_root.
  lo_base = NEW cx_sy_zerodivide( ).

  DATA(lo_descr) = cl_abap_typedescr=>describe_by_object_ref( lo_base ).
  WRITE: / 'Actual runtime type:', lo_descr->get_relative_name( ).

  " Only cast if RTTI confirms the type
  IF lo_descr->get_relative_name( ) = 'CX_SY_MOVE_CAST_ERROR'.
    DATA(lo_cast) = CAST cx_sy_move_cast_error( lo_base ).
    WRITE: / lo_cast->get_text( ).
  ELSE.
    WRITE: / 'Skipping cast — type mismatch confirmed by RTTI'.
  ENDIF.

IS INSTANCE OF is the cleanest guard

IS INSTANCE OF returns true for the exact class AND all its subclasses, matching the same semantics as CAST. It is faster than RTTI and reads clearly.

Prevention

  • Never use CAST without an IS INSTANCE OF guard unless you are 100% certain of the runtime type (e.g. you just created the object yourself with NEW).
  • When working with polymorphic factory results, always check the type before narrowing.

See also

Comments