Les convertisseurs COBOL vers Java produisent du « Jobol »

Pourquoi les convertisseurs COBOL vers Java produisent du « Jobol » et comment l'éviter

La conversion s'est achevée en trois mois. L'application Java a été compilée, a passé avec succès les tests de base et a été déclarée prête pour le déploiement. Six mois après sa mise en production, l'équipe plateforme a constaté un problème préoccupant : aucun membre de l'équipe Java n'était capable de maintenir le code. Les revues de code prenaient deux fois plus de temps que d'habitude. Les rapports de bogues faisaient référence à des concepts tels que WORKING-STORAGE, la logique de PROCEDURE DIVISION et les conventions des champs décimaux compactés, autant de notions que les développeurs Java sans expérience mainframe ne pouvaient pas comprendre. L'application avait été convertie de COBOL en Java, mais le code Java résultant pensait, était structuré et se comportait comme du COBOL. C'était du Jobol.

Jobol (parfois stylisé JOBOL) est un mot-valise désignant du code Java qui conserve la sémantique procédurale du COBOL sans pour autant exploiter les capacités orientées objet de Java. Jason Bloomberg, fondateur du cabinet d'analystes Intellyx, le définit clairement : Jobol est un code Java techniquement valide qui préserve l'architecture logicielle d'origine et la sémantique du COBOL, obligeant les développeurs à traiter le Java comme s'il s'agissait de COBOL. La conséquence majeure est immédiate : une entreprise qui convertit du COBOL en Jobol ne s'affranchit pas pour autant de la nécessité d'une expertise COBOL. Elle déplace simplement cette exigence d'expertise vers un langage qui ne prend pas en charge nativement les modèles COBOL présents dans le code. Elle crée ainsi un système plus difficile à maintenir que le COBOL d'origine et qui ne bénéficie d'aucun des avantages attendus de Java.

Graphique de dépendances avant la conception du domaine

SMART TS XL cartographie chaque dépendance PERFORM et CALL afin que la couche de service Java reflète la structure de couplage réelle, et non la structure de classe COBOL.

EN SAVOIR PLUS…

À quoi ressemble réellement Jobol ?

Avant d'en diagnostiquer les causes, il est nécessaire d'identifier le schéma. Prenons l'exemple d'une même logique métier, une vérification de solvabilité client, sous trois formes : le COBOL d'origine, le Jobol généralement produit par les convertisseurs automatisés, et le Java idiomatique issu d'une modernisation réalisée par des experts.

Le COBOL original :

Cobol

       PERFORM-CREDIT-CHECK.
           MOVE WS-CUST-ID TO DB-CUST-KEY
           READ CUSTOMER-FILE KEY IS DB-CUST-KEY
               INVALID KEY
                   MOVE 'NOT FOUND' TO WS-STATUS
                   PERFORM HANDLE-MISSING-CUSTOMER
           END-READ

           IF CUST-CREDIT-SCORE < 650
               MOVE 'DECLINED' TO WS-CREDIT-RESULT
               MOVE 'Score below threshold' TO WS-DECLINE-REASON
           ELSE IF CUST-OUTSTANDING-BALANCE > CUST-CREDIT-LIMIT
               MOVE 'DECLINED' TO WS-CREDIT-RESULT
               MOVE 'Balance exceeds limit' TO WS-DECLINE-REASON
           ELSE
               MOVE 'APPROVED' TO WS-CREDIT-RESULT
           END-IF.

Le Jobol produit par un convertisseur ligne par ligne :

Java

// Jobol: procedural COBOL logic mapped directly to Java
// No OOP, no encapsulation, static global state, COBOL naming conventions
public class CreditCheckProgram {
    static String WS_CUST_ID;
    static String DB_CUST_KEY;
    static String WS_STATUS;
    static String WS_CREDIT_RESULT;
    static String WS_DECLINE_REASON;
    static int CUST_CREDIT_SCORE;
    static double CUST_OUTSTANDING_BALANCE;
    static double CUST_CREDIT_LIMIT;

    public static void PERFORM_CREDIT_CHECK() {
        DB_CUST_KEY = WS_CUST_ID;
        Customer customer = CustomerFile.read(DB_CUST_KEY);
        if (customer == null) {
            WS_STATUS = "NOT FOUND";
            HANDLE_MISSING_CUSTOMER();
            return;
        }
        CUST_CREDIT_SCORE = customer.getCreditScore();
        CUST_OUTSTANDING_BALANCE = customer.getOutstandingBalance();
        CUST_CREDIT_LIMIT = customer.getCreditLimit();

        if (CUST_CREDIT_SCORE < 650) {
            WS_CREDIT_RESULT = "DECLINED";
            WS_DECLINE_REASON = "Score below threshold";
        } else if (CUST_OUTSTANDING_BALANCE > CUST_CREDIT_LIMIT) {
            WS_CREDIT_RESULT = "DECLINED";
            WS_DECLINE_REASON = "Balance exceeds limit";
        } else {
            WS_CREDIT_RESULT = "APPROVED";
        }
    }
}

L'idiome Java, une modernisation experte, produit :

Java

// Idiomatic Java: domain model, encapsulation, value objects, Spring service
@Service
public class CreditCheckService {

    private final CustomerRepository customerRepository;

    public CreditCheckService(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }

    public CreditDecision check(CustomerId customerId) {
        Customer customer = customerRepository.findById(customerId)
            .orElseThrow(() -> new CustomerNotFoundException(customerId));

        if (customer.creditScore() < CreditThresholds.MINIMUM_SCORE) {
            return CreditDecision.declined("Score below threshold");
        }
        if (customer.outstandingBalance().compareTo(customer.creditLimit()) > 0) {
            return CreditDecision.declined("Balance exceeds limit");
        }
        return CreditDecision.approved();
    }
}

La version Jobol comporte 32 lignes d'état global statique, structurée de manière procédurale, nommée selon la syntaxe COBOL et impossible à tester unitairement sans initialiser au préalable l'état global de la classe. La version idiomatique, quant à elle, ne compte que 20 lignes, est injectable, testable et immédiatement lisible par tout développeur Java sans connaissance préalable du COBOL. Les deux versions implémentent les mêmes règles métier. Seule la première est maintenable par l'équipe que la modernisation était censée faciliter.

Les six modèles Jobol et pourquoi les transformateurs les produisent

Modèle 1 : État statique global remplaçant WORKING-STORAGE

La section WORKING-STORAGE de COBOL contient les variables de programme qui persistent entre les appels PERFORM. Elle représente l'état mutable partagé du programme ; chaque paragraphe lit et écrit les mêmes variables WS. Les convertisseurs automatiques établissent un mappage direct vers les champs statiques Java d'une seule classe, préservant ainsi la sémantique de l'état partagé tout en violant tous les principes d'encapsulation de Java.

Pourquoi les convertisseurs agissent-ils ainsi ? La correspondance entre WORKING-STORAGE et les champs statiques est une transformation mécanique un à un. Le convertisseur ne comprend pas que le modèle d'encapsulation de Java remplace l'état partagé au niveau du programme par des variables d'instance, des paramètres de méthode et des valeurs de retour. Il convertit ce qu'il voit sans comprendre la raison d'être de la structure d'origine.

Conséquence : l’état statique est impossible à tester isolément, non sécurisé pour les threads sans synchronisation explicite et structurellement identique au COBOL qu’il remplace. Tout développeur souhaitant ajouter de la concurrence au système doit d’abord démêler l’état statique, une tâche reportée de la conversion à chaque mainteneur ultérieur.

Solution : Associer les variables de stockage de travail (WORKING-STORAGE) à des champs d’instance, des paramètres de méthode ou des valeurs de retour en fonction de leur contexte d’utilisation. Les variables utilisées uniquement au sein d’un même paragraphe deviennent des variables locales. Les variables transmises entre les paragraphes deviennent des paramètres de méthode ou des valeurs de retour. Les variables représentant un état d’objet partagé deviennent des champs d’instance d’une classe de domaine correctement encapsulée.

Modèle 2 : Les chaînes PERFORM deviennent de longues méthodes statiques

Les programmes COBOL sont organisés en chaînes PERFORM. La procédure principale exécute une séquence de paragraphes nommés, chacun pouvant en exécuter d'autres. Des convertisseurs automatiques traduisent chaque paragraphe en une méthode statique et chaque instruction PERFORM en un appel de méthode statique. Il en résulte une classe unique comportant des dizaines de méthodes statiques s'appelant mutuellement via un graphe d'appels qui reflète la structure PERFORM de COBOL.

Java

// Jobol: PERFORM chain mapped to static method calls
public class ProcessOrderProgram {
    public static void main() {
        validateOrder();
        checkInventory();
        calculateTotal();
        applyDiscounts();
        processPayment();
        generateConfirmation();
        updateInventory();
    }

    public static void validateOrder() { /* ... */ }
    public static void checkInventory() { /* ... */ }
    // ... 40 more static methods
}

Pourquoi les convertisseurs agissent ainsi : la correspondance structurelle entre un paragraphe COBOL et une méthode Java est directe. Le convertisseur identifie correctement que PERFORM signifie « appeler cette unité de code ». Ce qu’il ne prend pas en compte, c’est que le modèle objet de Java organise le code autour d’entités du domaine, et non autour d’étapes de traitement.

Le correctif: Identifiez les concepts du domaine intégrés dans la chaîne PERFORM. validateOrder, calculateTotal et applyDiscounts sont des opérations sur une commande. processPayment est une opération effectuée dans un contexte de paiement. checkInventory et updateInventory Il s'agit d'opérations sur l'inventaire. La refactorisation répartit les méthodes statiques dans les classes de domaine auxquelles elles appartiennent, remplaçant une longue chaîne d'appels procéduraux par une courte méthode d'orchestration qui délègue aux objets de domaine appropriés.

Modèle 3 : Types numériques sans gestion de précision

Les champs décimaux compactés (COMP-3) de COBOL permettent des calculs décimaux exacts. Des convertisseurs permettent de mapper COMP-3 vers Java. double or float introduire des erreurs d'arrondi systématiques en virgule flottante qui s'accumulent sur des millions de transactions financières.

Java

// Jobol: COMP-3 mapped to double (wrong)
static double WS_TRANSACTION_AMOUNT;  // Was PIC S9(9)V99 COMP-3
static double WS_RUNNING_TOTAL;

// After 10M additions of 0.1:
// Expected: 1,000,000.00
// Actual:   1,000,000.0000000002 (floating point accumulation)

Java

// Idiomatic Java: COMP-3 mapped to BigDecimal (correct)
private BigDecimal transactionAmount;  // Preserves decimal precision
private BigDecimal runningTotal;

// After 10M additions of 0.1:
// Result: exactly 1,000,000.00

Pourquoi les convertisseurs font cela : double Il s'agit du type numérique par défaut de Java. Un convertisseur qui ne prend pas en charge la sémantique COMP-3 utilisera par défaut le type numérique Java le plus courant, ce qui est inapproprié pour les calculs décimaux exacts.

Le correctif: Chaque champ COMP-3 correspond à BigDecimalChaque calcul numérique impliquant des valeurs COMP-3 utilise BigDecimal arithmétique. scale() du système BigDecimal correspond au nombre de décimales implicites dans la clause PIC : PIC S9(9)V99 comporte 2 décimales implicites, correspondant à BigDecimal à l'échelle 2.

Modèle 4 : Chaînes EBCDIC et vestiges de tableaux d’octets

COBOL traite les données de caractères sous forme de chaînes de caractères de longueur fixe complétées par des espaces, encodées en EBCDIC. Les convertisseurs produisent souvent du code Java qui préserve la sémantique des chaînes de caractères de longueur fixe complétées par des espaces, même après conversion en UTF-8, ce qui donne un code Java qui gère les chaînes de caractères comme un programmeur de mainframe des années 1970.

Java

// Jobol: preserves COBOL fixed-length string handling
static char[] WS_CUSTOMER_NAME = new char[40]; // Was PIC X(40)
static char[] WS_CUSTOMER_ADDR = new char[80]; // Was PIC X(80)

// COBOL-style space-padding comparison
if (new String(WS_CUSTOMER_NAME).trim().equals("SMITH")) { ... }

// COBOL-style string concatenation with padding
System.arraycopy(name.toCharArray(), 0, WS_CUSTOMER_NAME, 0,
                 Math.min(name.length(), 40));

Java

// Idiomatic Java: proper String types and domain objects
private String customerName;  // Trim on read, no padding
private String customerAddress;

// Clean comparison
if ("Smith".equalsIgnoreCase(customer.name())) { ... }

Pourquoi les convertisseurs agissent ainsi : la définition COBOL PIC X(40) possède une longueur que le convertisseur conserve comme taille de tableau Java. La conversion EBCDIC vers UTF-8 peut être gérée, mais la sémantique de longueur fixe est inutilement conservée.

Le correctif: Carte PIC X(n) champs à String Les objets sont tronqués à la lecture et les contraintes de longueur fixe sont supprimées. Les contraintes de longueur n'ont d'importance qu'aux limites de persistance (longueurs des colonnes de la base de données, positions dans le format de fichier), et non dans le modèle de domaine.

Modèle 5 : Gestion des erreurs via les codes d’état

En COBOL, les erreurs sont communiquées par des codes d'état : FILE STATUS, SQLCODE et RETURN-CODE. Les convertisseurs Java préservent ce modèle en générant un code qui vérifie les codes d'état entiers après chaque opération, plutôt que d'utiliser le modèle d'exceptions de Java.

Java

// Jobol: COBOL status code pattern in Java
static int WS_FILE_STATUS;
static int WS_SQL_CODE;

CustomerFile.read(DB_CUST_KEY);
WS_FILE_STATUS = CustomerFile.getLastStatus();
if (WS_FILE_STATUS == 10) {
    // Not found
} else if (WS_FILE_STATUS == 35) {
    // File not available
} else if (WS_FILE_STATUS != 0) {
    // Unknown error
}

Java

// Idiomatic Java: exceptions for error conditions
try {
    Customer customer = customerRepository.findById(customerId)
        .orElseThrow(() -> new CustomerNotFoundException(customerId));
    // process customer
} catch (DatabaseUnavailableException e) {
    log.error("Database unavailable during credit check", e);
    throw new CreditCheckServiceException("Service temporarily unavailable", e);
}

Pourquoi les convertisseurs procèdent ainsi : la vérification du code d’état est une pratique courante en COBOL ; la convertir en une méthode Java qui renvoie ou définit un champ d’état est une simple transformation mécanique. Comprendre que les exceptions Java constituent l’équivalent idiomatique requiert un jugement architectural, et non une traduction mécanique.

Le correctif: Associez les conditions d'erreur COBOL aux exceptions Java. L'erreur FILE STATUS 10 (enregistrement introuvable) devient Optional.empty() ou une exception spécifique au domaine. Les erreurs SQLCODE deviennent des exceptions typées de base de données. La structure du code Java reflète l'intention : bloc try/catch pour les modes d'échec prévus, exceptions non vérifiées pour les imprévus.

Modèle 6 : Structure de classe monolithique

Les programmes COBOL sont monolithiques par conception : un seul programme gère une fonction métier complète. Les convertisseurs créent une seule classe Java par programme COBOL, produisant des classes Java pouvant compter des milliers de lignes et gérer de multiples responsabilités qu’une conception orientée objet répartirait entre plusieurs classes spécialisées.

Pourquoi les convertisseurs procèdent ainsi : un programme COBOL correspond à une classe Java ; l’unité de traduction du convertisseur est le programme lui-même. La structure interne du programme (ses paragraphes, sections et chaînes PERFORM) devient la structure interne de la classe sans décomposition en classes plus petites.

La solution : appliquer le principe de responsabilité unique à chaque classe convertie. Une classe Jobol de 3 000 lignes qui valide les entrées, lit les données d’une base de données, applique les règles métier, formate la sortie et écrit dans un fichier devrait devenir : un validateur d’entrée, un dépôt, un service de domaine, un formateur et un enregistreur, chacun testable indépendamment et dédié à une seule responsabilité.

Prérequis en analyse structurale

Chaque modèle Jobol possède une refactorisation correspondante. Cependant, cette refactorisation ne peut être appliquée par le convertisseur ; il est nécessaire de comprendre le fonctionnement du programme avant de décider de sa restructuration. C’est précisément ce que permet l’analyse structurelle du code source COBOL avant l’exécution de tout outil de conversion.

Plus précisément, l'analyse structurelle fournit les données nécessaires aux décisions de conversion :

Inventaire de la logique métier. Il s'agit de l'énumération complète des conditions EVALUATE, des branches IF et des formules COMPUTE de chaque programme, décrivant ce que le programme décide, et non seulement sa structure. Cet inventaire constitue la spécification à laquelle le code Java refactorisé doit se conformer. Un programme comportant 847 conditions EVALUATE requiert une structure de classes cible pouvant gérer 847 points de décision ; le résultat du convertisseur est une ébauche, et non le produit final.

Graphe de dépendances. Ce graphe représente les appels entre programmes. La chaîne PERFORM, qui s'étend sur plusieurs programmes COBOL, devient un graphe d'appels de services en Java. Ce graphe détermine quels programmes peuvent être refactorisés indépendamment (peu de dépendances entrantes) et lesquels doivent l'être en groupes coordonnés (nombreuses dépendances partagées via des copybooks ou des données partagées).

Identification du code mort. Les paragraphes et sections qui ne sont jamais exécutés en production sont considérés comme du code mort et ne doivent pas figurer dans le code Java converti. Les convertisseurs qui incluent du code mort augmentent considérablement la taille du code converti et l'effort de refactorisation ultérieur.

Récupération du modèle de données. Les entrées VSAM FD, les définitions de tables DB2 et les structures de copybook qui définissent les données du programme. Chaque structure de données COBOL correspond à un ou plusieurs objets de domaine Java ; cette correspondance est déterminée par le modèle de données, et non par les noms de champs dans WORKING-STORAGE.

Données d'analyseDécision de conversion qu'elle pilote
ÉVALUER/SI nombre de branchesDécomposition du domaine en classes : nombre de cas de décision gérés par chaque service
nombre de dépendances entrantesSéquence de refactorisation : commencer par les programmes ayant le moins de dépendances.
Pourcentage de code mortRéduction du périmètre, exclusion du code inaccessible de la conversion
Champs COMP-3BigDecimal mappage pour chaque champ numérique
longueurs de champ PIC XMappage des types de chaînes et contraintes de la couche de persistance
structure de graphe PERFORMDécomposition de la couche de service, les chaînes PERFORM deviennent des appels de service de domaine
Utilisation de l'état des fichiersConception du modèle d'exceptions : quels codes d'état correspondent à quels types d'exceptions ?

Ce que la conversion devrait produire à la place

L'alternative à Jobol est un processus de conversion qui utilise le COBOL comme spécification plutôt que comme modèle. Le programme COBOL connaît la logique métier ; le programme Java doit exprimer cette logique selon l'idiome Java.

Le principe de conversion : Convertissez l'intention, pas la syntaxe. Le COBOL IF CUST-CREDIT-SCORE < 650 MOVE 'DECLINED' TO WS-CREDIT-RESULT Le message indique qu'« un client dont le score de crédit est inférieur à 650 se voit refuser une demande ». Le code Java devrait exprimer la même chose dans son propre langage. if (customer.creditScore() < CreditThresholds.MINIMUM_SCORE) return CreditDecision.declined(...). ne pas if (CUST_CREDIT_SCORE < 650) { WS_CREDIT_RESULT = "DECLINED"; }.

Le flux de travail de conversion pratique qui produit du Java idiomatique :

Étape 1 : Analyse structurelle du code source COBOL, graphe de dépendances, inventaire de la logique métier, récupération du modèle de données, identification du code mort. Cette étape génère la spécification. Aucune conversion n’est effectuée tant que cette étape n’est pas terminée.

Étape 2 : Conception du modèle de domaine : identifier les entités métier (Client, Compte, Transaction, Police) sur lesquelles opèrent les programmes COBOL. Celles-ci deviennent des classes de domaine Java. Les variables de stockage de travail représentant l’état des entités deviennent des champs d’instance de la classe de domaine appropriée.

Étape 3 : Conception de la couche de service. Identifiez les opérations métier (vérification de solvabilité, traitement des paiements, calcul du solde) implémentées par les paragraphes et sections COBOL. Celles-ci deviennent des méthodes de service Java dans la classe de service de domaine appropriée. La chaîne PERFORM qui séquence les paragraphes constitue la logique d’orchestration du service.

Étape 4 : Conversion automatisée en tant que générateur de brouillons : utilisez l’outil de conversion pour produire un brouillon Java de départ pour chaque programme, en vous basant sur le modèle de domaine conçu aux étapes 2 et 3. Examinez le brouillon par rapport à l’inventaire de la logique métier pour vérifier que chaque branche de décision est représentée.

Étape 5 : Refactorisation systématique contre les modèles Jobol, application des six correctifs d'anti-modèles : remplacer l'état statique par un état d'instance, distribuer les méthodes statiques aux classes de domaine, remplacer les codes d'état par des exceptions, remplacer double au BigDecimal, remplacez les tableaux de longueur fixe par String, décomposer les classes monolithiques en services à responsabilité unique.

Étape 6 : Tests d’équivalence comportementale : exécuter le code Java refactorisé sur le corpus de tests de caractérisation généré par l’analyse COBOL. Chaque condition EVALUATE et chaque branche IF doit comporter au moins un cas de test qui l’applique et vérifie son équivalence avec la sortie COBOL.

Comment SMART TS XL Empêche Jobol à la source

L'analyse structurelle préalable à la prévention des erreurs Jobol, à savoir le graphe de dépendances, l'inventaire de la logique métier, l'identification du code mort et la récupération du modèle de données, est exactement ce que SMART TS XLL'analyse du code source COBOL par [nom de l'entreprise] est effectuée avant l'exécution de tout outil de conversion.

SMART TS XL's analyse de code statique L'outil de conversion énumère chaque condition EVALUATE, chaque branche IF, chaque formule COMPUTE et chaque relation PERFORM du code source COBOL, constituant ainsi l'inventaire complet de la logique métier que le code Java refactorisé doit fidèlement reproduire. Lors de la génération du Jobol, cet inventaire sert de spécification de référence pour la validation de la refactorisation : chaque condition de l'inventaire doit apparaître en Java, dans la classe de domaine appropriée et exprimée selon les conventions Java.

Le mappage des dépendances applicatives génère le graphe PERFORM et le graphe d'appels de programmes qui déterminent la structure de la couche de services Java. Les programmes qui s'appellent fréquemment appartiennent au même contexte délimité. Les programmes appelés par de nombreux autres constituent les services partagés, qui doivent être conçus avec soin pour prendre en charge tous leurs consommateurs dans l'architecture Java. Le graphe de dépendances sert d'entrée à l'étape de conception de la couche de services, convertissant la structure de couplage implicite de COBOL en la structure d'injection de dépendances explicite de Java.

L' analyse de modernisation du code existant identifie le code mort, c'est-à-dire les paragraphes et sections jamais exécutés en production, qui doivent être totalement exclus du périmètre de conversion. Convertir le COBOL mort en Jobol mort, puis refactoriser ce dernier, représente un gaspillage d'efforts que l'étape d'identification du code mort permet d'éviter avant même le début de la conversion.

La fonction d'analyse d'impact valide la séquence de refactorisation : lorsqu'une interface de programme COBOL est modifiée lors de la refactorisation de Jobol vers Java idiomatique, l'analyse d'impact identifie tous les autres programmes dont l'équivalent Java doit également être mis à jour pour rester cohérent avec la nouvelle interface. La refactorisation par ordre de dépendance, en commençant par les programmes les moins dépendants, minimise la coordination nécessaire à chaque étape.

Les convertisseurs traduisent la syntaxe. La modernisation transforme le sens.

Les convertisseurs COBOL vers Java automatisés sont plus rapides que la réécriture humaine. Ils sont également optimisés pour un objectif différent : la correspondance syntaxique plutôt que l’équivalence sémantique dans l’idiome du langage cible. Jobol est le résultat de cette correspondance syntaxique lorsque le langage source (COBOL) et le langage cible (Java) possèdent des modèles structurels fondamentalement différents.

Les organisations qui évitent Jobol ne sont pas celles qui utilisent de meilleurs convertisseurs. Ce sont celles qui considèrent le convertisseur comme un générateur d'ébauches plutôt que comme un produit fini, qui investissent dans une analyse structurelle avant le lancement de la conversion, qui conçoivent le modèle de domaine Java avant l'exécution du convertisseur et qui appliquent une refactorisation systématique au résultat du convertisseur en utilisant l'inventaire de la logique métier comme spécification de validation.

Jobol est évitable. Le coût de cette solution réside dans l'analyse structurelle et la modélisation du domaine qui doivent précéder toute conversion. Le coût de son utilisation est un système Java dont la maintenance requiert une expertise COBOL, ce qui signifie que le bénéfice attendu de la modernisation, à savoir la possibilité de maintenir le système avec des développeurs Java, ne se concrétise jamais.

Convertissez l'intention, pas la syntaxe. C'est là toute la valeur de la modernisation.