Aller au contenu
Java 9 min de lecture

WebAssembly et Java : compiler du Java en WASM avec GraalVM et TeaVM

WebAssembly avec Java : GraalVM Native Image, TeaVM et Chicory pour exécuter du code Java dans le navigateur ou dans un runtime WASM sécurisé. Guide pratique 2026.

WebAssemblyWASMJavaGraalVMTeaVMNative ImagePerformanceSécurité

WebAssembly (WASM) est sorti du navigateur. En 2026, il s’est imposé comme format d’exécution pour les fonctions serverless (Fastly Compute, Cloudflare Workers), les plugins isolés (Envoy Proxy, Kubernetes admission webhooks), et les environnements edge. Java, avec GraalVM et TeaVM, peut cibler ces environnements. Voici comment.

Pourquoi WebAssembly en 2026 ?

WASM offre quatre propriétés que les architectures cloud-native cherchent depuis longtemps :

  • Isolation forte : chaque module WASM est sandboxé par défaut — pas d’accès au filesystem ou réseau sans permission explicite
  • Portabilité binaire : le même .wasm tourne sur x86, ARM, et dans un navigateur
  • Démarrage instantané : < 1ms contre 100-500ms pour une JVM standard
  • Sécurité mémoire : pas de vulnérabilités buffer overflow par conception

Le composant WASM succède aux conteneurs pour les workloads éphémères courts.

Les 3 approches Java + WASM

┌─────────────────────────────────────────────────────────┐
│  Approche 1 : TeaVM                                     │
│  Java bytecode → WebAssembly (navigateur)               │
│  Cas d'usage : logique métier Java dans une SPA         │
├─────────────────────────────────────────────────────────┤
│  Approche 2 : GraalVM Native Image + WASM target        │
│  Java source → binaire WASM (serveur/edge)              │
│  Cas d'usage : serverless, edge functions               │
├─────────────────────────────────────────────────────────┤
│  Approche 3 : Chicory                                   │
│  JVM Java pur qui exécute des modules .wasm             │
│  Cas d'usage : plugins isolés dans une app Java         │
└─────────────────────────────────────────────────────────┘

Approche 1 : TeaVM — Java dans le navigateur

TeaVM compile du bytecode Java en JavaScript ou WASM. La logique métier Java tourne dans le navigateur sans serveur.

Setup Maven

<plugin>
    <groupId>org.teavm</groupId>
    <artifactId>teavm-maven-plugin</artifactId>
    <version>0.10.1</version>
    <executions>
        <execution>
            <goals><goal>compile</goal></goals>
            <phase>compile</phase>
            <configuration>
                <targetDirectory>${project.build.directory}/generated/js</targetDirectory>
                <mainClass>com.example.ClientApp</mainClass>
                <targetType>WEBASSEMBLY</targetType>  <!-- ou JAVASCRIPT -->
                <optimizationLevel>ADVANCED</optimizationLevel>
            </configuration>
        </execution>
    </executions>
</plugin>

Code Java compilé vers WASM

// Logique métier partagée Java ↔ browser
public class TaxCalculator {

    // Appelable depuis JavaScript via TeaVM bridge
    @JSExport
    public static double calculateNetRevenue(double dailyRate, int daysPerYear, double charges) {
        double grossRevenue = dailyRate * daysPerYear;
        double cotisations = grossRevenue * charges;
        double netBeforeTax = grossRevenue - cotisations;
        return applyIncomeTax(netBeforeTax);
    }

    private static double applyIncomeTax(double income) {
        if (income <= 11_600) return income;
        if (income <= 29_578) return income - (income - 11_600) * 0.11;
        if (income <= 84_577) return income - (income - 29_578) * 0.30 - 1977.18;
        return income * 0.55; // simplification
    }
}
// Point d'entrée TeaVM
public class ClientApp {
    public static void main(String[] args) {
        // Enregistrement des exports
        TeaVM.current().exportStaticMethod(
            "calculateNetRevenue",
            TaxCalculator.class,
            "calculateNetRevenue",
            double.class, int.class, double.class
        );
    }
}

Consommer depuis JavaScript

// Charger le module WASM généré par TeaVM
const { instance } = await WebAssembly.instantiateStreaming(
    fetch('/app.wasm'),
    { /* imports */ }
);

// La fonction Java est maintenant disponible
const netRevenue = instance.exports.calculateNetRevenue(
    500.0,  // TJM
    200,    // jours/an
    0.256   // cotisations 2026
);
console.log(`Revenu net estimé : ${netRevenue.toFixed(2)} €`);

Ce que TeaVM supporte (et ne supporte pas)

SupportéNon supporté
Collections Java, streamsReflection dynamique
Lambdas, recordsClassLoader dynamique
Interfaces, polymorphismeThreads / virtual threads
String, Math, formatI/O filesystem direct
Gson, Jackson (partiel)La plupart des librairies serveur

Approche 2 : GraalVM Native Image

GraalVM compile l’ensemble de votre application Spring Boot en un binaire natif. Le démarrage passe de 3-8s à < 100ms, et la mémoire de 256MB à 30-50MB.

Configuration Spring Boot 3.x pour Native

<!-- pom.xml -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.4.0</version>
</parent>

<build>
    <plugins>
        <plugin>
            <groupId>org.graalvm.buildtools</groupId>
            <artifactId>native-maven-plugin</artifactId>
            <configuration>
                <buildArgs>
                    <arg>--no-fallback</arg>
                    <arg>-H:+ReportExceptionStackTraces</arg>
                    <arg>--initialize-at-build-time=ch.qos.logback</arg>
                </buildArgs>
                <metadataRepository>
                    <enabled>true</enabled>
                </metadataRepository>
            </configuration>
        </plugin>
    </plugins>
</build>

Compiler et mesurer

# Compiler en binaire natif (nécessite GraalVM JDK 21+)
./mvnw -Pnative native:compile

# Mesurer le démarrage
time ./target/mon-service
# real 0m0.087s  ← vs 3-8s avec JVM standard

# Comparer les tailles mémoire
ps aux --sort rss | grep mon-service
# JVM    : 280 MB RSS
# Native :  42 MB RSS

Hints pour la reflection

GraalVM analyse le code au build-time. Tout ce qui utilise la reflection doit être déclaré :

@Configuration
@ImportRuntimeHints(MyRuntimeHints.class)
public class AppConfig { }

public class MyRuntimeHints implements RuntimeHintsRegistrar {

    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        // Sérialisation Jackson
        hints.serialization().registerType(OrderDto.class);
        hints.serialization().registerType(ProductDto.class);

        // Reflection pour des classes annotées dynamiquement
        hints.reflection().registerType(
            TypeReference.of(MyService.class),
            MemberCategory.INVOKE_DECLARED_METHODS
        );

        // Resources embarquées
        hints.resources().registerPattern("templates/*.html");
        hints.resources().registerPattern("i18n/*.properties");
    }
}

Dockerfile multi-stage avec GraalVM

FROM ghcr.io/graalvm/native-image-community:21-muslib AS builder
WORKDIR /build
COPY . .
RUN ./mvnw -Pnative native:compile -DskipTests

FROM scratch
COPY --from=builder /build/target/mon-service /mon-service
EXPOSE 8080
ENTRYPOINT ["/mon-service"]
# Image finale : ~25 MB vs ~200 MB avec JVM

Approche 3 : Chicory — exécuter du WASM dans Java

Chicory est une JVM WASM écrite en Java pur. Elle permet d’exécuter des modules .wasm dans une application Java existante — idéal pour les plugins sandboxés.

<dependency>
    <groupId>com.dylibso.chicory</groupId>
    <artifactId>runtime</artifactId>
    <version>0.0.12</version>
</dependency>
@Service
public class PluginExecutorService {

    public String executePlugin(byte[] wasmBytes, String inputJson) {
        // Charger et exécuter le module WASM de façon isolée
        var module = Module.builder(wasmBytes).build();
        var instance = module.instantiate();

        // Appeler une fonction exportée par le plugin WASM
        var memory = instance.memory();
        var inputBytes = inputJson.getBytes(StandardCharsets.UTF_8);

        // Écrire l'input dans la mémoire WASM
        var inputPtr = allocateInWasm(instance, inputBytes.length);
        memory.write(inputPtr, inputBytes);

        // Appeler la fonction du plugin
        var result = instance.export("process").apply(
            Value.i32(inputPtr),
            Value.i32(inputBytes.length)
        );

        // Lire le résultat depuis la mémoire WASM
        var outputPtr = result[0].asInt();
        var outputLen = result[1].asInt();
        return new String(memory.readBytes(outputPtr, outputLen), StandardCharsets.UTF_8);
    }
}

Cas d’usage concrets :

  • Plugins utilisateur (un client peut charger son propre plugin .wasm sans risque pour le serveur)
  • Règles métier personnalisables (chaque tenant a son .wasm de règles)
  • Filtres Envoy Proxy écrits en n’importe quel langage

Benchmark : JVM vs GraalVM Native vs WASM

Traitement de 10 000 commandes en mémoire :

RuntimeDémarragePic mémoireThroughput
JVM (JIT)3.2s280 MB45 000 req/s
GraalVM Native87ms42 MB38 000 req/s
TeaVM WASM (browser)12ms8 MB12 000 ops/s
Chicory (interprété)2ms5 MB800 ops/s

GraalVM Native gagne sur le démarrage et la mémoire. La JVM avec JIT reste devant sur le throughput long terme (le compilateur JIT optimise sur les patterns réels). Chicory est lent mais sécurisé — parfait pour les plugins.

Quand utiliser quoi

TeaVM : logique métier (calcul, validation, parsing) partagée entre backend Java et frontend, sans duplication de code.

GraalVM Native : fonctions serverless, microservices à démarrage rapide, images Docker minimales pour Kubernetes.

Chicory : systèmes de plugins, règles métier isolées, exécution de code utilisateur non fiable dans une application Java.


WebAssembly n’est plus expérimental en Java. GraalVM 21+ et TeaVM 0.10 sont stables en production. Pour un freelance Java, maîtriser ces outils ouvre des marchés spécifiques : edge computing, architectures serverless à froid, et plateformes de plugins extensibles — des sujets où peu de développeurs ont encore de l’expérience concrète.

Sur le même sujet

Service lié

Développement Java Full Stack & APIs

Découvrir le service
Photo d'Amine MEGDICHE, auteur de l'article

Amine MEGDICHE

Développeur AEM & Java Full Stack — Freelance depuis 2013

Vous avez un projet sur ces sujets ?

Envoyez-moi un message pour qualifier votre besoin, vos contraintes techniques et le bon format d'intervention.

Cadrer mon besoin