This topic shows how to define a strongly typed annotation, apply it to methods, and retrieve annotation values at runtime using the reflection API.

Define a strongly typed annotation class

Define a strongly typed annotation by creating a dedicated .cls file that uses the ANNOTATION statement. An annotation class describes the metadata structure by declaring one or more strongly typed properties.

For example, the following annotation class defines metadata for mapping a class element to a service endpoint:

/* service/ResourceMapping.cls */
ANNOTATION service.ResourceMapping:
  DEFINE PUBLIC PROPERTY type      AS CHARACTER.
  DEFINE PUBLIC PROPERTY operation AS CHARACTER.
  DEFINE PUBLIC PROPERTY URI       AS CHARACTER.
  DEFINE PUBLIC PROPERTY mediaType AS CHARACTER INITIAL "application/json".
END ANNOTATION.

Annotation classes have a constrained structure:

  • They contain properties only.
  • They define no methods, constructors, or events.
  • Property values are read‑only at runtime.
  • Only one annotation definition is allowed per .cls file.

Apply a strongly typed annotation

You can apply strongly typed annotations to methods to describe metadata such as service mappings, configuration, or framework behavior.

Use the [ ] Strongly typed annotation syntax and place it immediately before the declaration you want to annotate. The annotation should appear on its own line and is associated with the next applicable declaration.

The following example applies the ResourceMapping annotation to a method:

USING service.ResourceMapping FROM PROPATH.

CLASS com.sales.Orders:

  [ResourceMapping(
    type      = "REST",
    operation = "create",
    URI       = "/orders")
  ].  
  METHOD PUBLIC VOID GetOrders ():
    /* Method implementation */
  END METHOD.

END CLASS.

When the class is compiled:

  • The compiler validates the annotation usage against the annotation class definition.
  • The annotation metadata is stored in r‑code.
  • Properties that are not explicitly assigned retain their default values, as defined either by an INITIAL value in the annotation class or by the default value of the property’s data type.

Understand annotation placement and ordering rules

Strongly typed annotations are associated with the next applicable declaration in the source file. The compiler determines the association based on placement rules, not indentation or formatting.

Keep the following rules in mind when applying annotations:

  • An annotation should appear on its own line and be terminated with a period. Place the annotation immediately before the declaration it annotates.
  • An annotation applies to the next declaration that the compiler encounters, skipping blank lines, comments, and other annotations.
  • You can apply multiple annotations to the same declaration by placing them on consecutive lines.
  • You cannot apply the same annotation class more than once to a declaration.
  • An annotation applies only to the declaration it precedes. Annotations are not inherited by subclasses or by overridden members.

For example, both annotations in the following code apply to the method:

[ResourceMapping(type = "REST", URI = "/orders") ].
[Exportable].
METHOD PUBLIC VOID GetOrders ():
  /* Method implementation */
END METHOD.

If an annotation is placed in a location where annotations are not supported, the compiler raises an error.

If an annotation is not placed immediately before the intended declaration, it may be associated with a different element than you expect, or result in a compile‑time error. For example, in the following code, the annotation is not applied to the method:

[ResourceMapping(type = "REST", URI = "/orders")].

/* This comment is fine */
DEFINE VARIABLE orderCount AS INTEGER NO-UNDO.

METHOD PUBLIC VOID GetOrders ():
  /* Method implementation */
END METHOD.

In this case, the annotation applies to the DEFINE VARIABLE statement, not to the method. If annotations are not supported on that declaration type, the compiler raises an error.

To avoid this issue, always place the annotation directly before the declaration you want to annotate, with no intervening executable declarations.

Use marker annotations

An annotation class that defines no properties acts as a marker annotation. Marker annotations convey meaning through their presence alone.

For example:

ANNOTATION Exportable:
END ANNOTATION.

Apply a marker annotation without specifying property values:

[Exportable].
CLASS com.sales.Report:
END CLASS.

Parentheses are optional when using marker annotations.

Retrieve a specific annotation at runtime

To access annotations applied to a class or class member, use the GetAnnotation() method to retrieve a specific annotation, the GetAnnotations() method to retrieve all annotations, or the HasAnnotation() method to test for the presence of a marker annotation.

The following example retrieves the ResourceMapping annotation applied to a method:

USING service.ResourceMapping.

VAR CLASS Progress.Lang.Class      orderClass.
VAR CLASS Progress.Reflect.Method  methodInfo.
VAR CLASS Progress.Lang.Class      annType.
VAR CLASS ResourceMapping          mapping.

/* Get the reflected class */
orderClass = GET-CLASS(com.sales.Orders).

/* Get the reflected method */
methodInfo = orderClass:GetMethod("GetOrders").

/* Get the annotation class type */
annType = GET-CLASS(ResourceMapping).

/* Retrieve the annotation instance */
mapping = CAST(methodInfo:GetAnnotation(annType), ResourceMapping).

IF VALID-OBJECT(mapping) THEN
  MESSAGE
    mapping:type
    mapping:operation
    mapping:URI
    mapping:mediaType
  VIEW-AS ALERT-BOX.

The annotation instance returned by GetAnnotation() behaves like a regular object-oriented ABL object, but its properties are read‑only.

Retrieve all annotations or test for marker annotations

Retrieve all annotations applied to an element by calling GetAnnotations():

VAR Progress.Lang.Annotation[] annotations.

annotations = methodInfo:GetAnnotations().

Test for the presence of a marker annotation by using HasAnnotation():

IF methodInfo:HasAnnotation(GET-CLASS(Exportable)) THEN
  MESSAGE "Method is exportable".

These approaches are commonly used by frameworks and infrastructure code that adapts behavior based on annotation metadata.

Access annotation property values safely

When reading annotation properties:

  • If an annotation property is not explicitly assigned in the annotation usage, its value is taken from the INITIAL value specified in the annotation class, if one exists.
  • If no INITIAL value is specified, the property contains the default value for its data type.

Always account for default and unknown values when using annotation metadata in application logic.

Summary

Strongly typed annotations provide a structured way to define, apply, and access metadata in object-oriented ABL applications. By defining annotation classes, applying annotations enclosed in square brackets, and retrieving metadata through reflection, you can build applications and frameworks that are easier to configure, extend, and maintain without embedding metadata directly in executable code.