> ## Content Index
> Fetch the complete content index at: https://www.sfeir.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Quarkus : la forge aux extensions
- URL: https://www.sfeir.dev/back/quarkus-la-forge-aux-extensions/
- Published: 2026-08-27T06:51:52.000Z
- Updated: 2026-08-27T06:51:52.000Z
- Description: Jusqu’ici, la Taverne utilisait la magie de Quarkus. Cette fois, le tavernier descend dans la forge pour créer sa propre extension : runtime, deployment, BuildStep, Dev UI… et surtout les pièges silencieux qui peuvent transformer une relique parfaitement forgée en artefact muet.
- Author: Erwan Le Tutour
- Tags: Back, Java, quarkus

## La descente dans la Forge

The Falling Whale dort encore quand le tavernier soulève la trappe. Personne, en des années de service au comptoir, n'avait remarqué cette latte de bois plus sombre que les autres, ni le courant d'air froid qui en montait certains soirs d'orage.

L'escalier descend plus profond que la cave à tonneaux. Il descend jusqu'à une salle de pierre noircie, où dort une forge qu'on croyait éteinte depuis des générations. Le charbon y est encore tiède.

Jusqu'ici, dans cette taverne, nous avons appris à manier des outils. Des requêtes GraphQL, des flux réactifs, un CLI qui répond au doigt et à l'œil. Des reliques que d'autres avaient forgées avant nous, et que nous avons simplement su tenir en main.

Ce chapitre est différent. Il est méta, au sens propre : on ne va plus utiliser un outil de Quarkus, on va apprendre à en forger un nouveau. Descendre sous les fondations du royaume pour comprendre comment elles tiennent debout, et couler notre propre pierre.

> « Nous avons appris à manier les outils, dit le tavernier en époussetant l'enclume. Il est temps d'en forger de nouveaux. »

## Pourquoi forger une extension ?

Avant d'allumer la forge, une question honnête : pourquoi s'infliger ça ? Une extension Quarkus, ce n'est pas un service de plus dans une application. C'est un artefact séparé, avec son propre cycle de build, ses propres tests, sa propre publication. Le coût d'entrée n'est pas nul.

Mais certains besoins reviennent, service après service, royaume après royaume :

- Centraliser un comportement commun sans le copier-coller dans chaque projet
- Exposer une fonctionnalité réutilisable, avec sa propre configuration, ses propres conventions
- Étendre Quarkus à la manière d'un artisan qui agrandit son atelier plutôt que de racheter des outils à chaque commande
- Donner une identité technique propre à son organisation, un peu comme la Taverne a fini par avoir son propre folklore

Une extension, c'est le moment où on arrête de consommer la magie du royaume pour commencer à en produire. C'est un changement de posture, pas juste un changement de code.

## Anatomie d'une extension Quarkus

Une extension Quarkus vit toujours en deux moitiés, et comprendre pourquoi ces deux moitiés existent est la clé de tout le reste.

**Le module `runtime`** contient ce qui finira réellement dans le JAR de l'application qui consomme l'extension. Des services, des annotations, de la configuration. C'est la relique elle-même, celle que l'aventurier emporte avec lui.

**Le module `deployment`** ne s'exécute jamais chez l'aventurier. Il tourne une seule fois, au moment du build, et n'existe que pour décider quoi mettre dans la relique. C'est la forge : le feu, le marteau, l'enclume, tout ce qui ne voyage pas avec l'objet fini.

Cette séparation a une conséquence concrète et souvent déroutante au premier abord : le code du module `deployment` ne tourne jamais dans l'application finale, même en production. Il tourne pendant le build, point final. C'est ce qui permet à Quarkus de faire une bonne partie de son travail avant le démarrage plutôt que pendant, et c'est une des raisons de son temps de démarrage.

Dans ce module `deployment`, on trouve des classes appelées **processeurs**, dont les méthodes sont annotées `@BuildStep`. Chaque `@BuildStep` est un coup de marteau : il s'exécute une fois, prend en entrée ce que d'autres coups de marteau ont produit avant lui, et produit à son tour un ou plusieurs **BuildItem**, les pièces qui circulent entre les différentes étapes de la forge.

Le `BuildItem` le plus simple à comprendre est le `FeatureBuildItem` : il ne fait rien d'autre que déclarer "cette extension existe", ce qui la fait apparaître dans la liste des fonctionnalités installées au démarrage de l'application. C'est souvent le tout premier coup de marteau qu'on donne en forgeant une nouvelle extension, et le plus rassurant : le voir apparaître dans les logs, c'est la première preuve tangible que la forge s'est bien allumée.

## Forger la relique : `@Taverne`, du concept au sortilège

Assez de théorie. Descendons vraiment dans la forge et façonnons une relique complète, du minerai brut jusqu'à l'objet fini : une annotation `@Taverne`, qui transforme n'importe quelle méthode d'un service en une petite scène jouée par le tavernier lui-même.

### Initialisation du projet

Une extension Quarkus se structure en un projet Maven multi-module, avec un module racine qui n'existe que pour déclarer les sous-modules :

```text
quarkus-taverne-extension/          (module racine, packaging pom)
├── pom.xml
├── runtime/                        → artifactId quarkus-taverne
├── deployment/                     → artifactId quarkus-taverne-deployment
└── integration-tests/              (un royaume qui invoque la relique)

```

Le nom des répertoires est libre. Celui des `artifactId`, non, et c'est le premier piège de la descente : **l'artifactId du module `deployment` doit être exactement celui du module `runtime` suffixé de `-deployment`**. Ce n'est pas une convention de politesse, c'est un mécanisme. Quarkus s'en sert pour recoller les deux moitiés de l'extension, dans `ArtifactInfoUtil` :

```java
public static final String DEPLOYMENT = "-deployment";
// ...
if (artifactId.endsWith(DEPLOYMENT)) {
    artifactId = artifactId.substring(0, artifactId.length() - DEPLOYMENT.length());
}

```

Nommer naïvement ses modules `runtime` et `deployment`, comme leurs répertoires, suffit à casser ce recollement : `"deployment".endsWith("-deployment")` vaut `false`, l'artifactId reste `deployment`, et les `BuildItem` produits par la forge se retrouvent rattachés à une extension fantôme qui ne correspond à rien. Comme souvent dans cette cave, rien n'explose : ça ne marche simplement pas.

Le module `runtime` ne dépend que du strict nécessaire, ici `quarkus-arc` pour l'injection de dépendances :

```xml
<artifactId>quarkus-taverne</artifactId>
<name>La Taverne</name>
<description>Invoque l'esprit de la taverne : journalise chaque quête entreprise par les héros du royaume.</description>

<dependencies>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-arc</artifactId>
    </dependency>
</dependencies>

```

Ce `<description>` n'est pas de la décoration Maven : il est recopié tel quel dans les métadonnées de l'extension, et on verra en descendant plus bas qu'il vaut mieux ne pas l'oublier.

Viennent ensuite trois plugins indispensables, qui se font oublier facilement, au point d'avoir eu raison de moi une bonne partie d'une soirée de mise au point de cet article. Sans eux, l'extension compile très bien, s'installe très bien, et ne fait strictement rien. Attention à ne pas les mélanger : **deux vont dans le `pom.xml` du module `runtime`, le troisième dans celui du module `deployment`**.

Côté `runtime`, `quarkus-extension-maven-plugin` génère `META-INF/quarkus-extension.yaml` et `META-INF/quarkus-extension.properties`. C'est ce descripteur qui dit à Quarkus « je suis une extension, et voici où trouver ma forge » :

```xml
<plugin>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-extension-maven-plugin</artifactId>
    <version>${quarkus.platform.version}</version>
    <executions>
        <execution>
            <goals>
                <goal>extension-descriptor</goal>
            </goals>
            <phase>process-resources</phase>
            <configuration>
                <deployment>fr.eletutour:quarkus-taverne-deployment:${project.version}</deployment>
            </configuration>
        </execution>
    </executions>
</plugin>

```

Toujours côté `runtime`, `jandex-maven-plugin` indexe les classes du module pour que Quarkus puisse les retrouver au build, notamment tout ce qui touche à l'injection CDI :

```xml
<plugin>
    <groupId>io.smallrye</groupId>
    <artifactId>jandex-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>make-index</id>
            <goals>
                <goal>jandex</goal>
            </goals>
        </execution>
    </executions>
</plugin>

```

Et côté `deployment`, cette fois, l'annotation processor `quarkus-extension-processor`. C'est lui qui génère `META-INF/quarkus-build-steps.list`, le registre dans lequel Quarkus va chercher les processeurs :

```xml
<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>${compiler-plugin.version}</version>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-extension-processor</artifactId>
                <version>${quarkus.platform.version}</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

```

Celui-là mérite qu'on insiste, parce que son absence produit le symptôme le plus déroutant de la descente. Quarkus ne découvre pas les `@BuildStep` en scannant le classpath : il lit ce fichier, et uniquement ce fichier. Sans l'annotation processor, le jar `deployment` contient bien la classe du processeur, compilée, correcte, et pas la moindre ligne pour signaler son existence :

```text
META-INF/MANIFEST.MF
fr/eletutour/taverne/deployment/TaverneProcessor.class
META-INF/maven/...

```

Aucun `@BuildStep` ne s'exécute. Aucun, y compris celui qui déclare l'extension.

> **Encart du forgeron** — Si un jour ton extension s'installe sans erreur mais n'apparaît jamais dans `Installed features` au démarrage, ouvre le jar `deployment` et cherche `META-INF/quarkus-build-steps.list` avant toute autre chose. S'il n'y est pas, tu as ta réponse. C'est le genre de silence qui ne lève aucune exception, et qui peut faire perdre une soirée entière à quelqu'un qui n'a personne pour lui souffler la réponse.  
>  
> Le pire, dans mon cas, c'est que l'extension avait l'air de fonctionner : les annonces du tavernier s'affichaient bien à chaque appel. Un faux positif complet. L'intercepteur était découvert comme bean par un tout autre chemin, le `META-INF/jandex.idx` du module `runtime`, parce que `@Interceptor` est une *bean-defining annotation*. La forge était éteinte, mais la relique brillait quand même.

### La configuration

Les runes gravées dans la pierre, ce sont les paramètres que l'aventurier peut ajuster sans reforger la relique. Quarkus recommande aujourd'hui `@ConfigMapping`, une interface plutôt qu'une classe à annoter champ par champ :

```java
@ConfigMapping(prefix = "taverne") 
@ConfigRoot(phase = ConfigPhase.RUN_TIME) 
public interface TaverneConfig {
  @WithDefault("true")
  boolean enabled();
  
  @WithDefault("BARDE")
  Ton ton();
  
  enum Ton {
      BARDE,
      GROGNON,
      SOBRE
  }
}  

```

On reste volontairement en `RUN_TIME` : rien n'empêche le tavernier de changer d'humeur, ou de se taire complètement, sans reconstruire le royaume.

### Le sortilège runtime

Le cœur de la relique est un intercepteur CDI classique. Chaque méthode marquée `@Taverne` passe entre ses mains avant de s'exécuter :

```java
@Taverne
@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
public class TaverneInterceptor {

    private static final Logger LOG = Logger.getLogger("la-taverne");

    @Inject
    TaverneConfig config;

    @AroundInvoke
    Object crierLaQuete(InvocationContext context) throws Exception {
        if (!config.enabled()) {
            return context.proceed();
        }

        String quete = resoudreNomQuete(context);
        annoncer(quete);

        try {
            Object resultat = context.proceed();
            LOG.infof("[la-taverne] %s : quête accomplie.", quete);
            return resultat;
        } catch (Exception e) {
            LOG.warnf("[la-taverne] %s : la quête a échoué (%s).", quete, e.getMessage());
            throw e;
        }
    }

    private void annoncer(String quete) {
        switch (config.ton()) {
            case GROGNON -> LOG.infof("[la-taverne] Encore une quête... %s. Bon.", quete);
            case SOBRE -> LOG.infof("[la-taverne] Invocation : %s", quete);
            case BARDE -> LOG.infof("[la-taverne] Que l'on m'entende ! La quête « %s » commence !", quete);
        }
    }
}

```

Rien d'exotique jusque-là : c'est un intercepteur CDI que n'importe quel projet Quarkus aurait pu écrire seul, sans extension. La vraie question, la seule qui justifie de descendre dans la forge, arrive maintenant.

### L'annotation, et son piège

`@Taverne` est un `@InterceptorBinding` tout ce qu'il y a de plus standard :

```java
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Taverne {

    @Nonbinding
    String quete() default "";
}

```

Ce `@Nonbinding` sur `quete()` n'est pas décoratif. Sans lui, la valeur passée à l'annotation fait partie de l'identité du binding aux yeux de CDI : `@Taverne(quete = "Terrasser le dragon")` et `@Taverne(quete = "Explorer le donjon")` deviendraient deux bindings distincts, et un intercepteur simplement annoté `@Taverne` ne matcherait que la valeur par défaut, une chaîne vide.

C'est un piège redoutable parce qu'il ne casse rien de visible. Le build passe. L'extension se déclare, apparaît bien dans `Installed features`. L'application démarre, répond aux requêtes, renvoie les bons résultats. Et l'intercepteur ne se déclenche tout simplement jamais, en silence complet, sans la moindre exception pour te mettre sur la piste.

### Le BuildStep

`@Taverne` est bien découverte automatiquement par ArC, le conteneur CDI de Quarkus, parce que c'est un `@InterceptorBinding` standard. Mais `TaverneInterceptor` vit dans le jar `runtime` de l'extension, hors du bean archive de l'application consommatrice. Sans intervention, personne dans le royaume ne saurait qu'il existe.

C'est le rôle du processeur, dans le module `deployment`, de forger la pièce manquante :

```java
class TaverneProcessor {

    private static final String FEATURE = "taverne";

    @BuildStep
    FeatureBuildItem feature() {
        return new FeatureBuildItem(FEATURE);
    }

    @BuildStep
    AdditionalBeanBuildItem enregistrerLeSortilege() {
        return AdditionalBeanBuildItem.builder()
                .addBeanClass(TaverneInterceptor.class)
                .setUnremovable()
                .build();
    }
}

```

Deux coups de marteau, deux `BuildItem` produits. Le premier ne sert qu'à signaler l'existence de l'extension. Le second force ArC à considérer `TaverneInterceptor` comme un bean à part entière, alors même qu'il n'a jamais été scanné dans le code de l'application qui consomme l'extension.

Une précision honnête sur ce second coup de marteau, que j'ai apprise en me trompant : dans cette extension précise, il n'est pas ce qui fait fonctionner l'intercepteur. Le `jandex-maven-plugin` du module `runtime` a déjà indexé `TaverneInterceptor`, et comme `@Interceptor` est une *bean-defining annotation*, ArC le trouve tout seul. `AdditionalBeanBuildItem` reste utile — il rend la découverte explicite plutôt que dépendante de l'index, et le `setUnremovable()` protège le bean de l'élagage — mais il ne faut pas se raconter qu'il est la pièce maîtresse. Croire l'inverse mène droit à un diagnostic faux : voir les logs du tavernier ne prouve pas que la forge a tourné.

On reste ici volontairement à distance de la génération de bytecode à la volée, la vraie magie noire de l'écosystème Quarkus, portée par une bibliothèque nommée Gizmo. `AdditionalBeanBuildItem` suffit largement pour la plupart des extensions qu'on écrit dans un contexte professionnel : c'est exactement le pattern qu'utilisent en interne des extensions comme le cache de Quarkus. Générer une classe entière à la compilation, avec Gizmo, est un sujet suffisamment dense pour mériter un chapitre à lui seul, un jour peut-être.

Un troisième coup de marteau viendra plus loin, quand on voudra que la relique ait sa propre vitrine dans la Dev UI.

### L'épreuve de trempe

Une extension se teste différemment d'une application classique, avec `QuarkusUnitTest`, qui boote une mini-application Quarkus rien que pour vérifier le comportement de l'extension :

```java
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
        .withApplicationRoot((jar) -&gt; jar
                .addClasses(QuestServiceDeTest.class));

@Inject
QuestServiceDeTest questService;

@Test
void laQueteDoitEtreAccomplie() {
    String resultat = questService.partirEnQuete();
    Assertions.assertEquals("Le dragon est vaincu", resultat);
}

```

Voilà un test qui inspire confiance, qui passe au vert du premier coup, et qui pourtant ne prouve strictement rien sur le sujet qu'il est censé couvrir. Il vérifie que `partirEnQuete()` renvoie la bonne valeur, ce qui serait vrai que l'intercepteur se déclenche ou non : `context.proceed()` renvoie de toute façon le résultat de la méthode originale. C'est exactement le test qui m'a laissé croire, pendant un temps, que le piège du `@Nonbinding` n'existait pas.

L'épreuve de trempe honnête doit vérifier que le sortilège a réellement été prononcé, pas seulement que la quête a été accomplie. On capture les logs émis par l'intercepteur, avec un `Handler` attaché au bon logger :

```java
private static final List<LogRecord> RECORDS = new ArrayList<>();

@BeforeAll
static void ecouterLaTaverne() {
    Logger.getLogger("la-taverne").addHandler(new Handler() {
        @Override public void publish(LogRecord record) { RECORDS.add(record); }
        @Override public void flush() {}
        @Override public void close() {}
    });
}

@Test
void lInterceptorDoitReellementParler() {
    questService.partirEnQuete();

    boolean sortilegeProclame = RECORDS.stream()
            .anyMatch(r -> r.getMessage().contains("Terrasser le dragon"));

    Assertions.assertTrue(sortilegeProclame,
            "l'intercepteur n'a laissé aucune trace, le sortilège n'a pas été prononcé");
}

```

Ce test-là aurait crié tout de suite, dès la première exécution, que quelque chose clochait avec le `@Nonbinding` manquant. La leçon dépasse le cadre de cette extension : un test d'extension qui ne vérifie que la valeur de retour d'une méthode, sans jamais observer si le comportement ajouté par l'extension a réellement eu lieu, est une épreuve de trempe qui ment.

### Un mot sur le natif

`TaverneInterceptor` ne fait ici que de l'introspection légère sur le contexte d'invocation, rien qui nécessite de la réflexion Java classique. Mais dès qu'une extension a besoin d'instancier dynamiquement une classe, ou d'inspecter des champs privés par réflexion, GraalVM en mode natif ne le devine pas tout seul : il faut le déclarer explicitement au build avec un `ReflectiveClassBuildItem`, sans quoi le sortilège se brise silencieusement une fois l'image native compilée, alors qu'il fonctionnait très bien sur la JVM.

## La vitrine : une card dans la Dev UI

Une relique posée sur une étagère sans étiquette reste une curiosité. La Dev UI de Quarkus, accessible sur `/q/dev-ui` en mode développement, est la vitrine du royaume : chaque extension y dispose d'une *card*, et rien n'empêche la nôtre d'y afficher sa propre page.

Il faut d'abord une dépendance supplémentaire dans le module `deployment`, et seulement là — cette vitrine ne voyage jamais avec l'application finie :

```xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-devui-deployment-spi</artifactId>
</dependency>

```

Puis un troisième `@BuildStep`, qui produit un `CardPageBuildItem` :

```java
@BuildStep(onlyIf = IsLocalDevelopment.class)
CardPageBuildItem carteDeLaTaverne() {

    CardPageBuildItem card = new CardPageBuildItem();

    card.addPage(Page.webComponentPageBuilder()
            .title("La carte de la Taverne")
            .icon("font-awesome-solid:map")
            .componentLink("qwc-taverne-map.js"));

    return card;
}

```

Le `onlyIf = IsLocalDevelopment.class` n'est pas un détail : sans lui, ce coup de marteau s'exécuterait aussi lors d'un build de production, pour une vitrine que personne n'ouvrira jamais.

Reste le composant lui-même. Il vit dans le module `deployment`, sous `src/main/resources/dev-ui/`, et le nom du fichier doit correspondre exactement au `componentLink()` déclaré plus haut. La Dev UI est bâtie sur Lit, et fournit `lit` à l'import sans qu'on ait un seul `package.json` à écrire :

```js
import { LitElement, html } from 'lit';

export class TaverneMap extends LitElement {

    render() {
        return html`
            <div>
                <h1>La carte de la Taverne</h1>
                <p>Bienvenue à The Falling Whale.</p>
            </div>
        `;
    }
}

customElements.define('qwc-taverne-map', TaverneMap);

```

Le préfixe `qwc-` (pour *Quarkus Web Component*) est la convention de nommage de la Dev UI, et le nom passé à `customElements.define()` doit être unique dans toute la page : c'est là que se rejoignent toutes les extensions du royaume.

> **Encart du forgeron** — C'est ici que la descente m'a coûté le plus cher, et pour une raison qu'aucun log ne m'a soufflée. Ma card n'apparaissait pas. Pire : la page Extensions de la Dev UI était entièrement vide, cards `cdi` et `rest` comprises. Le coupable était dans la console du navigateur :  
>  
> `text TypeError: Cannot use 'in' operator to search for 'strTag' in null at qwc-extensions.js:298 `  
>  
> La Dev UI rend la description de chaque extension via `msg()`, la fonction de traduction de Lit Localize, qui commence par tester `'strTag' in template`. Mon `runtime/pom.xml` n'avait pas de `<description>`, donc le descripteur de l'extension non plus, donc la Dev UI recevait `null`, donc `'strTag' in null` levait une `TypeError` qui tuait le rendu de **toutes** les cards d'un coup.  
>  
> Deux leçons. La première : un `<description>` absent est parfaitement légal dans un descripteur d'extension, mais la Dev UI ne le supporte pas — renseigne-le. La seconde, plus générale : quand une page de la Dev UI est vide, le problème n'est pas forcément dans ton extension. Ouvre la console du navigateur avant de suspecter ton `@BuildStep`.

Et si la card reste absente alors que la page s'affiche normalement, c'est l'autre bout du fil qu'il faut tirer : celui du nommage des modules, tout en haut de cette descente. Une card rattachée à un artifactId qui ne correspond à aucune extension déclarée n'a nulle part où s'afficher.

![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/2026/08/image-10.png)

Notre artefact présent dans la dev-ui

## Intégration dans un projet Quarkus

Du point de vue d'un service quelconque du royaume, toute cette forge disparaît complètement. Ajouter la dépendance suffit :

```xml
<dependency>
    <groupId>fr.eletutour</groupId>
    <artifactId>quarkus-taverne</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

```

Seul le module `runtime` est déclaré. Le module `deployment` n'apparaît nulle part : Quarkus le récupère tout seul, en lisant le `deployment-artifact` du descripteur généré plus haut.

Et poser la relique sur n'importe quelle méthode, sans jamais avoir entendu parler de `BuildStep` ni d'ArC :

```java
@Path("/quete")
@ApplicationScoped
public class QuestResource {

    @GET
    @Taverne(quete = "Explorer le donjon de la Taverne")
    public String explorer() {
        return "Le héros trouve un coffre. Il contient... un README.";
    }
}

```

![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/2026/08/image-11.png)

Au démarrage, on voit bien notre relique ajoutée à la liste des features.

Un simple `curl` sur cet endpoint, et la console du service annonce, d'une voix de barde : « Que l'on m'entende ! La quête « Explorer le donjon de la Taverne » commence ! ». Chaque service du royaume peut désormais invoquer la magie de la Taverne, sans savoir qu'il existe une forge sous les fondations.

```log
2026-08-12 10:42:55,095 INFO  [la-taverne] (executor-thread-1) [la-taverne] Que l'on m'entende ! La quête « Explorer le donjon de la Taverne » commence !
2026-08-12 10:42:55,098 INFO  [la-taverne] (executor-thread-1) [la-taverne] Explorer le donjon de la Taverne : quête accomplie.
```

## Aller plus loin

Quelques pistes pour qui voudrait pousser sa relique plus loin sans attendre un futur chapitre : la card de la Dev UI peut faire bien plus qu'afficher une page statique. En dialoguant avec le build ou le runtime via JSON-RPC, elle peut exposer ses propres métriques et son propre panneau de configuration — c'est exactement ce que fait la card d'ArC pour compter les graines, les observateurs et les intercepteurs du royaume.

La documentation de la configuration, elle, se génère automatiquement à partir des annotations `@ConfigMapping`, à condition de soigner les commentaires Javadoc sur chaque méthode de l'interface : c'est ce texte-là qui finit dans les fichiers de référence générés au build.

## Vers la Guilde : Quarkiverse

Une relique forgée dans l'ombre d'une seule taverne reste une relique isolée. Quarkiverse est la guilde des forgerons de tout l'écosystème Quarkus : un espace où une extension peut être présentée aux pairs, revue, certifiée, puis mise à disposition de tous les aventuriers du royaume, bien au-delà des murs de The Falling Whale.

Publier sur Quarkiverse dépasse le cadre de cette descente dans la forge : ça implique des conventions de nommage, un processus de revue par la communauté, un engagement de maintenance dans la durée. Mais l'objectif final d'une relique bien forgée n'est jamais de rester enfermée dans une cave. Peut-être qu'un jour, la nôtre rejoindra la guilde. Ce sera l'histoire d'un autre chapitre.

[https://hub.quarkiverse.io/](https://hub.quarkiverse.io/?ref=sfeir.dev)

## Conclusion : l'artefact est forgé

Le tavernier remonte de la forge, une relique brillante entre les mains. Elle n'a rien d'imposant, une simple annotation, un intercepteur, trois coups de marteau dans un processeur. Mais elle porte en elle tout ce qu'il a fallu comprendre pour exister : la différence entre ce qui s'exécute au build et ce qui tourne au runtime, la manière dont un jar isolé se fait connaître d'un royaume entier, et cette collection de pièges qui n'en sont pas vraiment — un `@Nonbinding` oublié, un module mal nommé, un `<description>` manquant, un annotation processor absent. Aucun d'eux ne lève d'exception. Tous les quatre suffisent, seuls, à rendre la relique muette.

« Nous avons forgé notre propre magie, dit le tavernier en posant la relique sur le comptoir. Le royaume peut maintenant s'étendre. »

Il ne le dit pas à voix haute, mais il sait qu'une bonne partie de cette magie a d'abord échoué en silence, avant de fonctionner. C'est peut-être la leçon la plus utile de toute cette descente : une extension Quarkus ne prévient presque jamais quand elle rate. Elle se contente de ne rien faire, poliment, jusqu'à ce qu'on aille chercher pourquoi.

---

Tout le code relatif à cet article est trouvable ici :

[GitHub - ErwanLT/quarkus-demo: Demo project for quarkus possibilityDemo project for quarkus possibility. Contribute to ErwanLT/quarkus-demo development by creating an account on GitHub.![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/favicon-0427c343-01ef-40d9-be86-bb6ae05a2892.png)GitHubErwanLT![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/5635256a-85fe-4dc6-8c3a-7e4df754b269-b2e853bd-f9e2-4185-a970-405283e1d7ea)](https://github.com/ErwanLT/quarkus-demo?ref=sfeir.dev)