GraalVM is becoming an increasingly popular target to deploy Apache CXF services. The most challenging deployments are the ones which bundle Apache CXF services and applications as native images. There are some limitations with that and there are certain improvements incorporated into Apache CXF recently to provide the way to overcome those.

JAX-RS Support

The Apache CXF's JAX-RS implementation does not rely on code generation and dynamic class loading at runtime. In many cases, producing GraalVM's native images is straightforward, assuming the benefits of the assisted configuration of native image builds could be taken of.

You may run into dynamic class generation and/or loading in a few cases:

  • when CDI (or other kinds of runtime instrumentation) is involved

Samples

JAX-WS Support

For many JAX-WS services which rely on Apache CXF and use its code-generation capabilities, producing GraalVM's native images is straightforward, assuming the benefits of the assisted configuration of native image builds could be taken of.

Dynamic Servers / Clients

In certain scenarios Apache CXF does aggressive code generation and dynamic class loading at runtime. This violates one of the GraalVM's ahead-of-time compilation limitations which states that "... all classes and all bytecodes that are reachable at run time must be known at build time". Since 3.3.9 / 3.4.2 / 3.5.x there is a way to capture all dynamically generated classes in order to include them into the native image at build time and then use class loading (instead of class generation) at runtime. It eliminates the need for dynamic class generation and loading. The capturing capability is provided by GeneratedClassClassLoaderCapture extension (shown below).

public interface GeneratedClassClassLoaderCapture {
    void capture(String className, byte[] bytes);
}

The example of capturing class loader service may look like this:

public class DumpingClassLoaderCapturer implements GeneratedClassClassLoaderCapture {
    private final Map<String, byte[]> classes = new ConcurrentHashMap<>();
    
    public void dumpTo(File file) throws IOException {
        if (!file.exists() || !file.isDirectory()) {
            throw new IllegalArgumentException("The dump location does not exist or is not a directory: " + file);
        }
        
        for (Map.Entry<String, byte[]> entry: classes.entrySet()) {
            final Path path = file.toPath().resolve(StringUtils.periodToSlashes(entry.getKey()) + ".class");
            Files.createDirectories(path.getParent());
            
            try (OutputStream out = Files.newOutputStream(path, StandardOpenOption.CREATE)) {
                out.write(entry.getValue());
            }
        }
    }

    @Override
    public void capture(String className, byte[] bytes) {
        classes.putIfAbsent(className, bytes);
    }
}

The stored generated classes should be injected at build time, whereas the following extensions replace dynamic class generation with class loading of the captured (generated) classes at runtime:

Here is the programmatic way to configure these extensions:

 final Bus bus = ...; /* Bus instance */
 bus.setExtension(new WrapperHelperClassLoader(bus), WrapperHelperCreator.class);
 bus.setExtension(new ExtensionClassLoader(bus), ExtensionClassCreator.class);
 bus.setExtension(new ExceptionClassLoader(bus), ExceptionClassCreator.class);
 bus.setExtension(new WrapperClassLoader(bus), WrapperClassCreator.class);
 bus.setExtension(new FactoryClassLoader(bus), FactoryClassCreator.class);
 bus.setExtension(new GeneratedNamespaceClassLoader(bus), NamespaceClassCreator.class);

You may run into dynamic class generation and/or loading in a few cases:

  • when using JaxWsDynamicClientFactory (fully dynamic clients)
  • when using request / response wrappers and fault exception wrappers (which may not generated at build time)
  • when CDI (or other kinds of runtime instrumentation) is involved

Samples