ABAP Cleaner: Automatic Code Cleanup

Category
DevOps
Published
Author
Johannes

ABAP Cleaner is an open-source tool that automatically cleans up, formats, and modernizes ABAP code. It applies over 60 cleanup rules and helps enforce consistent code standards across teams.

What is ABAP Cleaner?

ABAP Cleaner analyzes ABAP code and automatically transforms it according to configurable rules. Unlike the Pretty Printer, ABAP Cleaner goes far beyond pure formatting and also modernizes language constructs.

AspectPretty PrinterABAP Cleaner
FocusIndentation, caseComplete code modernization
Rules~5 basic rules60+ configurable rules
Language constructsNo changesModernization (e.g., NEW instead of CREATE OBJECT)
Empty linesSimple rulesIntelligent grouping
CommentsNo changesAlignment and formatting
Performance-Removal of unnecessary statements
ProfilesFixedIndividually configurable

Benefits

  • Consistency: Uniform code style across the entire team
  • Productivity: Automatic cleanup saves manual work
  • Modernization: Old syntax is automatically updated
  • Readability: Better structure and formatting
  • Code Review: Fewer discussions about style questions
  • Onboarding: New team members write compliant code immediately

Installation in ADT

Prerequisites

  • Eclipse with ADT (ABAP Development Tools)
  • Eclipse version 2022-03 or newer
  • Java 11 or newer

Installation via Eclipse Marketplace

  1. Open Eclipse and navigate to Help → Eclipse Marketplace
  2. Search for “ABAP Cleaner”
  3. Click Install and follow the instructions
  4. Restart Eclipse

Manual Installation via Update Site

If the Marketplace is not available:

Help → Install New Software...
Update Site URL:
https://sap.github.io/abap-cleaner/updatesite
Available Features:
☑ ABAP Cleaner
→ Next → Accept License → Finish → Restart Eclipse

Verify Installation

After restart, ABAP Cleaner should be available:

┌──────────────────────────────────────────────────────────────┐
│ ADT Toolbar │
├──────────────────────────────────────────────────────────────┤
│ │
│ [📁] [💾] [↩️] [↪️] ... [🧹 ABAP Cleaner] │
│ │
│ Or: Source → Clean Up With Interactive ABAP Cleaner... │
│ Shortcut: Ctrl+4 (configurable) │
│ │
└──────────────────────────────────────────────────────────────┘

Key Cleanup Rules

ABAP Cleaner groups its rules into categories. Here are the most important ones:

1. Empty Lines - Optimize Whitespace

Intelligent grouping of code blocks:

Before:

METHOD process_order.
DATA lv_status TYPE string.
DATA lv_amount TYPE p DECIMALS 2.
SELECT SINGLE status
FROM zorders
INTO lv_status
WHERE order_id = iv_order_id.
IF lv_status = 'OPEN'.
lv_amount = calculate_total( ).
update_order( lv_amount ).
ENDIF.
ENDMETHOD.

After:

METHOD process_order.
DATA lv_status TYPE string.
DATA lv_amount TYPE p DECIMALS 2.
SELECT SINGLE status
FROM zorders
INTO lv_status
WHERE order_id = iv_order_id.
IF lv_status = 'OPEN'.
lv_amount = calculate_total( ).
update_order( lv_amount ).
ENDIF.
ENDMETHOD.

2. Declarations - Modernize Declarations

Before:

DATA: lv_count TYPE i,
lt_items TYPE TABLE OF zitem,
ls_item TYPE zitem,
lo_processor TYPE REF TO zcl_processor.
CREATE OBJECT lo_processor.
CLEAR lt_items.

After:

DATA lv_count TYPE i.
DATA lt_items TYPE TABLE OF zitem.
DATA ls_item TYPE zitem.
DATA lo_processor TYPE REF TO zcl_processor.
lo_processor = NEW #( ).
CLEAR lt_items.

3. Syntax - Use Modern ABAP Syntax

Before:

CALL METHOD lo_service->get_data
EXPORTING
iv_id = lv_id
RECEIVING
rt_data = lt_data.
IF lt_data IS INITIAL.
" ...
ENDIF.
LOOP AT lt_data INTO ls_data.
MOVE-CORRESPONDING ls_data TO ls_result.
APPEND ls_result TO lt_result.
ENDLOOP.

After:

lt_data = lo_service->get_data( iv_id = lv_id ).
IF lt_data IS INITIAL.
" ...
ENDIF.
LOOP AT lt_data INTO DATA(ls_data).
lt_result = VALUE #( BASE lt_result ( CORRESPONDING #( ls_data ) ) ).
ENDLOOP.

4. Commands - Remove Unnecessary Statements

Before:

CLEAR lv_result.
lv_result = calculate_value( ).
IF sy-subrc = 0.
" Success
ENDIF.
MOVE lv_source TO lv_target.

After:

lv_result = calculate_value( ).
IF sy-subrc = 0.
" Success
ENDIF.
lv_target = lv_source.

5. Alignment - Improve Alignment

Before:

ls_order-order_id = lv_id.
ls_order-customer = lv_customer.
ls_order-status = 'NEW'.
ls_order-created_at = sy-datum.
ls_order-created_by = sy-uname.
ls_order-amount = lv_amount.

After:

ls_order-order_id = lv_id.
ls_order-customer = lv_customer.
ls_order-status = 'NEW'.
ls_order-created_at = sy-datum.
ls_order-created_by = sy-uname.
ls_order-amount = lv_amount.

6. Pretty Print - Formatting

Before:

IF LV_STATUS='OPEN'AND LV_AMOUNT>100.
CALL METHOD LO_SERVICE->PROCESS(EXPORTING IV_ID=LV_ID CHANGING CT_DATA=LT_DATA).
ENDIF.

After:

IF lv_status = 'OPEN' AND lv_amount > 100.
lo_service->process( EXPORTING iv_id = lv_id
CHANGING ct_data = lt_data ).
ENDIF.

7. Inline Declarations - Use VALUE and NEW

Before:

DATA lt_orders TYPE TABLE OF zorder.
DATA ls_order TYPE zorder.
DATA lo_service TYPE REF TO zcl_order_service.
CREATE OBJECT lo_service
EXPORTING
iv_client = sy-mandt.
ls_order-order_id = '12345'.
ls_order-status = 'NEW'.
APPEND ls_order TO lt_orders.

After:

DATA(lo_service) = NEW zcl_order_service( iv_client = sy-mandt ).
DATA(lt_orders) = VALUE zorders_t(
( order_id = '12345' status = 'NEW' )
).

8. Comments - Format Comments

Before:

"This method calculates the total price
"incl. VAT and discount.
"Parameter: iv_net_price - Net price
" iv_discount - Discount in percent
"Return: Gross price
METHOD calculate_gross_price.

After:

" This method calculates the total price
" incl. VAT and discount.
" Parameter: iv_net_price - Net price
" iv_discount - Discount in percent
" Return: Gross price
METHOD calculate_gross_price.

Configuration and Profiles

Profile Concept

ABAP Cleaner uses profiles for configuration. A profile defines which rules are active and how they are applied.

┌──────────────────────────────────────────────────────────────┐
│ Profile Manager │
├──────────────────────────────────────────────────────────────┤
│ │
│ Profiles: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ [★] Default │ │
│ │ [ ] Team Standard │ │
│ │ [ ] Strict Modernization │ │
│ │ [ ] Minimal Changes │ │
│ │ [ ] + Create new profile... │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ [ Edit ] [ Copy ] [ Delete ] [ Export ] [ Import ] │
│ │
└──────────────────────────────────────────────────────────────┘

Create and Configure Profile

  1. Open Window → Preferences → ABAP Cleaner
  2. Select Profiles
  3. Click Create new profile

Rule Configuration

Each rule can be individually configured:

┌──────────────────────────────────────────────────────────────┐
│ Rule Configuration: "Use inline declarations" │
├──────────────────────────────────────────────────────────────┤
│ │
│ ☑ Rule enabled │
│ │
│ Options: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Convert DATA declarations: │ │
│ │ ○ Only if used once │ │
│ │ ● If used in same block │ │
│ │ ○ Always (aggressive) │ │
│ │ │ │
│ │ ☑ Convert FIELD-SYMBOLS │ │
│ │ ☑ Keep declarations for complex types │ │
│ │ ☐ Convert even if type is lost │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Preview: │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ DATA lv_name TYPE │ │ DATA(lv_name) = │ │
│ │ string. │→│ get_name( ). │ │
│ │ lv_name = get_name( │ │ │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘

Export and Share Profile

Profiles can be exported as JSON files and shared within the team:

{
"profileName": "Team Standard",
"rules": {
"EmptyLinesInClassDefinition": {
"enabled": true,
"maxEmptyLines": 1
},
"UseInlineDeclaration": {
"enabled": true,
"convertFieldSymbols": true,
"onlyIfUsedOnce": false
},
"ReplaceCreateObject": {
"enabled": true
},
"RemoveUnusedVariables": {
"enabled": false
}
}
}

Distribute within team:

1. Export profile: Preferences → ABAP Cleaner → Export Profile
2. Place JSON file in Git repository: /config/abap-cleaner-profile.json
3. Team members import: Preferences → ABAP Cleaner → Import Profile

Daily Usage

Interactive Mode

Interactive mode shows all changes before applying them:

Shortcut: Ctrl+4 (or Source → Clean Up With Interactive ABAP Cleaner...)
┌──────────────────────────────────────────────────────────────┐
│ Interactive ABAP Cleaner │
├──────────────────────────────────────────────────────────────┤
│ │
│ Rule: "Replace obsolete ADD with modern syntax" │
│ │
│ ┌─────────────────────────────┬─────────────────────────┐ │
│ │ Before │ After │ │
│ ├─────────────────────────────┼─────────────────────────┤ │
│ │ ADD 1 TO lv_count. │ lv_count += 1. │ │
│ │ ADD lv_amount TO lv_total. │ lv_total += lv_amount. │ │
│ └─────────────────────────────┴─────────────────────────┘ │
│ │
│ [✓ Apply] [✗ Skip] [Apply All] [Skip All] [Show Rule Info] │
│ │
│ Changes: 2 of 15 applied | Profile: Team Standard │
│ │
└──────────────────────────────────────────────────────────────┘

Automatic Mode

Apply all rules without confirmation:

Shortcut: Ctrl+Shift+4 (or Source → Clean Up With Automatic ABAP Cleaner)

Cleanup on Save

ABAP Cleaner can be automatically executed on every save:

Preferences → ABAP Cleaner → Clean-up on save
☑ Enable clean-up on save
Profile: [Team Standard ▼]
Scope:
○ Current method only
● Changed code blocks
○ Entire source code
☑ Show notification after clean-up

Configure Keyboard Shortcuts

Window → Preferences → General → Keys
Search: "ABAP Cleaner"
Results:
- Clean Up With Interactive ABAP Cleaner: Ctrl+4
- Clean Up With Automatic ABAP Cleaner: Ctrl+Shift+4
- Open ABAP Cleaner Preferences: Ctrl+Shift+Alt+4

Complete Before/After Example

Before: Legacy Code

CLASS zcl_order_processor DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS: process_order
IMPORTING
iv_order_id TYPE zorder_id
RETURNING
VALUE(rv_success) TYPE abap_bool
RAISING
zcx_order_error.
PRIVATE SECTION.
DATA: mv_client TYPE mandt.
DATA: mo_logger TYPE REF TO zcl_logger.
ENDCLASS.
CLASS zcl_order_processor IMPLEMENTATION.
METHOD process_order.
DATA: lv_status TYPE zorder_status,
lt_items TYPE TABLE OF zorder_item,
ls_item TYPE zorder_item,
lv_total TYPE p DECIMALS 2,
lo_validator TYPE REF TO zcl_order_validator.
CREATE OBJECT lo_validator.
CLEAR lv_total.
SELECT SINGLE status
FROM zorders
INTO lv_status
WHERE order_id EQ iv_order_id.
IF sy-subrc NE 0.
RAISE EXCEPTION TYPE zcx_order_error.
ENDIF.
IF lv_status EQ 'CLOSED'.
rv_success = abap_false.
RETURN.
ENDIF.
SELECT *
FROM zorder_items
INTO TABLE lt_items
WHERE order_id EQ iv_order_id.
LOOP AT lt_items INTO ls_item.
ADD ls_item-amount TO lv_total.
ENDLOOP.
CALL METHOD lo_validator->validate
EXPORTING
iv_order_id = iv_order_id
iv_total = lv_total
RECEIVING
rv_valid = DATA(lv_valid).
IF lv_valid EQ abap_true.
UPDATE zorders SET status = 'PROCESSING' WHERE order_id = iv_order_id.
rv_success = abap_true.
ELSE.
rv_success = abap_false.
ENDIF.
ENDMETHOD.
ENDCLASS.

After: Modernized Code

CLASS zcl_order_processor DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS process_order
IMPORTING iv_order_id TYPE zorder_id
RETURNING VALUE(rv_success) TYPE abap_bool
RAISING zcx_order_error.
PRIVATE SECTION.
DATA mv_client TYPE mandt.
DATA mo_logger TYPE REF TO zcl_logger.
ENDCLASS.
CLASS zcl_order_processor IMPLEMENTATION.
METHOD process_order.
DATA(lo_validator) = NEW zcl_order_validator( ).
SELECT SINGLE status
FROM zorders
INTO @DATA(lv_status)
WHERE order_id = @iv_order_id.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE zcx_order_error.
ENDIF.
IF lv_status = 'CLOSED'.
rv_success = abap_false.
RETURN.
ENDIF.
SELECT *
FROM zorder_items
INTO TABLE @DATA(lt_items)
WHERE order_id = @iv_order_id.
DATA(lv_total) = REDUCE #( INIT sum = 0
FOR ls_item IN lt_items
NEXT sum += ls_item-amount ).
DATA(lv_valid) = lo_validator->validate( iv_order_id = iv_order_id
iv_total = lv_total ).
IF lv_valid = abap_true.
UPDATE zorders SET status = 'PROCESSING' WHERE order_id = iv_order_id.
rv_success = abap_true.
ELSE.
rv_success = abap_false.
ENDIF.
ENDMETHOD.
ENDCLASS.

Summary of Changes

CategoryChange
Empty linesRemoved unnecessary empty lines, logical grouping
DeclarationsResolved chained DATA: declarations
CREATE OBJECTReplaced with NEW #( )
CLEARRemoved unnecessary CLEAR (variable is directly assigned)
CALL METHODUsed functional notation
EQ/NEReplaced with =/<>
Inline DATAInline declarations where appropriate
REDUCEReplaced LOOP AT with ADD with REDUCE
AlignmentConsistent parameter alignment

CI/CD Integration

ABAP Cleaner can be used as part of the CI/CD pipeline to check code standards:

.github/workflows/abap-style.yml
name: ABAP Style Check
on:
pull_request:
paths:
- 'src/**.abap'
jobs:
style-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
java-version: '11'
distribution: 'temurin'
- name: Run ABAP Cleaner Check
run: |
# Download ABAP Cleaner CLI
wget https://github.com/SAP/abap-cleaner/releases/latest/download/abap-cleaner-cli.jar
# Check if cleanup changes would be needed
java -jar abap-cleaner-cli.jar \
--profile config/abap-cleaner-profile.json \
--check-only \
--source src/
- name: Upload Report
if: failure()
uses: actions/upload-artifact@v4
with:
name: cleanup-report
path: cleanup-report.txt

Best Practices

TopicRecommendation
Profile selectionStart with default profile, adjust gradually
Team standardDefine a shared profile for the entire team
Cleanup on saveEnable for consistent code
Interactive modeCheck interactively first for legacy code
Code reviewPerform cleanup before review
New rulesCheck new rules on ABAP Cleaner updates
ExceptionsRules can be disabled for specific files
CI/CDIntegrate as style check in pipeline
MigrationClean up legacy code gradually, not all at once
DocumentationDocument team profile decisions
  • gCTS - Git-based Transport Management
  • CI/CD with ABAP Cloud - Automated Pipelines
  • ADT Tips & Tricks - Efficient Development in Eclipse