Skip to main content

REST Proxies

One of the most powerful features of the REST client class is the ability to turn a plain Java interface into a fully-working client for a 3rd-party REST resource.

See the guide: Call an existing REST API with a typed proxy

You describe the API with a Java interface and a handful of annotations — the HTTP method, the path, which arguments become headers, query parameters, form data, or the request body, and what the response should be parsed into. RestClient then hands you back a dynamic proxy that implements the interface, translating every method call into an HTTP request and every response back into a Java return value.

The methods used to retrieve remote interfaces are:

RestClientgetRemote(Class)getRemote(Class,Object)getRemote(Class,Object,Serializer,Parser)

Quick Start​

Define an interface, annotate it, and get a proxy from a RestClient:

Example
@Remote(path="/petstore")
public interface PetStore {

@RemotePost("/pets")
Pet addPet(
@Content CreatePet createPet,
@Header("E-Tag") UUID etag,
@Query("debug") boolean debug
);

@RemoteGet("/pets/{petId}")
Pet getPet(@Path("petId") int petId);
}
// Use a RestClient with default JSON 5 support.
RestClient client = RestClient.create().json5().build();

// Instantiate our proxy interface.
PetStore store = client.getRemote(PetStore.class, "http://localhost:10000");

// Use it to create a pet...
CreatePet createPet = new CreatePet("Fluffy", 9.99);
Pet pet = store.addPet(createPet, UUID.randomUUID(), true);

// ...and fetch it back.
Pet sameCat = store.getPet(pet.getId());

The addPet(...) call above translates to a real HTTP request under the hood:

POST http://localhost:10000/petstore/pets HTTP/1.1
Accept: application/json
Content-Type: application/json
E-Tag: 475588d4-0b27-4f56-9296-cc683251d314

{
name: 'Fluffy',
price: 9.99
}

No hand-written URL building, serialization, or response parsing required — the interface is the contract.

See Also

The @RemoteOp annotation (and its method/path) can often be skipped entirely if your Java method names follow a simple naming convention. See @RemoteOp for the inference rules.

Annotation Reference​

Method behavior is driven entirely by annotations on the interface, its methods, and their parameters:

AnnotationApplies toPurpose
@RemoteInterfaceMarks the interface as a REST proxy; sets the base path, common headers, and client versioning.
@RemoteOp (and @RemoteGet/@RemotePost/@RemotePut/@RemotePatch/@RemoteDelete)MethodMaps a method to an HTTP verb + path. Often optional if the method name implies the verb.
@PathParameterURL path variables ({petId}-style placeholders).
@QueryParameterQuery-string parameters.
@HeaderParameterHTTP request headers — including dynamic, multi-value forms.
@FormDataParameterapplication/x-www-form-urlencoded body parameters.
@ContentParameterThe HTTP request body.
@RequestParameter or classBundles multiple parts (content/headers/query/etc.) behind a single request bean.
@ResponseReturn typeBundles content/headers/status behind a single response bean.

Every one of these supports both single-value forms (@Query("limit") int limit) and dynamic, multi-value forms (@Query Map<String,Object> params) — see the dynamic headers example below for the pattern in detail.

Capabilities Showcase​

The sections below are short teasers of what's possible. Each links to a deep-dive page with the full set of supported types and edge cases.

Sending data to the server​

Path variables (@Path) fill in {placeholders} in the URL:

@RemoteGet("/pets/{petId}")
Pet getPet(@Path("petId") int petId);

Query parameters (@Query) — and the equivalent for @FormData form posts:

@RemoteGet("/pets")
Pet[] findPets(@Query("species") String species, @Query("limit") int limit);

Dynamic and multiple headers​

@Header (and @Query/@FormData/@Path) isn't limited to one header per parameter. Leave the name off (or use the explicit wildcard "*") and pass a Map, a HeaderList, or a bean — every entry or property is expanded into its own header:

@Remote(path="/petstore")
public interface PetStore {

// Every map entry becomes its own header.
@RemoteGet("/pets")
Pet[] getPets(@Header("*") Map<String,Object> headers);

// Same idea via a pre-built HeaderList...
@RemoteGet("/pets")
Pet[] getPetsViaHeaderList(@Header HeaderList headers);

// ...or a bean whose properties become individual headers.
@RemoteGet("/pets")
Pet[] getPetsViaBean(@Header PetHeaders headers);
}
Map<String,Object> headers = JsonMap.of("X-Client-Id", "acme", "X-Trace-Id", UUID.randomUUID());
Pet[] pets = store.getPets(headers);

This is a great way to forward a variable, caller-supplied set of headers (correlation IDs, feature flags, tenant context, etc.) without adding a new interface parameter every time one shows up. See @Header for the full list of supported dynamic forms.

Request bodies (@Content) accept any serializable POJO, or raw Reader/InputStream:

@RemotePost("/pets")
Pet addPet(@Content CreatePet pet);

Request beans (@Request) bundle several of the above behind one argument, so callers pass a single object instead of a long parameter list:

@RemotePost
String postPet(CreatePetRequest bean);
@Request
public class CreatePetRequest {

@Content
public CreatePet getContent() { ... }

@Query
public Map<String,Object> getQueryParams() { ... }

@Header("E-Tag")
public UUID getUUID() { ... }
}

Getting results back​

Return types are flexible — parsed POJOs, raw streams, async wrappers, or just the status code:

@Remote(path="/petstore")
public interface PetStore {

@RemoteGet("/pets") Pet[] getPets(); // Parsed POJO
@RemoteGet(path="/pets", returns=STATUS) int getPetsStatus(); // Just the HTTP status
@RemoteGet("/pets") Future<Pet[]> getPetsAsync(); // Async
@RemoteGet("/pets/{petId}") InputStream getPetRaw(@Path("petId") int petId); // Raw stream
}

See @RemoteOp for the complete list of supported return types.

Response beans (@Response) do the same thing in reverse — bundle the body, headers, and status code of a response behind one typed object:

@RemotePost
CreatePetResponse postPet(@Content CreatePet pet);
@Response
public interface CreatePetResponse {

@Content Pet getContent();
@Header("E-Tag") UUID getUUID();
@StatusCode int getStatus();
}

Error handling happens automatically through checked exceptions. Declare a throws clause using any of the roughly 60 predefined HTTP response exceptions (or your own), and Juneau throws the matching one when the server returns that status code:

@RemoteGet("/pets/{petId}")
Pet getPet(@Path("petId") int petId) throws NotFound;
try {
Pet pet = store.getPet(999);
} catch (NotFound e) {
// HTTP 404 was returned - Juneau matched the status to the declared exception type.
}

Power-User Patterns​

Default Values​

Default values let you specify fallbacks for null arguments right on the annotation, so callers don't have to pass every parameter every time:

@RemoteGet("/pets")
Pet[] getPets(
@Header(name="Accept-Language", def="en-US") String language,
@Query(name="limit", def="10") Integer limit
);
// Uses default language="en-US" and limit=10.
Pet[] pets = store.getPets(null, null);

def is supported on @Header, @Query, @FormData, @Path, and @Content, at either the parameter or method level (parameter wins when both are set).

Dual-Purpose Interfaces​

The same Java interface can define both the client proxy and the server implementation, so a signature change is caught by the compiler on both sides instead of drifting apart at runtime:

@Remote(path="/petstore")
public interface PetStore {
@RemoteGet("/pet")
Collection<Pet> getPets() throws NotAcceptable;
}
@Rest(path="/petstore")
public class PetStoreResource extends BasicRestServlet implements PetStore {
@Override
@RestOp(method=GET, path="/pet")
public Collection<Pet> getPets() throws NotAcceptable {
return store.getPets();
}
}

See Dual-purpose (end-to-end) interfaces for the full walkthrough, including how parameter-level annotations like @Header and @Path are inherited from the interface for free.

See Also
  • @Remote - Interface-level configuration: base path, common headers, versioning.
  • @RemoteOp - HTTP method/path mapping, naming-convention inference, return types.
  • @Content - The request body.
  • @FormData - Form post parameters.
  • @Query - Query-string parameters.
  • @Header - Request headers, including dynamic/multi-value forms.
  • @Path - URL path variables.
  • @Request - Bean-style bundling of request parts.
  • @Response - Bean-style bundling of response parts.
  • Dual-purpose (end-to-end) interfaces - Sharing one interface between client and server.