Skip to content

palantir/conjure-java

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Autorelease

Conjure-Java License

CLI to generate Java POJOs and interfaces from Conjure API definitions.

Note that all Java APIs in this repository (including conjure-java-core) are considered "internal" in the sense that they may change at any time and without prior warning or deprecation notice. These packages are published for convenience, but users must assume responsibility for adapting their code when dependencies change.

Usage

The recommended way to use conjure-java is via a build tool like gradle-conjure. However, if you don't want to use gradle-conjure, there is also an executable which conforms to RFC 002, published on maven central.

Usage: conjure-java generate [...options] <input> <output>

Generate Java bindings for a Conjure API
    <input>      Path to the input IR file
    <output>     Output directory for generated source

Options:
    --objects    Generate POJOs for Conjure type definitions
    --jersey     Generate jax-rs annotated interfaces for client or server-usage
    --undertow   Generate undertow handlers and interfaces for server-usage
    --requireNotNullAuthAndBodyParams
                 Generate @NotNull annotations for AuthHeaders and request body params
    --undertowServicePrefixes
                 Generate service interfaces for Undertow with class names prefixed 'Undertow'
    --undertowListenableFutures
                 Generate Undertow services which return Guava ListenableFuture for asynchronous processing
    --useImmutableBytes
                 Generate binary fields using the immutable 'Bytes' type instead of 'ByteBuffer'
    --strictObjects
                 Generate POJOs that by default will fail to deserialize unknown fields
    --nonNullCollections
                 Generate POJOs that by default will fail to deserialize collections with null values
    --useStagedBuilders
                 Generates compile-time safe builders to ensure all attributes are set, except those of type list,
                 set, map, or optional. If --useStrictStagedBuilders is set, this option is ignored.
    --useStrictStagedBuilders
                 Generates compile-time safe builders to ensure all attributes are set.
    --jakartaPackages
                 Generates jax-rs annotated interfaces which use the newer 'jakarta` packages instead of the
                 legacy 'javax' packages.
    --externalFallbackTypes
                 Java external type imports are generated using their fallback type.
    --excludeDialogueAsyncInterfaces
                 Exclude the generation of asynchronous interfaces for Dialogue clients.

Known Tag Values

Endpoint Tags

  • incubating: Describes an endpoint as incubating and likely to change. These endpoints are generated with an @Incubating annotation.
  • server-request-context: Opt into an additional RequestContext parameter in conjure-undertow interfaces, which allows request metadata to be read, and additional arguments to be associated with the request log.
  • server-async: Opt into asynchronous request processing in conjure-undertow. The generated interface returns a ListenableFuture of the defined return type, allowing processing to occur in the background without blocking the request thread.
  • deprecated-for-removal: For endpoint definitions that are marked deprecated, indicates that the endpoint will be removed in a future release. This has the effect of Java code being generated with an @Deprecated(forRemoval = true) annotation on the endpoint method in the Dialogue service interface.

Endpoint Argument Tags

  • server-squelch: Opts out of attaching safe non-body parameters to the request. By default, all known log-safe non-body inputs are included.

The following argument tags are deprecated, replaced by safety declarations.

  • safe: Annotates parameters as @Safe to log using safe-logging annotations. Implementations may add this data to the request log.
  • unsafe: Annotates parameters as @Unsafe to log using safe-logging annotations. Implementations may add this data to the request log.

Feature Flags

Conjure-java supports feature flags to enable additional opt-in features. To enable features provided by a feature flag, simply supply the feature flag as an additional parameter when executing conjure-java. Available feature flags can be found in the Options class.

Example generated objects

Conjure-java objects are always immutable and thread-safe. Fields are never null; instead, Java 8 Optionals are used. JSON serialization is handled using Jackson annotations.

  • Conjure object: ManyFieldExample

    Objects can only be instantiated using the builder pattern:

    ManyFieldExample example = ManyFieldExample.builder()
            .string("foo")
            .integer(123)
            .optionalItem("bar")
            .addAllItems(iterable)
            .build();

    Or using Jackson via conjure-jackson-serialization:

    ObjectMapper mapper = ObjectMappers.newServerObjectMapper();
    ManyFieldExample fromJson = mapper.readValue("{\"string\":\"foo\", ...}", ManyFieldExample.class);
  • Conjure union: UnionTypeExample

    Union types can be one of a few variants. To interact with a union value, users should use the .accept method and define a Visitor that handles each of the possible variants, including the possibility of an unknown variant.

    Foo output = unionTypeExample.accept(new Visitor<Foo>() {
    
        public Foo visitStringExample(StringExample value) {
            // your logic here!
        }
    
        public Foo visitSet(Set<String> value) {}
    
        // ...
    
        public Foo visitUnknown(String unknownType) {}
    
    });

    Visitors may seem clunky in Java, but they have the upside of compile-time assurance that you've handled all the possible variants. If you upgrade an API dependency and the API author added a new variant, the Java compiler will force you to explicitly deal with this new variant. We intentionally avoid switch statements and instanceof checks for this exact reason.

    There is also a more concise visitor builders pattern that can be used to create a visitor:

    Foo output = unionTypeExample.accept(Visitor.<Foo>builder()
        .stringExample(value -> ...)
        .set(value -> ...)
        .throwOnUnknown()
        .build());
    });
  • Conjure enum: EnumExample

    Enums are subtly different from regular Java enums because they tolerate deserializing unknown values. This is important because it ensures server authors can add new variants to an enum without causing runtime errors for consumers that use older API jars.

    EnumExample one = EnumExample.ONE;
    System.out.println(one); // prints: 'ONE'
    
    EnumExample fromJson = mapper.readValue("\"XYZ\"", EnumExample.class);
    System.out.println(fromJson); // prints: 'XYZ'
  • Conjure alias: StringAliasExample

    Aliases have exactly the same JSON representation as their inner type, so they are useful for making error-prone function signatures more bulletproof:

    -doSomething(String, String, String);
    +doSomething(ProductId, UserId, EmailAddress);

Example Jersey interfaces

Conjure-java generates Java interfaces with JAX-RS annotations, so they can be used on the client side or on the server-side.

Example jersey interface: EteService

@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/")
@Generated("com.palantir.conjure.java.services.JerseyServiceGenerator")
public interface EteService {
    @GET
    @Path("base/string")
    String string(@HeaderParam("Authorization") AuthHeader authHeader);
}

Jersey clients

Use conjure-java-runtime's JaxRsClient which configures Feign with sensible defaults:

RecipeBookService recipes = JaxRsClient.create(
        RecipeBookService.class,
        userAgent,
        hostMetrics,
        clientConfiguration);

List<Recipe> results = recipes.getRecipes();

Jersey servers

You need a Jersey-compatible server. We recommend Dropwizard (which is based on Jetty), but Grizzly, Tomcat, etc should also work fine. Use conjure-java-runtime's ConjureJerseyFeature to configure Jackson and Exception mappers appropriately. Then, you just need to implement the interface:

public final class RecipeBookResource implements RecipeBookService {

    @Override
    public List<Recipe> getRecipes() {
        // ...
    }
}

Then in your server main method, register your resource. For example, using Dropwizard:

 public void run(YourConfiguration configuration, Environment environment) {
     // ...
+    environment.jersey().register(new RecipeBookResource());
 }

Undertow

In the undertow setting, for a ServiceName conjure defined service, conjure will generate an interface: ServiceName to be extended by your resource and an UndertowService named ServiceNameEndpoints

To avoid conflicts with the equivalent jersey interface (when you are generating both), use in your build.gradle:

conjure {
    java {
        undertowServicePrefixes = true
    }
}

With this option, Undertow generated interfaces will be prefixed with Undertow. Here the interface would be called: UndertowServiceName

To use the generated handlers:

public static void main(String[] args) {
    Undertow server = Undertow.builder()
            .addHttpListener(8080, "0.0.0.0")
            .setHandler(ConjureHandler.builder()
                    .services(RecipeBookServiceEndpoints.of(new RecipeBookResource()))
                    .build())
            .build();
    server.start();
}

Asynchronous Request Processing

The Conjure Undertow generator supports asynchronous request processing allowing all service methods to return a Guava ListenableFuture. These methods may be implemented synchronously by replacing return object with return Futures.immediateFuture(object).

Asynchronous request processing decouples the HTTP request lifecycle from server task threads, allowing you to replace waiting on shared resources with callbacks, reducing the number of required threads.

This feature is enabled on individual endpoints using the server-async endpoint tag:

getEvents:
  http: GET /events
  returns: list<Event>
  tags:
    - server-async

Or for all endpoints using the undertowListenableFutures generator flag:

conjure {
    java {
        undertowListenableFutures = true
    }
}

Timeouts

By default, asynchronous request processing imposes a 3-minute timeout on the asynchronous component of the request, canceling the return future after this duration is exceeded. A custom duration may be used by extending the tag value using the form server-async{timeout=5 minutes}.

Examples

Asynchronous request processing is helpful for endpoints which do not need a thread for the entirety of the request.

πŸ‘ Delegation to an asynchronous client, for instance dialogue πŸ‘

@Override
public ListenableFuture<String> getValue() {
    // Assuming this dialogue client was compiled with --undertowListenableFutures
    return dialogueClient.getValue();
}

πŸ‘ Long polling πŸ‘

Long polling provides lower latency than simple repeated polling, but requests take a long time relative to computation. A single thread can often handle updating all polling requests without blocking N request threads waiting for results.

@Override
public ListenableFuture<String> getValue() {
    SettableFuture<String> result = SettableFuture.create();
    registerFuture(result);
    return result;
}

πŸ‘Ž Not for delegation to synchronous operations, Feign clients for example πŸ‘Ž

This example is less efficient than return Futures.immediateFuture(feignClient.getValue()) because you pay an additional cost to switch threads, and maintain an additional executor beyond the configured server thread pool.

ListeningExecutor executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

@Override
public ListenableFuture<String> getValue() {
    // BAD: do not do this!
    return executor.submit(() -> feignClient.getValue());
}

πŸ‘Ž Not waiting on another thread πŸ‘Ž

This is even less efficient than the previous example because it requires two entire threads for the duration of the request. It's reasonable to defer computation to an executor bounded to the work, but you should wrap the executor with MoreExecutors.listeningDecorator and return the future to avoid blocking a server worker thread for both the queued and execution durations of the task.

ExecutorService executor = Executors.newFixedThreadPool(CORES);

@Override
public ListenableFuture<BigInteger> getValue() {
    // BAD: do not do this!
    Future<BigInteger> future = executor.submit(() -> complexComputation());
    BigInteger result = Uninterruptibles.getUninterruptibly(future);
    return Futures.immediateFuture(result);
}

Request Metadata

The RequestContext may be requested on an opt-in basis per-endpoint using the server-request-context conjure endpoint tag:

services:
  ExampleService:
    name: Example
    package: com.palantir.example
    base-path: /example

    endpoints:
      context:
        http: GET /ping
        returns: string
        tags: [server-request-context]

This tag generates an additional parameter in the generated service interface:

public interface ExampleService {
  String ping(RequestContext context);
}

Implementations may read arbitrary headers and query parameters, or associate additional data with the request log:

import java.util.concurrent.atomic.AtomicInteger;

public final class ExampleResource implements ExampleService {
  private final AtomicInteger requestIndex = new AtomicInteger();

  @Override
  public String ping(RequestContext context) {
    // Apply a unique index value to the request log:
    context.requestArg(SafeArg.of("requestNumber", requestIndex.getAndIncrement()));
    // Read the User-Agent header from the current request:
    String userAgent = context.firstHeader("User-Agent");
    // And add it to the request log:
    context.requestArg(SafeArg.of("userAgent", userAgent));
    return "pong";
  }
}

Conjure-Undertow Annotation Processor

The conjure-undertow annotation processor generates Undertow handler for services that can not easily be represented using Conjure. You can find more details in the respective readme.

conjure-lib Bytes class

By default, conjure-java will use java.nio.ByteByffer to represent fields of Conjure type binary. However, the ByteBuffer class has many subtleties, including interior mutability.

To avoid many of these foot-guns, we recommend using the useImmutableBytes feature flag to opt-in to using the com.palantir.conjure.java.lib.Bytes class. This will become the default behaviour in a future major release of conjure-java.

Contributing

For instructions on how to set up your local development environment, check out the Contributing document.

License

This project is made available under the Apache 2.0 License.