Chapter 2. Developing Jakarta RESTful Web Services Web Services Jakarta RESTful Web Services is the Jakarta EE API for RESTful web services. It provides support for building web services using representational state transfer, or "REST," using annotations. These annotations simplify the process of mapping Java objects to web resources. RESTEasy is the Red Hat JBoss Enterprise Application Platform 7 implementation of Jakarta RESTful Web Services. JBoss EAP 7.3 and later are compliant with the Jakarta Enterprise Web Services 1.4 and the Jakarta RESTful Web Services 2.1 Jakarta EE specifications. They also provide additional features to the specification. To get started with Jakarta RESTful Web Services, see the helloworld-rs , jaxrs-client , and kitchensink quickstarts that ship with Red Hat JBoss Enterprise Application Platform 7. Note JBoss EAP does not support the resteasy-crypto , resteasy-yaml-provider , and jose-jwt modules. 2.1. Jakarta RESTful Web Services Application When creating providers and web resources, you have the following options for declaring them: Simple subclassing of javax.ws.rs.core.Application without a web.xml file. Using a web.xml file. Subclassing javax.ws.rs.core.Application and providing a custom implementation. 2.1.1. Simple Subclassing javax.ws.rs.core.Application You can use the javax.ws.rs.core.Application class to create a subclass that declares those providers and web resources. This class is provided by the RESTEasy libraries included with JBoss EAP. To configure a resource or provider using javax.ws.rs.core.Application , simply create a class that extends it and add an @ApplicationPath annotation. Example: Application Class import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/root-path") public class MyApplication extends Application { } 2.1.2. Using web.xml Alternatively, if you do not want to create a class that extends javax.ws.rs.core.Application , you can add the following to your web.xml file. Example: web.xml <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <servlet> <servlet-name>javax.ws.rs.core.Application</servlet-name> </servlet> <servlet-mapping> <servlet-name>javax.ws.rs.core.Application</servlet-name> <url-pattern>/root-path/*</url-pattern> </servlet-mapping> ... </web-app> 2.1.3. Subclassing javax.ws.rs.core.Application with a Custom Implementation When subclassing javax.ws.rs.core.Application you can choose to provide a custom implementation for any of the existing methods. The getClasses and getSingletons methods return a collection of classes or singletons that must be included in the published Jakarta RESTful Web Services application. If either getClasses and getSingletons returns a non-empty collection, only those classes and singletons are published in the Jakarta RESTful Web Services application. If both getClasses and getSingletons return an empty collection, then all root resource classes and providers that are packaged in the web application are included in the Jakarta RESTful Web Services application. RESTEasy will then automatically discover those resources. 2.2. Jakarta RESTful Web Services Client 2.2.1. Jakarta RESTful Web Services Client API Jakarta RESTful Web Services 2.0 introduced a new client API to send HTTP requests to remote RESTful web services. It is a fluent request-building API with 3 main classes: Client WebTarget Response The Client interface is a builder of WebTarget instances. The WebTarget represents a distinct URL or URL template to build subresource WebTargets or invoke requests on. There are two ways to create a client: the standard way, or using the ResteasyClientBuilder class. The advantage of using the ResteasyClientBuilder class is that it provides a few more helper methods to configure your client. While the ResteasyClientBuilder class provides these helper methods, the class is specific to JBoss EAP APIs. If you want to migrate the application to a new server you must rebuild the application. The ResteasyClientBuilder class depends on RESTEasy and the class is not portable. The standard way of creating a client adheres to both the Jakarta RESTful Web Services and Jakarta EE API specifications, and it is portable among Jakarta RESTful Web Services implementations. Note To ensure Jakarta RESTful Web Services applications remain portable, adhere to the Jakarta EE API specification and, if possible, use Jakarta EE API applications. Only use APIs specific to JBoss if a use case does not support using Jakarta EE APIs. Following these guidelines can help reduce the number of issues that might occur when migrating an application to a different server or to a new Jakarta EE compatible JBoss implementation. Using the Standard Way to Create a Client The following example shows one of the standard ways to create a client: Client client = ClientBuilder.newClient(); Alternatively, you can use another standard way to create a client as shown in the example below: Client client = ClientBuilder.newBuilder().build(); WebTarget target = client.target("http://foo.com/resource"); Response response = target.request().get(); String value = response.readEntity(String.class); response.close(); // You should close connections! Using the ResteasyClientBuilder Class to Create a Client The following example shows use of the ResteasyClientBuilder class to create a client: ResteasyClient client = new ResteasyClientBuilder().build(); ResteasyWebTarget target = client.target("http://foo.com/resource"); With Jakarta RESTful Web Services 2.1, you can add two timeout methods to the ClientBuilder class. Timeout methods are specification-compliant methods, and you can use them instead of using the RESTEasy methods. The following ClientBuilder specification-compliant methods replace certain deprecated RESTEasy methods: The connectTimeout method replaces the establishConnectionTimeout method. The connectTimeout method determines how long the client must wait when making a new server connection. The readTimeout method replaces the socketTimeout method. The readTimeout method determines how long the client must wait for a response from the server. The following example shows specified values for the connectTimeout and readTimeout methods: import javx.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Client; Client client = ClientBuilder.newBuilder() .connectTimeout(100, TimeUnit.SECONDS) .readTimeout(2, TimeUnit.SECONDS) .build(); Note that readTimeout applies to an executed request on an already existing connection. Note Setting the value of the timeout parameter to zero causes the server to wait indefinitely. RESTEasy automatically loads a set of default providers that includes all classes listed in the META-INF/services/javax.ws.rs.ext.Providers file. Additionally, you can manually register other providers, filters, and interceptors through the configuration object provided by the method call Client.configuration() . Configuration also lets you set configuration properties that might be needed. Each WebTarget has a configuration instance that inherits the components and properties registered with the parent instance. This lets you set specific configuration options for each target resource, for example, the username and password. Additional Resources For more information about the ResteasyClientBuilder class and its methods, see the Class ResteasyClientBuilder . Using RESTEasy Client Classes You must add the following dependency for RESTEasy client to your Maven pom.xml file: <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> <version> VERSION_IN_EAP </version> </dependency> See the jaxrs-client and resteasy-jaxrs-client quickstarts that ship with JBoss EAP for working examples that use the RESTEasy client classes. Client-side Filters The client side has two types of filters: ClientRequestFilter A ClientRequestFilter runs before an HTTP request is sent over the wire to the server. The ClientRequestFilter is also allowed to abort the request execution and provide a canned response without going over the wire to the server. ClientResponseFilter A ClientResponseFilter runs after a response is received from the server, but before the response body is unmarshalled. The ClientResponseFilter can modify the response object before it is handed to the application code. The following example illustrates these concepts: // execute request filters for (ClientRequestFilter filter : requestFilters) { filter.filter(requestContext); if (isAborted(requestContext)) { return requestContext.getAbortedResponseObject(); } } // send request over the wire response = sendRequest(request); // execute response filters for (ClientResponseFilter filter : responseFilters) { filter.filter(requestContext, responseContext); } Register Client-side Filters to the Client Request The following example shows how to register the client-side filters to the client request: client = ClientBuilder.newClient(); WebTarget base = client.target(generateURL("/") + "get"); base.register(ClientExceptionsCustomClientResponseFilter.class).request("text/plain").get(); Client-side Cache RESTEasy has the ability to set up a client-side cache. This cache looks for cache-control headers sent back with a server response. If the cache-control headers specify that the client is allowed to cache the response, RESTEasy caches it within the local memory. ResteasyWebTarget target = client.target(generateBaseUrl()); target.register(BrowserCacheFeature.class); Chunked Encoding Support RESTEasy provides the client API the ability to specify that requests should be sent in a chunked transfer mode. There are two ways of specifying the chunked transfer mode, as shown below. You can configure the org.jboss.resteasy.client.jaxrs.ResteasyWebTarget to send all the requests in chunked mode: ResteasyClient client = new ResteasyClientBuilder().build(); ResteasyWebTarget target = client.target("http://localhost:8081/test"); target.setChunked(b.booleanValue()); Invocation.Builder request = target.request(); Alternatively, you can configure a particular request to be sent in chunked mode: ResteasyClient client = new ResteasyClientBuilder().build(); ResteasyWebTarget target = client.target("http://localhost:8081/test"); ClientInvocationBuilder request = (ClientInvocationBuilder) target.request(); request.setChunked(b); Unlike the javax.ws.rs.client.Invocation.Builder class, org.jboss.resteasy.client.jaxrs.internal.ClientInvocationBuilder is a RESTEasy class. Note The ability to send the requests in chunked mode depends on the underlying transport layer. In particular, it depends on the implementation of the org.jboss.resteasy.client.jaxrs.ClientHttpEngine class being used. Currently, only the default implementation ApacheHttpClient43Engine and the implementation ApacheHttpClient4Engine support the chunked mode. Both these are available in the org.jboss.resteasy.client.jaxrs.engines package. See section Implementing RESTEasy with HTTP Client for more information. 2.2.2. Implementing RESTEasy with HTTP Client Network communication between the client and server is handled by default in RESTEasy. It uses the HttpClient from the Apache HttpComponents project. The interface between the RESTEasy client framework and the network is defined by the ClientHttpEngine interface. RESTEasy ships with four implementations of this interface. The default implementation is ApacheHttpClient43Engine . This implementation uses Apache 4.3. ApacheHttpClient4Engine is an implementation that uses the versions earlier than Apache 4.3. This class provides backward compatibility. RESTEasy automatically selects one of these two ClientHttpEngine implementations based on the detection of the Apache version. InMemoryClientEngine is an implementation that dispatches requests to a server in the same JVM, and URLConnectionEngine is an implementation that uses java.net.HttpURLConnection . A client executor can be passed to a specific ClientRequest : ResteasyClient client = new ResteasyClientBuilder().httpEngine(engine).build(); RESTEasy and HttpClient make the default decisions to use the client framework without referencing HttpClient . However, for some applications it might be necessary to drill down into the HttpClient details. ApacheHttpClient43Engine and ApacheHttpClient4Engine can be supplied with an instance of org.apache.http.client.HttpClient and org.apache.http.protocol.HttpContext , which can carry additional configuration details into the HttpClient layer. For example, authentication can be configured as follows: // Configure HttpClient to authenticate preemptively // by prepopulating the authentication data cache. // 1. Create AuthCache instance AuthCache authCache = new BasicAuthCache(); // 2. Generate BASIC scheme object and add it to the local auth cache AuthScheme basicAuth = new BasicScheme(); authCache.put(new HttpHost("sippycups.bluemonkeydiamond.com"), basicAuth); // 3. Add AuthCache to the execution context BasicHttpContext localContext = new BasicHttpContext(); localContext.setAttribute(ClientContext.AUTH_CACHE, authCache); // 4. Create client executor and proxy HttpClient httpClient = HttpClientBuilder.create().build(); ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient, localContext); ResteasyClient client = new ResteasyClientBuilder().httpEngine(engine).build(); HttpContextProvider is an interface provided by RESTEasy, using which you can supply a custom HttpContext to the ApacheHttpClient43Engine and ApacheHttpClient4Engine implementations. Note It is important to understand the difference between releasing a connection and closing a connection. Releasing a connection makes it available for reuse. Closing a connection frees its resources and makes it unusable. RESTEasy releases the connection without notification. The only counter example is the case in which the response is an instance of InputStream , which must be closed explicitly. On the other hand, if the result of an invocation is an instance of Response , the Response.close() method must be used to release the connection. WebTarget target = client.target("http://localhost:8081/customer/123"); Response response = target.request().get(); System.out.println(response.getStatus()); response.close(); You may execute this in a try-finally block. Releasing a connection makes it available for another use. It does not normally close the socket. ApacheHttpClient4Engine.finalize() closes any open sockets, if it created the HttpClient that it has been using. It is not safe to rely on JDK to call finalize() . If an HttpClient is passed to the ApacheHttpClient4Executor , the user must close the connections, as shown below: HttpClient httpClient = new HttpClientBuilder.create().build(); ApacheHttpClient4Engine executor = new ApacheHttpClient4Engine(httpClient); ... httpClient.getConnectionManager().shutdown(); Note If ApacheHttpClient4Engine has created its own instance of HttpClient , it is not necessary to wait for finalize() to close open sockets. The ClientHttpEngine interface has a close() method for this purpose. Finally, if the javax.ws.rs.client.Client class has created the engine automatically, call Client.close() . This call cleans up any socket connections. 2.2.2.1. HTTP Redirect The ClientHttpEngine implementations based on the Apache HttpClient support HTTP redirection. This feaure is disabled by default. You can enable this by setting the setFollowRedirects method to true , as shown below: ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(); engine.setFollowRedirects(true); Client client = new ResteasyClientBuilder().httpEngine(engine).build(); 2.3. Jakarta RESTful Web Services Request Processing 2.3.1. Asynchronous HTTP Request Processing Asynchronous request processing allows you to process a single HTTP request using non-blocking input and output and, if required, in separate threads. Consider an AJAX chat client in which you want to push and pull from both the client and the server. This scenario has the client blocking for a long time on the server's socket, waiting for a new message. In case of synchronous HTTP processing, where the server is blocking on incoming and outgoing input and output, you have one separate thread consumed per client connection. This model of request processing consumes a lot of memory and valuable thread resources. Asynchronous processing separates the connection accepting and the request processing operations. It allocates two different threads: one to accept the client connection; the other to handle heavy, time-consuming operations. In this model, the container works as follows: It dispatches a thread to accept a client connection, which is the acceptor. Then it hands over the request to the processing thread, which is the worker. Finally, it releases the acceptor thread. The result is sent back to the client by the worker thread. Hence, the client's connection remains open, thereby improving the server's throughput and scalability. 2.3.1.1. Asynchronous NIO Request Processing RESTEasy's default asynchronous engine implementation class is ApacheHttpAsyncClient4Engine . It is built on HttpAsyncClient from the Apache HttpComponents , which internally dispatches requests using a non-blocking IO model. You can set the asynchronous engine as the active engine by calling the useAsyncHttpEngine method in the ResteasyClientBuilder class: Client asyncClient = new ResteasyClientBuilder().useAsyncHttpEngine() .build(); Future<Response> future = asyncClient .target("http://locahost:8080/test").request() .async().get(); Response res = future.get(); Assert.assertEquals(HttpResponseCodes.SC_OK, res.getStatus()); String entity = res.readEntity(String.class); 2.3.1.2. Server Asynchronous Response Processing On the server side, asynchronous processing involves suspending the original request thread and initiating the request processing on a different thread, which releases the original server-side thread to accept other incoming requests. 2.3.1.2.1. AsyncResponse API The Jakarta RESTful Web Services 2.1 specification defines asynchronous HTTP support using two classes: the @Suspended annotation and the AsyncResponse interface. Injecting an AsyncResponse as a parameter to your Jakarta RESTful Web Services method prompts RESTEasy to detach the HTTP request and response from the thread being executed currently. This ensures that the current thread does not try to automatically process the response. The AsyncResponse is the callback object. The act of calling one of the resume() methods causes a response to be sent back to the client and also terminates the HTTP request. The following is an example of asynchronous processing: import javax.ws.rs.container.Suspended; import javax.ws.rs.container.AsyncResponse; @Path("/") public class SimpleResource { @GET @Path("basic") @Produces("text/plain") public void getBasic(@Suspended final AsyncResponse response) throws Exception { Thread t = new Thread() { @Override public void run() { try { Response jaxrs = Response.ok("basic").type(MediaType.TEXT_PLAIN).build(); response.resume(jaxrs); } catch (Exception e) { e.printStackTrace(); } } }; t.start(); } } 2.3.1.3. AsyncInvoker Client API Similarly, on the client side, asynchronous processing prevents blocking the request thread since no time is spent waiting for a response from the server. For instance, a thread that issued a request may also update a user interface component. If that thread is blocked waiting for a response, the user's perceived performance of the application will suffer. 2.3.1.3.1. Using Future In the code snippet below, the get() method is called on the async() method rather than the request. This changes the call mechanism from synchronous to asynchronous. Instead of responding synchronously, the async() method returns a future object. When you call the get() method, the call is blocked until the response is ready. The future.get() method will be returned whenever the response is ready. import java.util.concurrent.Future; import javax.ws.rs.client.Client; ... @Test public void AsyncGetTest() throws Exception { Client client = ClientBuilder.newClient(); Future<String> future = client.target(generateURL("/test")).request().async().get(String.class); String entity = future.get(); Assert.assertEquals("get", entity); } 2.3.1.3.2. Using InvocationCallback The AsyncInvoker interface allows you to register an object that will be called back when the asynchronous invocation is ready for processing. The InvocationCallback interface provides two methods: completed() and failed() . The completed() method is called whenever the processing is finished successfully and the response is received. Conversely, the failed() method is called whenever the request processing is not successful. import javax.ws.rs.client.InvocationCallback; ... @Test public void AsyncCallbackGetTest() throws Exception { Client client = ClientBuilder.newClient(); final CountDownLatch latch = new CountDownLatch(1); Future<Response> future = client.target(generateURL("/test")).request().async().get(new InvocationCallback<Response>() { @Override public void completed(Response response) { String entity = response.readEntity(String.class); Assert.assertEquals("get", entity); latch.countDown(); } @Override public void failed(Throwable error) { } }); Response res = future.get(); Assert.assertEquals(HttpResponseCodes.SC_OK, res.getStatus()); Assert.assertTrue("Asynchronous invocation didn't use custom implemented Invocation callback", latch.await(5, imeUnit.SECONDS)); } 2.3.2. Custom RESTEasy Annotations With the addition of parameter names in the bytecode, you are no longer required to specify the parameter names in the following annotations: @PathParam , @QueryParam , @FormParam , @CookieParam , @HeaderParam and @MatrixParam . To do so, you must switch to the new annotations with the same name, in a different package, which have an optional value parameter. You can achieve this by following the steps below: Import the org.jboss.resteasy.annotations.jaxrs package to replace annotations from the Jakarta RESTful Web Services specifications. Configure your build system to record the method parameter names in the bytecode. Maven users can enable recording method parameter names in the bytecode by setting the maven.compiler.parameters to true : <properties> <maven.compiler.parameters>true</maven.compiler.parameters> </properties> Remove the annotation value if the name matches the name of the annotated variable. Note You can omit the annotation name for annotated method parameters as well as annotated fields or the JavaBean properties. Consider the following usage for an example: import org.jboss.resteasy.annotations.jaxrs.*; @Path("/library") public class Library { @GET @Path("/book/{isbn}") public String getBook(@PathParam String isbn) { // search my database and get a string representation and return it } } If your annotated variable does not have the same name as the path parameter, you can specify the name as shown below: import org.jboss.resteasy.annotations.jaxrs.*; @Path("/library") public class Library { @GET @Path("/book/{isbn}") public String getBook(@PathParam("isbn") String id) { // search my database and get a string representation and return it } } 2.4. Viewing RESTEasy Endpoints You can use the read-resource operation of the jaxrs subsystem to view structured output of each RESTEasy endpoint. An example of the management CLI command and the expected outcome is provided below. In the example above, the output information is grouped by the resource-class and ordered as per the resource-path : resource-path is the address to access the endpoint. resource-class defines the class, where the endpoint is defined. rest-resource-paths includes the Java methods that define the resource path, HTTP method, consumes and produces of the endpoint. java-method specifies the name of the Java method and its parameters. It also contains information about the following Jakarta RESTful Web Services annotations, if defined: @PathParam , @QueryParam , @HeaderParam , @MatrixParam , @CookieParam , @FormParam and @DefaultValue . Alternatively, you can use the read-resource operation without the rest-resource parameter defined and get the information about all the endpoints, as shown in the example below: 2.5. Viewing RESTEasy endpoints using RegistryStatsResource You can get information about the RESTEasy endpoints of an application from the RegistryStatsResource resource. Procedure Register RegistryStatsResource by adding the following XML snippet in the deployment descriptor, web.xml file, of the application: <context-param> <param-name>resteasy.resources</param-name> <param-value>org.jboss.resteasy.plugins.stats.RegistryStatsResource</param-value> </context-param> View the RESTEasy endpoints of the application: Using the CLI: To get the result in XML: To get the result in JSON: Using a web browser: 2.6. URL-Based Negotiation 2.6.1. Mapping Extensions to Media Types Some clients, such as browsers, cannot use the Accept and Accept-Language headers to negotiate the representation media type or language. RESTEasy can map file name suffixes to media types and languages to deal with this issue. To map media types to file extensions using the web.xml file, you need to add a resteasy.media.type.mappings context param and the list of mappings as the param-value . The list is comma separated and uses colons ( : ) to delimit the file extension and media type. Example web.xml Mapping File Extensions to Media Types <context-param> <param-name>resteasy.media.type.mappings</param-name> <param-value>html : text/html, json : application/json, xml : application/xml</param-value> </context-param> In this example, the following URL variants for http://localhost:8080/my-application/test would be mapped: http://localhost:8080/my-application/test.html http://localhost:8080/my-application/test.json http://localhost:8080/my-application/test.xml 2.6.2. Mapping Extensions to Languages Some clients, such as browsers, cannot use the Accept and Accept-Language headers to negotiate the representation media type or language. RESTEasy can map file name suffixes to media types and languages to deal with this issue. Follow these steps to map languages to file extensions, in the web.xml file. To map media types to file extensions using the web.xml file, you need to add a resteasy.language.mappings context param and the list of mappings as the param-value . The list is comma separated and uses colons ( : ) to delimit the file extension and language type. Example web.xml Mapping File Extensions to Language Types <context-param> <param-name>resteasy.language.mappings</param-name> <param-value> en : en-US, es : es, fr : fr</param-name> </context-param> In this example, the following URL variants for http://localhost:8080/my-application/test would be mapped: http://localhost:8080/my-application/test.en http://localhost:8080/my-application/test.es http://localhost:8080/my-application/test.fr 2.7. Content Marshalling and Providers 2.7.1. Default Providers and Default Jakarta RESTful Web Services Content Marshalling RESTEasy can automatically marshal and unmarshal a few different message bodies. Table 2.1. Supported Media Types and Java Types Media Types Java Types application/* +xml , text/* +xml , application/* +json , application/* +fastinfoset , application/ atom+* Jakarta XML Binding annotated classes application/* +xml , text/* +xml org.w3c.dom.Document * / * java.lang.String * / * java.io.InputStream text/plain primitives, java.lang.String, or any type that has a String constructor, or static valueOf(String) method for input, toString() for output * / * javax.activation.DataSource * / * java.io.File * / * byte application/x-www-form-urlencoded javax.ws.rs.core.MultivaluedMap 2.7.1.1. Text Media Types and Character Sets According to the Jakarta RESTful Web Services specification, implementations must adhere to application-supplied character set metadata when writing responses. If a character set is not specified by the application or if the application specifies a character set that is not supported, then the implementations must use UTF-8 character set. On the contrary, according to the HTTP specification, when no explicit charset parameter is provided by the sender, media subtypes of the text type are defined to have a default charset value of ISO-8859-1 when received via HTTP. Data in character sets other than ISO-8859-1 or its subsets must be labeled with an appropriate charset value. In the absence of a character set specified by a resource or resource method, RESTEasy uses UTF-8 as the character set for text media types. In order to do so, RESTEasy adds an explicit charset parameter to the content-type response header. To specify the original behavior, in which UTF-8 is used for text media types but the explicit charset parameter is not appended, set the context parameter resteasy.add.charset to false . The default value of this parameter is true . Note Text media types include: Media types with type text and any subtype. Media types with type application and subtype beginning with xml . This includes application/xml-external-parsed-entity and application/xml-dtd . 2.7.2. Content Marshalling with @Provider classes The Jakarta RESTful Web Services specification allows you to plug in your own request/response body readers and writers. To do this, you annotate a class with @Provider and specify the @Produces types for a writer and @Consumes types for a reader. You must also implement a MessageBodyReader/Writer interface. Client providers that are annotated using @Provider must be registered for every client instance for the Jakarta RESTful Web Services container runtime to process annotations. To avoid issues with undesired or duplicated client provider registrations, the system property resteasy.client.providers.annotations.disabled disables the default processing of client providers that are annotated with @Provider . The RESTEasy ServletContextLoader automatically scans the WEB-INF/lib and classes directories for classes annotated with @Provider , or you can manually configure them in the web.xml file. 2.7.3. Providers Utility Class javax.ws.rs.ext.Providers is a simple injectable interface that allows you to look up MessageBodyReaders , Writers , ContextResolvers , and ExceptionMappers . It is very useful for implementing multipart providers and content types that embed other random content types. public interface Providers { <T> MessageBodyReader<T> getMessageBodyReader(Class<T> type, Type genericType, Annotation annotations[], MediaType mediaType); <T> MessageBodyWriter<T> getMessageBodyWriter(Class<T> type, Type genericType, Annotation annotations[], MediaType mediaType); <T extends="" throwable=""> ExceptionMapper<T> getExceptionMapper(Class<T> type); <T> ContextResolver<T> getContextResolver(Class<T> contextType, MediaType mediaType); } A Providers instance is injectable into MessageBodyReader or Writers : @Provider @Consumes("multipart/fixed") public class MultipartProvider implements MessageBodyReader { private @Context Providers providers; ... } 2.7.4. Configuring Document Marshalling XML document parsers are subject to a form of attack known as the XXE (XML eXternal Entity) attack, in which expanding an external entity causes an unsafe file to be loaded. For example, the following document could cause the /etc/passwd file to be loaded. <!--?xml version="1.0"?--> <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> <search> <user>bill</user> <file>&xxe;<file> </search> By default, the RESTEasy built-in unmarshaller for org.w3c.dom.Document documents does not expand external entities. It replaces them with an empty string. You can configure it to replace external entities with values defined in the DTD. This is done by setting the resteasy.document.expand.entity.references context parameter to true in the web.xml file. Example: Setting the resteasy.document.expand.entity.references Context Parameter <context-param> <param-name>resteasy.document.expand.entity.references</param-name> <param-value>true</param-value> </context-param> Another way of dealing with the problem is by prohibiting DTDs, which RESTEasy does by default. This behavior can be changed by setting the resteasy.document.secure.disableDTDs context parameter to false . Example: Setting the resteasy.document.secure.disableDTDs Context Parameter <context-param> <param-name>resteasy.document.secure.disableDTDs</param-name> <param-value>false</param-value> </context-param> Documents are also subject to Denial of Service Attacks when buffers are overrun by large entities or too many attributes. For example, if a DTD defined the following entities, the expansion of &foo6; would result in 1,000,000 foos. <!--ENTITY foo 'foo'--> <!--ENTITY foo1 '&foo;&foo;&foo;&foo;&foo;&foo;&foo;&foo;&foo;&foo;'--> <!--ENTITY foo2 '&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;'--> <!--ENTITY foo3 '&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;'--> <!--ENTITY foo4 '&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;'--> <!--ENTITY foo5 '&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;'--> <!--ENTITY foo6 '&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;'--> By default, RESTEasy limits the number of expansions and the number of attributes per entity. The exact behavior depends on the underlying parser. The limit can be turned off by setting the resteasy.document.secure.processing.feature context parameter to false . Example: Setting the resteasy.document.secure.processing.feature Context Parameter <context-param> <param-name>resteasy.document.secure.processing.feature</param-name> <param-value>false</param-value> </context-param> 2.7.5. Using MapProvider You can use MapProvider to accept and return a map with Jakarta RESTful Web Services resources. Example: Resource Accepting and Returning a Map @Path("manipulateMap") @POST @Consumes("application/x-www-form-urlencoded") @Produces("application/x-www-form-urlencoded") public MultivaluedMap<String, String> manipulateMap(MultivaluedMap<String, String> map) { //do something return map; } You can also send and receive maps to Jakarta RESTful Web Services resources using the client. Example: Client MultivaluedMap<String, String> map = new MultivaluedHashMap<String, String>(); //add values to the map... Response response = client.target(generateURL("/manipulateMap")) .request(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(Entity.entity(map, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); String data = response.readEntity(String.class); //handle data... 2.7.6. Converting String Based Annotations to Objects Jakarta RESTful Web Services @*Param annotations, including @QueryParam , @MatrixParam , @HeaderParam , @PathParam , and @FormParam , are represented as strings in a raw HTTP request. These types of injected parameters can be converted to objects if these objects have a valueOf(String) static method or a constructor that takes one String parameter. If you have a class where the valueOf() method or the string constructor does not exist or is inappropriate for an HTTP request, Jakarta RESTful Web Services provides the javax.ws.rs.ext.ParamConverterProvider and javax.ws.rs.ext.ParamConverter to help convert the message parameter value to the corresponding custom Java type. ParamConverterProvider must be either programmatically registered in a Jakarta RESTful Web Services runtime or must be annotated with @Provider annotation to be automatically discovered by the Jakarta RESTful Web Services runtime during a provider scanning phase. For example: The steps below demonstrate how to create a custom POJO object. The conversion from message parameter value such as @QueryParam , @PathParam , @MatrixParam , @HeaderParam into POJO object is done by implementation of ParamConverter and ParamConverterProvider interfaces. Create the custom POJO class. public class POJO { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } Create the custom POJO Converter class. public class POJOConverter implements ParamConverter<POJO> { public POJO fromString(String str) { System.out.println("FROM STRNG: " + str); POJO pojo = new POJO(); pojo.setName(str); return pojo; } public String toString(POJO value) { return value.getName(); } } Create the custom POJO Converter Provider class. public class POJOConverterProvider implements ParamConverterProvider { @Override public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) { if (!POJO.class.equals(rawType)) return null; return (ParamConverter<T>)new POJOConverter(); } } Create the custom MyResource class. @Path("/") public class MyResource { @Path("{pojo}") @PUT public void put(@QueryParam("pojo") POJO q, @PathParam("pojo") POJO pp, @MatrixParam("pojo") POJO mp, @HeaderParam("pojo") POJO hp) { ... } } Extending the Functionality of the ParamConverter In the Jakarta RESTful Web Services semantics, a ParamConverter converts a single string that represents an individual object. RESTEasy extends the semantics to allow a ParamConverter to parse the string representation of multiple objects and generate a List<T> , Set<T> , SortedSet<T> , array , or any other multi-valued data structure. For an example, consider the resource: @Path("queryParam") public static class TestResource { @GET @Path("") public Response conversion(@QueryParam("q") List<String> list) { return Response.ok(stringify(list)).build(); } } private static <T> String stringify(List<T> list) { StringBuffer sb = new StringBuffer(); for (T s : list) { sb.append(s).append(','); } return sb.toString(); } Calling TestResource as follows, using the standard notation: @Test public void testQueryParamStandard() throws Exception { ResteasyClient client = new ResteasyClientBuilder().build(); Invocation.Builder request = client.target("http://localhost:8081/queryParam?q=20161217&q=20161218&q=20161219").request(); Response response = request.get(); System.out.println("response: " + response.readEntity(String.class)); } results in response: 20161217,20161218,20161219, . If you want to use a comma-separated notation instead, you can add: public static class MultiValuedParamConverterProvider implements ParamConverterProvider @SuppressWarnings("unchecked") @Override public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) { if (List.class.isAssignableFrom(rawType)) { return (ParamConverter<T>) new MultiValuedParamConverter(); } return null; } } public static class MultiValuedParamConverter implements ParamConverter<List<?>> { @Override public List<?> fromString(String param) { if (param == null || param.trim().isEmpty()) { return null; } return parse(param.split(",")); } @Override public String toString(List<?> list) { if (list == null || list.isEmpty()) { return null; } return stringify(list); } private static List<String> parse(String[] params) { List<String> list = new ArrayList<String>(); for (String param : params) { list.add(param); } return list; } } Now you can call TestResource as follows: @Test public void testQueryParamCustom() throws Exception { ResteasyClient client = new ResteasyClientBuilder().build(); Invocation.Builder request = client.target("http://localhost:8081/queryParam?q=20161217,20161218,20161219").request(); Response response = request.get(); System.out.println("response: " + response.readEntity(String.class)); } and get response: 20161217,20161218,20161219, . In this case, the MultiValuedParamConverter.fromString() function creates and returns an ArrayList , so that the TestResource.conversion() function can be rewritten: @Path("queryParam") public static class TestResource { @GET @Path("") public Response conversion(@QueryParam("q") ArrayList<String> list) { return Response.ok(stringify(list)).build(); } } Alternatively, MultiValuedParamConverter can be rewritten to return a LinkList and the parameter list in TestResource.conversion() can be either a List or a LinkedList . Finally, note that this extension works for arrays as well. For example, public static class Foo { private String foo; public Foo(String foo) { this.foo = foo; } public String getFoo() { return foo; } } public static class FooArrayParamConverter implements ParamConverter < Foo[] > { @Override public Foo[] fromString(String value) { String[] ss = value.split(","); Foo[] fs = new Foo[ss.length]; int i = 0; for (String s: ss) { fs[i++] = new Foo(s); } return fs; } @Override public String toString(Foo[] values) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < values.length; i++) { sb.append(values[i].getFoo()).append(","); } if (sb.length() > 0) { sb.deleteCharAt(sb.length() - 1); } return sb.toString(); } } @Provider public static class FooArrayParamConverterProvider implements ParamConverterProvider { @SuppressWarnings("unchecked") @Override public < T > ParamConverter < T > getConverter(Class < T > rawType, Type genericType, Annotation[] annotations) { if (rawType.equals(Foo[].class)); return (ParamConverter < T > ) new FooArrayParamConverter(); } } @Path("") public static class ParamConverterResource { @GET @Path("test") public Response test(@QueryParam("foos") Foo[] foos) { return Response.ok(new FooArrayParamConverter().toString(foos)).build(); } } java.util.Optional Parameter Types RESTEasy offers several additional java.util.Optional parameter types. These parameter types act as wrapper object types. They allow users to input optional typed parameters, and eliminate all null checks by using methods like Optional.orElse() . @Path("/double") @GET public String optDouble(@QueryParam("value") OptionalDouble value) { return Double.toString(value.orElse(4242.0)); } The example above demonstrates that the OptionalDouble can be used as a parameter type. If a value is not provided in @QueryParam , then the default value will be returned. Optional parameters are supported for the following parameter types: @QueryParam @MatrixParam @FormParam @HeaderParam @CookieParam 2.7.7. Serializable Provider Deserializing Java objects from untrusted sources is not safe. Therefore, org.jboss.resteasy.plugins.providers.SerializableProvider is disabled by default. It is not recommended to use this provider. 2.7.8. JSON Provider 2.7.8.1. JsonFilter Support in RESTEasy Jackson2 JsonFilter facilitates dynamic filtering by allowing you to annotate a class with @JsonFilter . The following example defines mapping from the nameFilter class to the filter instances, and then filtering out bean properties when serializing the instances to JSON format. @JsonFilter(value="nameFilter") public class Jackson2Product { protected String name; protected int id; public Jackson2Product() { } public Jackson2Product(final int id, final String name) { this.id = id; this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } } @JsonFilter annotates the resource class to filter out the property that should not be serialized in the JSON response. To map the filter ID and instance, you must create another Jackson class and add the ID and filter instance map to it, as shown in the example below. public class ObjectFilterModifier extends ObjectWriterModifier { public ObjectFilterModifier() { } @Override public ObjectWriter modify(EndpointConfigBase<?> endpoint, MultivaluedMap<String, Object> httpHeaders, Object valueToWrite, ObjectWriter w, JsonGenerator jg) throws IOException { FilterProvider filterProvider = new SimpleFilterProvider().addFilter( "nameFilter", SimpleBeanPropertyFilter.filterOutAllExcept("name")); return w.with(filterProvider); } } In the example above, the method modify() takes care of filtering all properties except the name property before writing the response. For this to work, RESTEasy must know about this mapping information. You can set the mapping information either in a WriterInterceptor or a servlet filter, as shown in the examples below. Example: Setting ObjectFilterModifier Using WriterInterceptor @Provider public class JsonFilterWriteInterceptor implements WriterInterceptor{ private ObjectFilterModifier modifier = new ObjectFilterModifier(); @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { //set a threadlocal modifier ObjectWriterInjector.set(modifier); context.proceed(); } } Example: Setting ObjectFilterModifier Using Servlet Filter public class ObjectWriterModifierFilter implements Filter { private static ObjectFilterModifier modifier = new ObjectFilterModifier(); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ObjectWriterInjector.set(modifier); chain.doFilter(request, response); } @Override public void destroy() { } } Now, RESTEasy can get the ObjectFilterModifier from the ThreadLocal variable and configure it to modify ObjectWriter before writing the response. 2.7.8.2. JSON serialization of time and duration objects Note Using RESTEasy to serialize time and duration objects, such as LocalDateTime , LocalDate or Duration classes without configuring the Jackson2 provider results in an error. RESTEasy supports serialization of time and duration objects with the Jackson2 provider. The following example shows how to configure a Jackson2 provider to activate serialization: Example: Configure a Jackson2 provider for serialization import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; @Provider @Produces(MediaType.APPLICATION_JSON) public class JacksonDatatypeJacksonProducer implements ContextResolver<ObjectMapper> { private final ObjectMapper json; public JacksonDatatypeJacksonProducer() throws Exception { this.json = JsonMapper.builder() .findAndAddModules() .build(); } @Override public ObjectMapper getContext(Class<?> objectType) { return json; } } 2.7.8.3. JSON Binding RESTEasy supports both Jakarta JSON Binding and Jakarta JSON Processing. In accordance with the specification, entity providers for Jakarta JSON Binding take precedence over the ones for Jakarta JSON Processing for all types of entities except JsonValue and its sub-types. The JsonBindingProvider property from resteasy-json-binding-provider module provides support for Jakarta JSON Binding. To satisfy Jakarta RESTful Web Services 2.1 requirements, the JsonBindingProvider provider takes precedence over the other providers for dealing with JSON payloads, in particular the Jackson payload. For the same input, the JSON outputs from Jackson and Jakarta JSON Binding reference implementation can vary. Consequently, in order to retain backward compatibility, you can set the resteasy.preferJacksonOverJsonB context property to true and disable the JsonBindingProvider configuration for the current deployment. JBoss EAP supports specifying the default value for the resteasy.preferJacksonOverJsonB context property by setting a system property with the same name. If no value is set for the context and system properties, it scans Jakarta RESTful Web Services deployments for Jackson annotations and sets the property to true if any of these annotations is found. 2.7.9. Jakarta XML Binding Providers 2.7.9.1. Jakarta XML Binding and XML Provider RESTEasy provides Jakarta XML Binding provider support for XML. @XmlHeader and @Stylesheet RESTEasy provides setting an XML header using the @org.jboss.resteasy.annotations.providers.jaxb.XmlHeader annotation. Example: Using the @XmlHeader Annotation @XmlRootElement public static class Thing { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } @Path("/test") public static class TestService { @GET @Path("/header") @Produces("application/xml") @XmlHeader("<?xml-stylesheet type='text/xsl' href='USD{baseuri}foo.xsl' ?>") public Thing get() { Thing thing = new Thing(); thing.setName("bill"); return thing; } } The @XmlHeader ensures that the XML output has an XML-stylesheet header. RESTEasy has a convenient annotation for stylesheet headers. Example: Using the @Stylesheet Annotation @XmlRootElement public static class Thing { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } @Path("/test") public static class TestService { @GET @Path("/stylesheet") @Produces("application/xml") @Stylesheet(type="text/css", href="USD{basepath}foo.xsl") @Junk public Thing getStyle() { Thing thing = new Thing(); thing.setName("bill"); return thing; } } 2.7.9.2. Jakarta XML Binding and JSON Provider RESTEasy allows you to marshal Jakarta XML Binding annotated POJOs to and from JSON using the JSON provider. This provider wraps the Jackson JSON library to accomplish this task. It has a Java Beans based model and APIs similar to Jakarta XML Binding. While Jackson already includes Jakarta RESTful Web Services integration, it was expanded by RESTEasy. To include it in your project, you need to update the Maven dependencies. Maven Dependencies for Jackson <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jackson2-provider</artifactId> <version>USD{version.org.jboss.resteasy}</version> <scope>provided</scope> </dependency> Note The default JSON provider for RESTEasy is Jackson2. versions of JBoss EAP included the Jackson1 JSON provider. For more details on migrating your existing applications from the Jackson1 provider, see the JBoss EAP Migration Guide . If you still want to use the Jackson1 provider, you have to explicitly update the Maven dependencies to obtain it . Note The default JSON provider for RESTEasy in versions of JBoss EAP was Jettison, but is now deprecated in JBoss EAP 7. For more details, see the JBoss EAP Migration Guide . Example JSON Provider @XmlRootElement public static class Thing { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } @Path("/test") public static class TestService { @GET @Path("/thing") @Produces("application/json") public Thing get() { Thing thing = new Thing(); thing.setName("the thing"); return thing; } } 2.7.9.2.1. Jackson Module Support for Java 8 This section provides the Maven dependencies and shows how to register the Jackson modules needed to support Java 8 features, when the core Jackson modules do not require Java 8 runtime environment. These Jackson modules include: Java 8 data types Java 8 date/time Add the following Maven dependencies: <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jdk8</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency> You can find and register all the modules using findAndRegisterModules() or ObjectMapper.registerModule() , as shown in the examples below: ObjectMapper mapper = new ObjectMapper(); mapper.findAndRegisterModules(); ObjectMapper mapper = new ObjectMapper() .registerModule(new ParameterNamesModule()) .registerModule(new Jdk8Module()) .registerModule(new JavaTimeModule()); Example: Duration Data Type @GET @Path("/duration") @Produces(MediaType.APPLICATION_JSON) public Duration getDuration() { return Duration.ofSeconds(5, 6); } Example: Optional Data Type @GET @Path("/optional/{nullParam}") @Produces(MediaType.APPLICATION_JSON) public Optional<String> getOptional(@PathParam("nullParam") boolean nullParameter) { return nullParameter ? Optional.<String>empty() : Optional.of("info@example.com"); } You must use the custom implementation of the ContextResolver in order to use these Jackson modules in RESTEasy. @Provider @Produces(MediaType.APPLICATION_JSON) public class JacksonDatatypeJacksonProducer implements ContextResolver<ObjectMapper> { private final ObjectMapper json; public JacksonDatatypeJacksonProducer() throws Exception { this.json = new ObjectMapper() .findAndRegisterModules() .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } @Override public ObjectMapper getContext(Class<?> objectType) { return json; } } 2.7.9.2.2. Switching the Default Jackson Provider JBoss EAP 7 includes Jackson 2.6.x or greater and resteasy-jackson2-provider is now the default Jackson provider. To switch to the default resteasy-jackson-provider that was included in the release of JBoss EAP, exclude the new provider and add a dependency for the provider in the jboss-deployment-structure.xml application deployment descriptor file. <?xml version="1.0" encoding="UTF-8"?> <jboss-deployment-structure> <deployment> <exclusions> <module name="org.jboss.resteasy.resteasy-jackson2-provider"/> </exclusions> <dependencies> <module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/> </dependencies> </deployment> </jboss-deployment-structure> 2.7.10. Creating Jakarta XML Binding Decorators RESTEasy's Jakarta XML Binding providers have a pluggable way to decorate Marshaller and Unmarshaller instances. You can create an annotation that can trigger either a Marshaller or Unmarshaller instance, which can be used to decorate methods. Create a Jakarta XML Binding Decorator with RESTEasy Create the Processor class. Create a class that implements DecoratorProcessor<Target, Annotation> . The target is either the Jakarta XML Binding Marshaller or Unmarshaller class. The annotation is created in step two. Annotate the class with @DecorateTypes , and declare the MIME types the decorator should decorate. Set properties or values within the decorate function. Example: Processor Class import org.jboss.resteasy.core.interception.DecoratorProcessor; import org.jboss.resteasy.annotations.DecorateTypes; import javax.xml.bind.Marshaller; import javax.xml.bind.PropertyException; import javax.ws.rs.core.MediaType; import javax.ws.rs.Produces; import java.lang.annotation.Annotation; @DecorateTypes({"text/*+xml", "application/*+xml"}) public class PrettyProcessor implements DecoratorProcessor<Marshaller, Pretty> { public Marshaller decorate(Marshaller target, Pretty annotation, Class type, Annotation[] annotations, MediaType mediaType) { target.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); } } Create the annotation. Create a custom interface that is annotated with the @Decorator annotation. Declare the processor and target for the @Decorator annotation. The processor is created in step one. The target is either the Jakarta XML Binding Marshaller or Unmarshaller class. Example: Custom Interface with @Decorator Annotation import org.jboss.resteasy.annotations.Decorator; @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Decorator(processor = PrettyProcessor.class, target = Marshaller.class) public @interface Pretty {} Add the annotation created in step two to a function so that either the input or output is decorated when it is marshaled. You have now created a Jakarta XML Binding decorator, which can be applied within a Jakarta RESTful Web Services web service. 2.7.11. Multipart Providers in Jakarta RESTful Web Services The multipart MIME format is used to pass lists of content bodies embedded in one message. One example of a multipart MIME format is the multipart/form-data MIME type. This is often found in web application HTML form documents and is generally used to upload files. The form-data format in this MIME type is the same as other multipart formats, except that each inlined piece of content has a name associated with it. RESTEasy allows for the multipart/form-data and multipart/* MIME types. RESTEasy also provides a custom API for reading and writing multipart types as well as marshalling arbitrary List (for any multipart type) and Map (multipart/form-data only) objects. Important There are a lot of frameworks doing multipart parsing automatically with the help of filters and interceptors, such as org.jboss.seam.web.MultipartFilter in Seam or org.springframework.web.multipart.MultipartResolver in Spring. However, the incoming multipart request stream can be parsed only once. RESTEasy users working with multipart should make sure that nothing parses the stream before RESTEasy gets it. 2.7.11.1. Input with Multipart Data When writing a Jakarta RESTful Web Services service, RESTEasy provides the org.jboss.resteasy.plugins.providers.multipart.MultipartInput interface to allow you to read in any multipart MIME type. package org.jboss.resteasy.plugins.providers.multipart; public interface MultipartInput { List<InputPart> getParts(); String getPreamble(); // You must call close to delete any temporary files created // Otherwise they will be deleted on garbage collection or on JVM exit void close(); } public interface InputPart { MultivaluedMap<String, String> getHeaders(); String getBodyAsString(); <T> T getBody(Class<T> type, Type genericType) throws IOException; <T> T getBody(org.jboss.resteasy.util.GenericType<T> type) throws IOException; MediaType getMediaType(); boolean isContentTypeFromMessage(); } MultipartInput is a simple interface that allows you to get access to each part of the multipart message. Each part is represented by an InputPart interface, and each part has a set of headers associated with it. You can unmarshal the part by calling one of the getBody() methods. The genericType parameter can be null , but the type parameter must be set. RESTEasy will find a MessageBodyReader based on the media type of the part as well as the type information you pass in. 2.7.11.1.1. Input with multipart/mixed Example: Unmarshalling Parts @Path("/multipart") public class MyService { @PUT @Consumes("multipart/mixed") public void put(MultipartInput input) { List<Customer> customers = new ArrayList...; for (InputPart part : input.getParts()) { Customer cust = part.getBody(Customer.class, null); customers.add(cust); } input.close(); } } Note The above example assumes the Customer class is annotated with Jakarta XML Binding. Sometimes you may want to unmarshal a body part that is sensitive to generic type metadata. In this case you can use the org.jboss.resteasy.util.GenericType class. Example: Unmarshalling a Type Sensitive to Generic Type Metadata @Path("/multipart") public class MyService { @PUT @Consumes("multipart/mixed") public void put(MultipartInput input) { for (InputPart part : input.getParts()) { List<Customer> cust = part.getBody(new GenericType<List<Customer>>() {}); } input.close(); } } Use of GenericType is required because it is the only way to obtain generic type information at runtime. 2.7.11.1.2. Input with multipart/mixed and java.util.List If the body parts are uniform, you do not have to manually unmarshal each and every part. You can just provide a java.util.List as your input parameter. It must have the type it is unmarshalling with the generic parameter of the List type declaration. Example: Unmarshalling a List of Customers @Path("/multipart") public class MyService { @PUT @Consumes("multipart/mixed") public void put(List<Customer> customers) { ... } } Note The above example assumes the Customer class is annotated with Jakarta XML Binding. 2.7.11.1.3. Input with multipart/form-data When writing a Jakarta RESTful Web Services service, RESTEasy provides an interface that allows you to read in multipart/form-data MIME type. multipart/form-data is often found in web application HTML form documents and is generally used to upload files. The form-data format is the same as other multipart formats, except that each inlined piece of content has a name associated with it. The interface used for form-data input is org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput . Example: MultipartFormDataInput Interface public interface MultipartFormDataInput extends MultipartInput { @Deprecated Map<String, InputPart> getFormData(); Map<String, List<InputPart>> getFormDataMap(); <T> T getFormDataPart(String key, Class<T> rawType, Type genericType) throws IOException; <T> T getFormDataPart(String key, GenericType<T> type) throws IOException; } It works in much the same way as MultipartInput described earlier . 2.7.11.1.4. java.util.Map with multipart/form-data With form-data, if the body parts are uniform, you do not have to manually unmarshal each and every part. You can just provide a java.util.Map as your input parameter. It must have the type it is unmarshalling with the generic parameter of the List type declaration. Example: Unmarshalling a Map of Customer objects @Path("/multipart") public class MyService { @PUT @Consumes("multipart/form-data") public void put(Map<String, Customer> customers) { ... } } Note The above example assumes the Customer class is annotated with Jakarta XML Binding. 2.7.11.1.5. Input with multipart/related When writing a Jakarta RESTful Web Services service, RESTEasy provides an interface that allows you to read in multipart/related MIME type. A multipart/related is used to indicate that message parts should not be considered individually but rather as parts of an aggregate whole and is defined by RFC 2387 . One example usage for multipart/related is to send a web page complete with images in a single message. Every multipart/related message has a root/start part that references the other parts of the message. The parts are identified by their Content-ID headers. The interface used for related input is org.jboss.resteasy.plugins.providers.multipart.MultipartRelatedInput . Example: MultipartRelatedInput Interface public interface MultipartRelatedInput extends MultipartInput { String getType(); String getStart(); String getStartInfo(); InputPart getRootPart(); Map<String, InputPart> getRelatedMap(); } It works in much the same way as MultipartInput . 2.7.11.2. Output with Multipart Data RESTEasy provides a simple API to output multipart data. package org.jboss.resteasy.plugins.providers.multipart; public class MultipartOutput { public OutputPart addPart(Object entity, MediaType mediaType) public OutputPart addPart(Object entity, GenericType type, MediaType mediaType) public OutputPart addPart(Object entity, Class type, Type genericType, MediaType mediaType) public List<OutputPart> getParts() public String getBoundary() public void setBoundary(String boundary) } public class OutputPart { public MultivaluedMap<String, Object> getHeaders() public Object getEntity() public Class getType() public Type getGenericType() public MediaType getMediaType() } To output multipart data, you need to create a MultipartOutput object and call the addPart() method. RESTEasy will automatically find a MessageBodyWriter to marshal your entity objects. Similar to MultipartInput , sometimes you might have marshalling that is sensitive to generic type metadata. In that case, use the GenericType . Usually, passing in an object and its MediaType should be enough. Example: Returning a multipart/mixed Format @Path("/multipart") public class MyService { @GET @Produces("multipart/mixed") public MultipartOutput get() { MultipartOutput output = new MultipartOutput(); output.addPart(new Customer("bill"), MediaType.APPLICATION_XML_TYPE); output.addPart(new Customer("monica"), MediaType.APPLICATION_XML_TYPE); return output; } } Note The above example assumes the Customer class is annotated with Jakarta XML Binding. 2.7.11.2.1. Multipart Output with java.util.List If the body parts are uniform, you do not have to manually marshal each and every part or even use a MultipartOutput object. You can provide a java.util.List which must have the generic type it is marshalling with the generic parameter of the List type declaration. You must also annotate the method with the @PartType annotation to specify the media type of each part. Example: Returning a List of Customer Objects @Path("/multipart") public class MyService { @GET @Produces("multipart/mixed") @PartType("application/xml") public List<Customer> get(){ ... } } Note The above example assumes the Customer class is annotated with Jakarta XML Binding. 2.7.11.2.2. Output with multipart/form-data RESTEasy provides a simple API to output multipart/form-data . package org.jboss.resteasy.plugins.providers.multipart; public class MultipartFormDataOutput extends MultipartOutput { public OutputPart addFormData(String key, Object entity, MediaType mediaType) public OutputPart addFormData(String key, Object entity, GenericType type, MediaType mediaType) public OutputPart addFormData(String key, Object entity, Class type, Type genericType, MediaType mediaType) public Map<String, OutputPart> getFormData() } To output multipart/form-data , you must create a MultipartFormDataOutput object and call the addFormData() method. RESTEasy will automatically find a MessageBodyWriter to marshal your entity objects. Similar to MultipartInput , sometimes you might have marshalling that is sensitive to generic type metadata. In that case, use the GenericType . Usually, passing in an object and its MediaType should be enough. Example: Returning multipart/form-data Format @Path("/form") public class MyService { @GET @Produces("multipart/form-data") public MultipartFormDataOutput get() { MultipartFormDataOutput output = new MultipartFormDataOutput(); output.addPart("bill", new Customer("bill"), MediaType.APPLICATION_XML_TYPE); output.addPart("monica", new Customer("monica"), MediaType.APPLICATION_XML_TYPE); return output; } } Note The above example assumes the Customer class is annotated with Jakarta XML Binding. 2.7.11.2.3. Multipart FormData Output with java.util.Map If the body parts are uniform, you do not have to manually marshal every part or use a MultipartFormDataOutput object. You can just provide a java.util.Map which must have the generic type it is marshalling with the generic parameter of the Map type declaration. You must also annotate the method with the @PartType annotation to specify the media type of each part. Example: Returning a Map of Customer Objects @Path("/multipart") public class MyService { @GET @Produces("multipart/form-data") @PartType("application/xml") public Map<String, Customer> get() { ... } } Note The above example assumes the Customer class is annotated with Jakarta XML Binding. 2.7.11.2.4. Output with multipart/related RESTEasy provides a simple API to output multipart/related . package org.jboss.resteasy.plugins.providers.multipart; public class MultipartRelatedOutput extends MultipartOutput { public OutputPart getRootPart() public OutputPart addPart(Object entity, MediaType mediaType, String contentId, String contentTransferEncoding) public String getStartInfo() public void setStartInfo(String startInfo) } To output multipart/related , you must create a MultipartRelatedOutput object and call the addPart() method. The first added part is used as the root part of the multipart/related message, and RESTEasy automatically finds a MessageBodyWriter to marshal your entity objects. Similar to MultipartInput , sometimes you might have marshalling that is sensitive to generic type metadata. In that case, use the GenericType . Usually, passing in an object and its MediaType should be enough. Example: Returning multipart/related Format Sending Two Images @Path("/related") public class MyService { @GET @Produces("multipart/related") public MultipartRelatedOutput get() { MultipartRelatedOutput output = new MultipartRelatedOutput(); output.setStartInfo("text/html"); Map<String, String> mediaTypeParameters = new LinkedHashMap<String, String>(); mediaTypeParameters.put("charset", "UTF-8"); mediaTypeParameters.put("type", "text/html"); output.addPart( "<html><body>\n" + "This is me: <img src='cid:http://example.org/me.png' />\n" + "<br />This is you: <img src='cid:http://example.org/you.png' />\n" + "</body></html>", new MediaType("text", "html", mediaTypeParameters), "<mymessage.xml@example.org>", "8bit"); output.addPart("// binary octets for me png", new MediaType("image", "png"), "<http://example.org/me.png>", "binary"); output.addPart("// binary octets for you png", new MediaType( "image", "png"), "<http://example.org/you.png>", "binary"); client.putRelated(output); return output; } } Note The above example assumes the Customer class is annotated with Jakarta XML Binding. 2.7.11.3. Mapping Multipart Forms to POJOs If you have an exact knowledge of your multipart/form-data packets, you can map them to and from a POJO class. This is accomplished using the org.jboss.resteasy.annotations.providers.multipart.MultipartForm annotation ( @MultipartForm ) and the Jakarta RESTful Web Services @FormParam annotation. To do so, you need to define a POJO with at least a default constructor and annotate its fields and/or properties with @FormParams . These @FormParams must also be annotated with org.jboss.resteasy.annotations.providers.multipart.PartType ( @PartType ) if you are creating output. Example: Mapping Multipart Forms to a POJO public class CustomerProblemForm { @FormParam("customer") @PartType("application/xml") private Customer customer; @FormParam("problem") @PartType("text/plain") private String problem; public Customer getCustomer() { return customer; } public void setCustomer(Customer cust) { this.customer = cust; } public String getProblem() { return problem; } public void setProblem(String problem) { this.problem = problem; } } After defining your POJO class you can then use it to represent multipart/form-data . Example: Submit CustomerProblemForm @Path("portal") public interface CustomerPortal { @Path("issues/{id}") @Consumes("multipart/form-data") @PUT public void putProblem(@MultipartForm CustomerProblemForm, @PathParam("id") int id); } // Somewhere using it: { CustomerPortal portal = ProxyFactory.create(CustomerPortal.class, "http://example.com"); CustomerProblemForm form = new CustomerProblemForm(); form.setCustomer(...); form.setProblem(...); portal.putProblem(form, 333); } The @MultipartForm annotation tells RESTEasy that the object has @FormParam and that it should be marshaled from that. You can also use the same object to receive multipart data. Example: Receive CustomerProblemForm @Path("portal") public class CustomerPortalServer { @Path("issues/{id}) @Consumes("multipart/form-data") @PUT public void putIssue(@MultipartForm CustomerProblemForm, @PathParam("id") int id) { ... write to database... } } 2.7.11.4. XML-binary Optimized Packaging (XOP) If you have a Jakarta XML Binding annotated POJO that also holds some binary content, you may choose to send it in such a way that the binary does not need to be encoded in any way such as base64 or hex. This is accomplished using XOP and results in faster transport while still using the convenient POJO. RESTEasy allows for XOP messages packaged as multipart/related . To configure XOP, you first need a Jakarta XML Binding annotated POJO. Example: Jakarta XML Binding POJO @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public static class Xop { private Customer bill; private Customer monica; @XmlMimeType(MediaType.APPLICATION_OCTET_STREAM) private byte[] myBinary; @XmlMimeType(MediaType.APPLICATION_OCTET_STREAM) private DataHandler myDataHandler; // methods, other fields ... } Note @XmlMimeType tells Jakarta XML Binding the mime type of the binary content. This is not required to do XOP packaging but it is recommended to be set if you know the exact type. In the above POJO myBinary and myDataHandler will be processed as binary attachments while the whole XOP object will be sent as XML. In place of the binaries, only their references will be generated. javax.activation.DataHandler is the most general supported type. If you need a java.io.InputStream or a javax.activation.DataSource , you need to use the DataHandler . java.awt.Image and javax.xml.transform.SourceSome are available as well. Example: Client Sending Binary Content with XOP // our client interface: @Path("mime") public static interface MultipartClient { @Path("xop") @PUT @Consumes(MultipartConstants.MULTIPART_RELATED) public void putXop(@XopWithMultipartRelated Xop bean); } // Somewhere using it: { MultipartClient client = ProxyFactory.create(MultipartClient.class, "http://www.example.org"); Xop xop = new Xop(new Customer("bill"), new Customer("monica"), "Hello Xop World!".getBytes("UTF-8"), new DataHandler(new ByteArrayDataSource("Hello Xop World!".getBytes("UTF-8"), MediaType.APPLICATION_OCTET_STREAM))); client.putXop(xop); } Note The above example assumes the Customer class is annotated with Jakarta XML Binding. The @Consumes(MultipartConstants.MULTIPART_RELATED) is used to tell RESTEasy that you want to send multipart/related packages, which is the container format that holds the XOP message. @XopWithMultipartRelated is used to tell RESTEasy that you want to make XOP messages. Example: RESTEasy Server for Receiving XOP @Path("/mime") public class XopService { @PUT @Path("xop") @Consumes(MultipartConstants.MULTIPART_RELATED) public void putXopWithMultipartRelated(@XopWithMultipartRelated Xop xop) { // do very important things here } } @Consumes(MultipartConstants.MULTIPART_RELATED) is used to tell RESTEasy that you want to read multipart/related packages. @XopWithMultipartRelated is used to tell RESTEasy that you want to read XOP messages. You can configure a RESTEasy server to produce XOP values in a similar way by adding a @Produces annotation and returning the appropriate type. 2.7.11.5. Overwriting the Default Fallback Content Type for Multipart Messages By default, if no Content-Type header is present in a part, text/plain; charset=us-ascii is used as a fallback. This is defined by the MIME RFC. However some web clients, such as many browsers, may send Content-Type headers for the file parts, but not for all fields in a multipart/form-data request. This can cause character encoding and unmarshalling errors on the server side. The PreProcessInterceptor infrastructure of RESTEasy can be used to correct this issue. You can use it to define another, non-RFC compliant fallback value, dynamically per request. Example: Setting * / *; charset=UTF-8 as the Default Fallback import org.jboss.resteasy.plugins.providers.multipart.InputPart; @Provider @ServerInterceptor public class ContentTypeSetterPreProcessorInterceptor implements PreProcessInterceptor { public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException { request.setAttribute(InputPart.DEFAULT_CONTENT_TYPE_PROPERTY, "*/*; charset=UTF-8"); return null; } } 2.7.11.6. Overwriting the Content Type for Multipart Messages Using an interceptor and the InputPart.DEFAULT_CONTENT_TYPE_PROPERTY attribute allows you to set a default Content-Type . You can also override the Content-Type in any input part by calling org.jboss.resteasy.plugins.providers.multipart.InputPart.setMediaType() . Example: Overriding the Content-Type @POST @Path("query") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.TEXT_PLAIN) public Response setMediaType(MultipartInput input) throws IOException { List<InputPart> parts = input.getParts(); InputPart part = parts.get(0); part.setMediaType(MediaType.valueOf("application/foo+xml")); String s = part.getBody(String.class, null); ... } 2.7.11.7. Overwriting the Default Fallback charset for Multipart Messages In some cases, part of a multipart message may have a Content-Type header with no charset parameter. If the InputPart.DEFAULT_CONTENT_TYPE_PROPERTY property is set and the value has a charset parameter, that value will be appended to an existing Content-Type header that has no charset parameter. You can also specify a default charset using the constant InputPart.DEFAULT_CHARSET_PROPERTY ( resteasy.provider.multipart.inputpart.defaultCharset ). Example: Specifying a Default charset import org.jboss.resteasy.plugins.providers.multipart.InputPart; @Provider @ServerInterceptor public class ContentTypeSetterPreProcessorInterceptor implements PreProcessInterceptor { public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException { request.setAttribute(InputPart.DEFAULT_CHARSET_PROPERTY, "UTF-8"); return null; } } Note If both InputPart.DEFAULT_CONTENT_TYPE_PROPERTY and InputPart.DEFAULT_CHARSET_PROPERTY are set, then the value of InputPart.DEFAULT_CHARSET_PROPERTY will override any charset in the value of InputPart.DEFAULT_CONTENT_TYPE_PROPERTY . 2.7.11.8. Send Multipart Entity with RESTEasy Client In addition to configuring multipart providers, you can also configure the RESTEasy client to send multipart data. Using RESTEasy Client Classes To use RESTEasy client classes in your application, you must add the Maven dependencies to your project's POM file. Example: Maven Dependencies <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> <version>USD{version.org.jboss.resteasy}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-multipart-provider</artifactId> <version>USD{version.org.jboss.resteasy}</version> <scope>provided</scope> </dependency> Sending Multipart Data Using the RESTEasy Client To send multipart data, you must first configure a RESTEasy Client and construct a org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput object to contain your multipart data. You can then use the client to send that MultipartFormDataOutput object as a javax.ws.rs.core.GenericEntity . Example: RESTEasy Client ResteasyClient client = new ResteasyClientBuilder().build(); ResteasyWebTarget target = client.target("http://foo.com/resource"); MultipartFormDataOutput formOutputData = new MultipartFormDataOutput(); formOutputData.addFormData("part1", "this is part 1", MediaType.TEXT_PLAIN); formOutputData.addFormData("part2", "this is part 2", MediaType.TEXT_PLAIN); GenericEntity<MultipartFormDataOutput> data = new GenericEntity<MultipartFormDataOutput>(formOutputData) { }; Response response = target.request().put(Entity.entity(data, MediaType.MULTIPART_FORM_DATA_TYPE)); response.close(); 2.7.12. RESTEasy Atom Support The RESTEasy Atom API and Provider is a simple object model that RESTEasy defines to represent Atom. The main classes for the API are in the org.jboss.resteasy.plugins.providers.atom package. RESTEasy uses Jakarta XML Binding to marshal and unmarshal the API. The provider is Jakarta XML Binding based, and is not limited to sending Atom objects using XML. All Jakarta XML Binding providers that RESTEasy has can be reused by the Atom API and provider, including JSON. import org.jboss.resteasy.plugins.providers.atom.Content; import org.jboss.resteasy.plugins.providers.atom.Entry; import org.jboss.resteasy.plugins.providers.atom.Feed; import org.jboss.resteasy.plugins.providers.atom.Link; import org.jboss.resteasy.plugins.providers.atom.Person; @Path("atom") public class MyAtomService { @GET @Path("feed") @Produces("application/atom+xml") public Feed getFeed() throws URISyntaxException { Feed feed = new Feed(); feed.setId(new URI("http://example.com/42")); feed.setTitle("My Feed"); feed.setUpdated(new Date()); Link link = new Link(); link.setHref(new URI("http://localhost")); link.setRel("edit"); feed.getLinks().add(link); feed.getAuthors().add(new Person("John Brown")); Entry entry = new Entry(); entry.setTitle("Hello World"); Content content = new Content(); content.setType(MediaType.TEXT_HTML_TYPE); content.setText("Nothing much"); entry.setContent(content); feed.getEntries().add(entry); return feed; } } 2.7.12.1. Using Jakarta XML Binding with Atom Provider The org.jboss.resteasy.plugins.providers.atom.Content class allows you to unmarshal and marshal Jakarta XML Binding annotated objects that are the body of the content. Example: Entry with a Customer @XmlRootElement(namespace = "http://jboss.org/Customer") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlElement private String name; public Customer() { } public Customer(String name) { this.name = name; } public String getName() { return name; } } @Path("atom") public static class AtomServer { @GET @Path("entry") @Produces("application/atom+xml") public Entry getEntry() { Entry entry = new Entry(); entry.setTitle("Hello World"); Content content = new Content(); content.setJAXBObject(new Customer("bill")); entry.setContent(content); return entry; } } The Content.setJAXBObject() method lets you specify the content object you send to Jakarta XML Binding to marshal appropriately. If you are using a different base format other than XML, that is application/atom+json , the attached Jakarta XML Binding object is marshalled in the same format. If you have an Atom document as input, you can also extract Jakarta XML Binding objects from Content using the Content.getJAXBObject(Class clazz) method. Example: Atom Document Extracting a Customer Object @Path("atom") public static class AtomServer { @PUT @Path("entry") @Produces("application/atom+xml") public void putCustomer(Entry entry) { Content content = entry.getContent(); Customer cust = content.getJAXBObject(Customer.class); } } 2.7.13. YAML Provider Warning The resteasy-yaml-provider module is not supported. Its use is not recommended due to a security issue in the SnakeYAML library used by RESTEasy for unmarshalling. RESTEasy comes with built in support for YAML using the SnakeYAML library. In releases prior to JBoss EAP 7.1, the YAML provider setting was enabled by default and you only needed to configure the Maven dependencies for YAML to use it in your application. Starting with JBoss EAP 7.1, the YAML provider is disabled by default and must be explicitly enabled in the application. Enable the YAML Provider To enable the YAML provider in your application, follow these steps: Create or update a file named javax.ws.rs.ext.Providers . Add the following content to the file. Place the file in the META-INF/services/ folder of your WAR or JAR file. YAML Provider Maven Dependencies To use the YAML provider in your application, you must add the snakeyaml JAR dependencies to the project POM file of your application. Example: Maven Dependencies for YAML <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-yaml-provider</artifactId> <version>USD{version.org.jboss.resteasy}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>USD{version.org.yaml.snakeyaml}</version> </dependency> YAML Provider Code Example The YAML provider recognizes three mime types: text/x-yaml text/yaml application/x-yaml The following is an example of how to use YAML in a resource method. Example: Resource Producing YAML import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; @Path("/yaml") public class YamlResource { @GET @Produces("text/x-yaml") public MyObject getMyObject() { return createMyObject(); } ... } 2.8. Using Jakarta JSON Processing Jakarta JSON Processing is defined in the Jakarta JSON Processing 1.1 specification . Jakarta JSON Processing defines an API to process JSON. JBoss EAP has support for javax.json.JsonObject , javax.json.JsonArray , and javax.json.JsonStructure as request or response entities. Note Jakarta JSON Processing is different from JSON with Padding (JSONP). Note Jakarta JSON Processing will not conflict with Jackson if they are on the same classpath. To create a JsonObject , use the JsonObjectBuilder by calling Json.createObjectBuilder() and building the JSON object. Example: Create javax.json.JsonObject JsonObject obj = Json.createObjectBuilder().add("name", "Bill").build(); Example: Corresponding JSON for javax.json.JsonObject To create a JsonArray , use the JsonArrayBuilder by calling Json.createArrayBuilder() and building the JSON array. Example: Create javax.json.JsonArray JsonArray array = Json.createArrayBuilder() .add(Json.createObjectBuilder().add("name", "Bill").build()) .add(Json.createObjectBuilder().add("name", "Monica").build()).build(); Example: Corresponding JSON for javax.json.JsonArray JsonStructure is a parent class of JsonObject and JsonArray . Example: Create javax.json.JsonStructure JsonObject obj = Json.createObjectBuilder().add("name", "Bill").build(); JsonArray array = Json.createArrayBuilder() .add(Json.createObjectBuilder().add("name", "Bill").build()) .add(Json.createObjectBuilder().add("name", "Monica").build()).build(); JsonStructure sObj = (JsonStructure) obj; JsonStructure sArray = (JsonStructure) array; You can use JsonObject , JsonArray , and JsonStructure directly in Jakarta RESTful Web Services resources. Example: Jakarta RESTful Web Services Resources with Jakarta JSON Processing @Path("object") @POST @Produces("application/json") @Consumes("application/json") public JsonObject object(JsonObject obj) { // do something return obj; } @Path("array") @POST @Produces("application/json") @Consumes("application/json") public JsonArray array(JsonArray array) { // do something return array; } @Path("structure") @POST @Produces("application/json") @Consumes("application/json") public JsonStructure structure(JsonStructure structure) { // do something return structure; } You can also use Jakarta JSON Processing from a client to send JSON. Example: Client Using Jakarta JSON Processing WebTarget target = client.target(...); JsonObject obj = Json.createObjectBuilder().add("name", "Bill").build(); JsonObject newObj = target.request().post(Entity.json(obj), JsonObject.class); 2.9. RESTEasy/Jakarta Enterprise Beans Integration To integrate RESTEasy with Jakarta Enterprise Beans, add Jakarta RESTful Web Services annotations to the Jakarta Enterprise Beans classes that you want to expose as Jakarta RESTful Web Services endpoints. You can also apply the annotations on the bean's business interface. There are two ways to activate the beans as endpoints: Using the web.xml file. Using javax.ws.rs.core.Application . To make an Jakarta Enterprise Beans function as a Jakarta RESTful Web Services resource, annotate a stateless session bean's @Remote or @Local interface with Jakarta RESTful Web Services annotations: @Local @Path("/Library") public interface Library { @GET @Path("/books/{isbn}") public String getBook(@PathParam("isbn") String isbn); } @Stateless public class LibraryBean implements Library { ... } Note Note that the Library interface is referenced by fully qualified name, whereas LibraryBean is referenced only by the simple class name. Then, manually register the Jakarta Enterprise Beans with RESTEasy using the resteasy.jndi.resources context parameter in the RESTEasy web.xml file: <web-app> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>resteasy.jndi.resources</param-name> <param-value>java:module/LibraryBean!org.app.Library</param-value> </context-param> <listener> <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class> </listener> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app> You can also specify multiple Java Naming and Directory Interface names of Jakarta Enterprise Beans, separated by commas, for the resteasy.jndi.resources context parameter. An alternate Jakarta EE-standard way to activate Jakarta Enterprise Beans as RESTEasy endpoints is by using javax.ws.rs.core.Application . This is achieved by including the Jakarta Enterprise Beans implementation class into the set returned by the application's getClasses() method. This approach does not need anything to be specified in the web.xml file. See the kitchensink , helloworld-html5 , and managed-executor-service quickstarts that ship with JBoss EAP for working examples that demonstrate RESTEasy integration with Jakarta Enterprise Beanss. 2.10. Spring Integration Note Your application must have an existing Jakarta XML Web Services service and client configuration. RESTEasy integrates with Spring 4.2.x. Maven users must use the resteasy-spring artifact. Alternatively, the JAR is available as a module in JBoss EAP. RESTEasy comes with its own Spring ContextLoaderListener that registers a RESTEasy specific BeanPostProcessor that processes Jakarta RESTful Web Services annotations when a bean is created by a BeanFactory . This means that RESTEasy automatically scans for @Provider and Jakarta RESTful Web Services resource annotations on your bean class and registers them as Jakarta RESTful Web Services resources. Add the following to your web.xml file to enable the RESTEasy/Spring integration functionality: <web-app> <display-name>Archetype Created Web Application</display-name> <listener> <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class> </listener> <listener> <listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app> The SpringContextLoaderListener must be declared after ResteasyBootstrap as it uses ServletContext attributes initialized by it. See the spring-resteasy quickstart that ships with JBoss EAP for a working example of a web application that demonstrates RESTEasy integration with Spring. 2.11. Jakarta Contexts and Dependency Injection Integration Integration between RESTEasy and Jakarta Contexts and Dependency Injection is provided by the resteasy-cdi module. Both the Jakarta RESTful Web Services and Jakarta Contexts and Dependency Injection specifications introduce their own component models. Every class placed in a Jakarta Contexts and Dependency Injection archive, which fulfills a set of basic constraints, is implicitly a Jakarta Contexts and Dependency Injection bean. Explicit declaration of your Java class with @Path or @Provider is required for it to become a Jakarta RESTful Web Services component. Without the integration code, annotating a class suitable for being a Jakarta Contexts and Dependency Injection bean with Jakarta RESTful Web Services annotations gives a faulty result and the Jakarta RESTful Web Services component is not managed by the Jakarta Contexts and Dependency Injection. The resteasy-cdi module is a bridge that allows RESTEasy to work with class instances obtained from the Jakarta Contexts and Dependency Injection container. During a web service invocation, the resteasy-cdi module asks the Jakarta Contexts and Dependency Injection container for the managed instance of a Jakarta RESTful Web Services component. Then, this instance is passed to RESTEasy. If a managed instance is not available for some reason, such as the class being placed in a JAR file that is not a bean deployment archive, RESTEasy falls back to instantiating the class itself. As a result, Jakarta Contexts and Dependency Injection services like injection, lifecycle management, events, decoration, and interceptor bindings can be used in Jakarta RESTful Web Services components. 2.11.1. Default Scope A Jakarta Contexts and Dependency Injection bean that does not explicitly define a scope is @Dependent scoped by default. This pseudo-scope means that the bean adapts to the lifecycle of the bean that it is injected into. Normal scopes, including request, session, and application, are more suitable for Jakarta RESTful Web Services components as they designate the component's lifecycle boundaries explicitly. Therefore, the resteasy-cdi module alters the default scoping in the following way: If a Jakarta RESTful Web Services root resource does not define a scope explicitly, it is bound to the request scope. If a Jakarta RESTful Web Services provider or javax.ws.rs.Application subclass does not define a scope explicitly, it is bound to the application scope. Warning Since the scope of all beans that do not declare a scope is modified by the resteasy-cdi module, this affects session beans as well. As a result, a conflict occurs if the scope of a stateless session bean or singleton is changed automatically as the specification prohibits these components to be @RequestScoped . Therefore, you need to explicitly define a scope when using stateless session beans or singletons. This requirement is likely to be removed in future releases. The resteasy-cdi module is bundled with JBoss EAP. Therefore, there is no need to download the module separately or add any additional configuration. See the kitchensink quickstart that ships with JBoss EAP for a working example of using Jakarta Contexts and Dependency Injection beans with a Jakarta RESTful Web Services resource. 2.12. RESTEasy Filters and Interceptors Jakarta RESTful Web Services has two different concepts for interceptions: filters and interceptors. Filters are mainly used to modify or process incoming and outgoing request headers or response headers. They execute before and after request and response processing. 2.12.1. Server-side Filters On the server side, you have two different types of filters: ContainerRequestFilters and ContainerResponseFilters . ContainerRequestFilters run before your Jakarta RESTful Web Services resource method is invoked. ContainerResponseFilters run after your Jakarta RESTful Web Services resource method is invoked. In addition, there are two types of ContainerRequestFilters : pre-matching and post-matching. Pre-matching ContainerRequestFilters are designated with the @PreMatching annotation and execute before the Jakarta RESTful Web Services resource method is matched with the incoming HTTP request. Post-matching ContainerRequestFilters are designated with the @PostMatching annotation and execute after the Jakarta RESTful Web Services resource method is matched with the incoming HTTP request. Pre-matching filters often are used to modify request attributes to change how it matches to a specific resource method, for example to strip .xml and add an Accept header. ContainerRequestFilters can abort the request by calling ContainerRequestContext.abortWith(Response) . For example, a filter might want to abort if it implements a custom authentication protocol. After the resource class method is executed, Jakarta RESTful Web Services runs all ContainerResponseFilters . These filters allow you to modify the outgoing response before it is marshalled and sent to the client. Example: Request Filter public class RoleBasedSecurityFilter implements ContainerRequestFilter { protected String[] rolesAllowed; protected boolean denyAll; protected boolean permitAll; public RoleBasedSecurityFilter(String[] rolesAllowed, boolean denyAll, boolean permitAll) { this.rolesAllowed = rolesAllowed; this.denyAll = denyAll; this.permitAll = permitAll; } @Override public void filter(ContainerRequestContext requestContext) throws IOException { if (denyAll) { requestContext.abortWith(Response.status(403).entity("Access forbidden: role not allowed").build()); return; } if (permitAll) return; if (rolesAllowed != null) { SecurityContext context = ResteasyProviderFactory.getContextData(SecurityContext.class); if (context != null) { for (String role : rolesAllowed) { if (context.isUserInRole(role)) return; } requestContext.abortWith(Response.status(403).entity("Access forbidden: role not allowed").build()); return; } } return; } } Example: Response Filter public class CacheControlFilter implements ContainerResponseFilter { private int maxAge; public CacheControlFilter(int maxAge) { this.maxAge = maxAge; } public void filter(ContainerRequestContext req, ContainerResponseContext res) throws IOException { if (req.getMethod().equals("GET")) { CacheControl cc = new CacheControl(); cc.setMaxAge(this.maxAge); res.getHeaders().add("Cache-Control", cc); } } } 2.12.2. Client-side Filters More information on client-side filters can be found in the Jakarta RESTful Web Services Client API section of this guide. 2.12.3. RESTEasy Interceptors 2.12.3.1. Intercept Jakarta RESTful Web Services Invocations RESTEasy can intercept Jakarta RESTful Web Services invocations and route them through listener-like objects called interceptors. While filters modify request or response headers, interceptors deal with message bodies. Interceptors are executed in the same call stack as their corresponding reader or writer. ReaderInterceptors wrap around the execution of MessageBodyReaders . WriterInterceptors wrap around the execution of MessageBodyWriters . They can be used to implement a specific content-encoding. They can be used to generate digital signatures or to post or pre-process a Java object model before or after it is marshalled. ReaderInterceptors and WriterInterceptors can be used on either the server or client side. They are annotated with @Provider , as well as either @ServerInterceptor or @ClientInterceptor so that RESTEasy knows whether or not to add them to the interceptor list. These interceptors wrap around the invocation of MessageBodyReader.readFrom() or MessageBodyWriter.writeTo() . They can be used to wrap the Output or Input streams. Example: Interceptor @Provider public class BookReaderInterceptor implements ReaderInterceptor { @Inject private Logger log; @Override @ReaderInterceptorBinding public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException { log.info("*** Intercepting call in BookReaderInterceptor.aroundReadFrom()"); VisitList.add(this); Object result = context.proceed(); log.info("*** Back from intercepting call in BookReaderInterceptor.aroundReadFrom()"); return result; } } The interceptors and the MessageBodyReader or Writer are invoked in one big Java call stack. ReaderInterceptorContext.proceed() or WriterInterceptorContext.proceed() are called in order to go to the interceptor or, if there are no more interceptors to invoke, the readFrom() or writeTo() method of the MessageBodyReader or MessageBodyWriter . This wrapping allows objects to be modified before they get to the Reader or Writer , and then cleaned up after proceed() returns. The example below is a server-side interceptor that adds a header value to the response. @Provider public class BookWriterInterceptor implements WriterInterceptor { @Inject private Logger log; @Override @WriterInterceptorBinding public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { log.info("*** Intercepting call in BookWriterInterceptor.aroundWriteTo()"); VisitList.add(this); context.proceed(); log.info("*** Back from intercepting call in BookWriterInterceptor.aroundWriteTo()"); } } 2.12.3.2. Registering an Interceptor To register a RESTEasy Jakarta RESTful Web Services interceptor in an application, list it in the web.xml file under the resteasy.providers parameter in the context-param element, or return it as a class or as an object in the Application.getClasses() or Application.getSingletons() method. <context-param> <param-name>resteasy.providers</param-name> <param-value>my.app.CustomInterceptor</paramvalue> </context-param> package org.jboss.resteasy.example; import javax.ws.rs.core.Application; import java.util.HashSet; import java.util.Set; public class MyApp extends Application { public java.util.Set<java.lang.Class<?>> getClasses() { Set<Class<?>> resources = new HashSet<Class<?>>(); resources.add(MyResource.class); resources.add(MyProvider.class); return resources; } } package org.jboss.resteasy.example; import javax.ws.rs.core.Application; import java.util.HashSet; import java.util.Set; public class MyApp extends Application { protected Set<Object> singletons = new HashSet<Object>(); public MyApp() { singletons.add(new MyResource()); singletons.add(new MyProvider()); } @Override public Set<Object> getSingletons() { return singletons; } } 2.12.4. GZIP Compression and Decompression RESTEasy supports GZIP compression and decompression. To support GZIP decompression, the client framework or a Jakarta RESTful Web Services service automatically decompresses a message body with a Content-Encoding of gzip , and it can automatically set the Accept-Encoding header to gzip, deflate so that you do not have to set this header manually. To support GZIP compression, RESTEasy compresses the outgoing message if the client framework is sending a request or if the server is sending a response with the Content-Encoding header set to gzip . You can use the @org.jboss.resteasy.annotation.GZIP annotation to set the Content-Encoding header. The following example tags the outgoing message body order to be gzip compressed. Example: GZIP Compression @Path("/") public interface MyProxy { @Consumes("application/xml") @PUT public void put(@GZIP Order order); } Example: GZIP Compression Tagging Server Responses @Path("/") public class MyService { @GET @Produces("application/xml") @GZIP public String getData() {...} } 2.12.4.1. Configuring GZIP Compression and Decompression Note RESTEasy disables GZIP compression and decompression by default in order to prevent decompression of an entity that might be huge in size but has been compressed by an attacker and sent to the server. There are three interceptors that are relevant to GZIP compression and decompression: org.jboss.resteasy.plugins.interceptors.GZIPDecodingInterceptor : If the Content-Encoding header is present and has the value gzip , GZIPDecodingInterceptor installs an InputStream that decompresses the message body. org.jboss.resteasy.plugins.interceptors.GZIPEncodingInterceptor : If the Content-Encoding header is present and has the value gzip , GZIPEncodingInterceptor installs an OutputStream that compresses the message body. org.jboss.resteasy.plugins.interceptors.AcceptEncodingGZIPFilter : If the Accept-Encoding header does not exist, AcceptEncodingGZIPFilter adds Accept-Encoding header with the value gzip, deflate . If the Accept-Encoding header exists but does not contain gzip , AcceptEncodingGZIPFilter interceptor appends the value , gzip . Note Enabling GZIP compression or decompression does not depend on the presence of the AcceptEncodingGZIPFilter interceptor. Enabling GZIP decompression sets an upper limit on the number of bytes the GZIPDecodingInterceptor can extract from the compressed message body. The default limit is 10,000,000 . 2.12.4.2. Server-side GZIP Configuration You can enable the interceptors by including their class names in the javax.ws.rs.ext.Providers file on the class path. The upper limit on deflated files is set using the web application context parameter resteasy.gzip.max.input . If this limit is exceeded on the server side, GZIPDecodingInterceptor returns a response with status 413 - Request Entity Too Large and a message specifying the upper limit. 2.12.4.2.1. Client-side GZIP Configuration You can enable the GZIP interceptors by registering them with, for example, a Client or WebTarget . For example: You can configure the upper limit on deflated files by creating an instance of GZIPDecodingInterceptor with a specific value: If the upper limit is exceeded on the client side, GZIPDecodingInterceptor throws a ProcessingException with a message specifying the upper limit. 2.12.5. Per-Resource Method Filters and Interceptors Sometimes you want a filter or interceptor to only run for a specific resource method. You can do this in two different ways: Implement the DynamicFeature interface . Use the @NameBinding annotation . Implement the DynamicFeature Interface The DynamicFeature interface includes a callback method, configure(ResourceInfo resourceInfo, FeatureContext context) , which is invoked for each and every deployed Jakarta RESTful Web Services method. The ResourceInfo parameter contains information about the current Jakarta RESTful Web Services method being deployed. FeatureContext is an extension of the Configurable interface. You can use the register() method of this parameter to bind the filters and interceptors that you want to assign to this method. Example: Using the DynamicFeature Interface @Provider public class AnimalTypeFeature implements DynamicFeature { @Override public void configure(ResourceInfo info, FeatureContext context) { if (info.getResourceMethod().getAnnotation(GET.class) != null) AnimalFilter filter = new AnimalFilter(); context.register(filter); } } } In the example above, the provider that you register using AnimalTypeFeature must implement one of the interfaces. This example registers the provider AnimalFilter that must implement one of the following interfaces: ContainerRequestFilter , ContainerResponseFilter , ReaderInterceptor , WriterInterceptor , or Feature . In this case AnimalFilter will be applied to all resource methods annotated with GET annotation. See DynamicFeature Documentation for details. Use the @NameBinding Annotation @NameBinding works a lot like Jakarta Contexts and Dependency Injection interceptors. You annotate a custom annotation with @NameBinding and then apply that custom annotation to your filter and resource method. Example: Using @NameBinding @NameBinding public @interface DoIt {} @DoIt public class MyFilter implements ContainerRequestFilter {...} @Path("/root") public class MyResource { @GET @DoIt public String get() {...} } See NameBinding Documentation for details. 2.12.6. Ordering Ordering is accomplished by using the @Priority annotation on your filter or interceptor class. 2.12.7. Exception Handling with Filters and Interceptors Exceptions associated with filters or interceptors can occur on either the client side or the server side. On the client side, there are two types of exceptions you will have to handle: javax.ws.rs.client.ProcessingException and javax.ws.rs.client.ResponseProcessingException . A javax.ws.rs.client.ProcessingException will be thrown on the client side if there was an error before a request is sent to the server. A javax.ws.rs.client.ResponseProcessingException will be thrown on the client side if there was an error in processing the response received by the client from the server. On the server side, exceptions thrown by filters or interceptors are handled in the same way as other exceptions thrown from Jakarta RESTful Web Services methods, which tries to find an ExceptionMapper for the exception being thrown. More details on how exceptions are handled in Jakarta RESTful Web Services methods can be found in the Exception Handling section. 2.13. Logging RESTEasy Providers and Interceptors RESTEasy logs the used providers and interceptors in the DEBUG level of logging. You can use the following management CLI commands to enable all the log levels related to RESTEasy: 2.14. Exception Handling 2.14.1. Creating an Exception Mapper Exception mappers are custom components provided by applications that catch thrown exceptions and write specific HTTP responses. When you create an exception mapper, you create a class that is annotated with the @Provider annotation and implements the ExceptionMapper interface. An example exception mapper is provided below: @Provider public class EJBExceptionMapper implements ExceptionMapper<javax.ejb.EJBException> { public Response toResponse(EJBException exception) { return Response.status(500).build(); } } To register an exception mapper, list it in the web.xml file, under the resteasy.providers context-param , or register it programmatically through the ResteasyProviderFactory class. 2.14.2. Managing Internally Thrown Exceptions Table 2.2. Exception List Exception HTTP Code Description BadRequestException 400 Bad Request. The request was not formatted correctly, or there was a problem processing the request input. UnauthorizedException 401 Unauthorized. Security exception thrown if you are using RESTEasy's annotation-based role-based security. InternalServerErrorException 500 Internal Server Error. MethodNotAllowedException 405 There is no Jakarta RESTful Web Services method for the resource to handle the invoked HTTP operation. NotAcceptableException 406 There is no Jakarta RESTful Web Services method that can produce the media types listed in the Accept header. NotFoundException 404 There is no Jakarta RESTful Web Services method that serves the request path/resource. ReaderException 400 All exceptions thrown from MessageBodyReaders are wrapped within this exception. If there is no ExceptionMapper for the wrapped exception, or if the exception is not a WebApplicationException, then by default, RESTEasy returns a 400 code. WriterException 500 All exceptions thrown from MessageBodyWriters are wrapped within this exception. If there is no ExceptionMapper for the wrapped exception, or if the exception is not a WebApplicationException, then by default, RESTEasy returns a 400 code. JAXBUnmarshalException 400 The Jakarta XML Binding providers (XML and Jackson) throw this exception on reads which might wrap JAXBExceptions. This class extends ReaderException. JAXBMarshalException 500 The Jakarta XML Binding providers (XML and Jackson) throw this exception on writes which might wrap JAXBExceptions. This class extends WriterException. ApplicationException N/A Wraps all exceptions thrown from application code, and it functions in the same way as InvocationTargetException. If there is an ExceptionMapper for wrapped exception, then that is used to handle the request. Failure N/A Internal RESTEasy error. Not logged. LoggableFailure N/A Internal RESTEasy error. Logged. DefaultOptionsMethodException N/A If the user invokes HTTP OPTIONS and no Jakarta RESTful Web Services method for it, RESTEasy provides a default behavior by throwing this exception. UnrecognizedPropertyExceptionHandler 400 RESTEasy Jackson provider throws this exception when JSON data is determined to be invalid. 2.15. Securing Jakarta RESTful Web Services Web Services RESTEasy supports the @RolesAllowed , @PermitAll , and @DenyAll annotations on Jakarta RESTful Web Services methods. However, you must enable role-based security in order for these annotations to be recognized. 2.15.1. Enable Role-Based Security Follow these steps to configure the web.xml file to enable role-based security. Warning Do not activate role-based security if the application uses Jakarta Enterprise Beans. The Jakarta Enterprise Beans container will provide the functionality, instead of RESTEasy. Enable Role-Based Security for a RESTEasy Jakarta RESTful Web Services Web Service Open the web.xml file for the application in a text editor. Add the following <context-param> to the file, within the <web-app> tags. <context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param> Declare all roles used within the RESTEasy Jakarta RESTful Web Services WAR file, using the <security-role> tags. <security-role> <role-name> ROLE_NAME </role-name> </security-role> <security-role> <role-name> ROLE_NAME </role-name> </security-role> Authorize access to all URLs handled by the Jakarta RESTful Web Services runtime for all roles. <security-constraint> <web-resource-collection> <web-resource-name>Resteasy</web-resource-name> <url-pattern> /PATH </url-pattern> </web-resource-collection> <auth-constraint> <role-name> ROLE_NAME </role-name> <role-name> ROLE_NAME </role-name> </auth-constraint> </security-constraint> Define the appropriate login configuration for this application. <login-config> <auth-method>BASIC</auth-method> <realm-name>jaxrs</realm-name> </login-config> Role-based security has been enabled within the application, with a set of defined roles. Example: Role-Based Security Configuration <web-app> <context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param> <security-constraint> <web-resource-collection> <web-resource-name>Resteasy</web-resource-name> <url-pattern>/security</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> <role-name>user</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>jaxrs</realm-name> </login-config> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>user</role-name> </security-role> </web-app> 2.15.2. Securing Jakarta RESTful Web Services Web Services Using Annotations To secure Jakarta RESTful Web Services web services using an annotation, complete the following steps. Enable role-based security . Add security annotations to the Jakarta RESTful Web Services web service. RESTEasy supports the following annotations: @RolesAllowed Defines which roles can access the method. All roles should be defined in the web.xml file. @PermitAll Allows all roles defined in the web.xml file to access the method. @DenyAll Denies all access to the method. Below is an example that uses the @RolesAllowed annotation to specify that the admin role can access the web service. @RolesAllowed("admin") @Path("/test") public class TestService { ... } 2.15.3. Setting Programmatic Security Jakarta RESTful Web Services includes a programmatic API for gathering security information about a secured request. The javax.ws.rs.core.SecurityContext interface has a method for determining the identity of the user making the secured HTTP invocation. It also has a method that allows you to check whether or not the current user belongs to a certain role: public interface SecurityContext { public Principal getUserPrincipal(); public boolean isUserInRole(String role); public boolean isSecure(); public String getAuthenticationScheme(); } You can access a SecurityContext instance by injecting it into a field, setter method, or resource method parameter using the @Context annotation. @Path("test") public class SecurityContextResource { @Context SecurityContext securityContext; @GET @Produces("text/plain") public String get() { if (!securityContext.isUserInRole("admin")) { throw new WebApplicationException(Response.serverError().status(HttpResponseCodes.SC_UNAUTHORIZED) .entity("User " + securityContext.getUserPrincipal().getName() + " is not authorized").build()); } return "Good user " + securityContext.getUserPrincipal().getName(); } } 2.16. RESTEasy Asynchronous Job Service The RESTEasy Asynchronous Job Service is designed to add asynchronous behavior to the HTTP protocol. While HTTP is a synchronous protocol, it is aware of asynchronous invocations. The HTTP 1.1 response code 202 Accepted means that the server has received and accepted the response for processing, but the processing has not yet been completed. The Asynchronous Job Service builds around this. 2.16.1. Enabling the Asynchronous Job Service Enable the asynchronous job service in the web.xml file: <context-param> <param-name>resteasy.async.job.service.enabled</param-name> <param-value>true</param-value> </context-param> 2.16.2. Configuring Asynchronous Jobs This section covers examples of the query parameters for asynchronous jobs with RESTEasy. Warning Role based security does not work with the Asynchronous Job Service as it cannot be implemented portably. If the Asynchronous Job Service is used, application security must be done through XML declarations in the web.xml file instead. Important While GET, DELETE, and PUT methods can be invoked asynchronously, this breaks the HTTP 1.1 contract of these methods. While these invocations might not change the state of the resource if invoked more than once, they do change the state of the server as new job entries with each invocation. The asynch query parameter is used to run invocations in the background. A 202 Accepted response is returned, as well as a location header with a URL pointing to where the response of the background method is located. The example above returns a 202 Accepted response. It also returns a location header with a URL pointing to where the response of the background method is located. An example of the location header is shown below: The URI will take the form of: GET, POST and DELETE operations can be performed on this URL. GET returns the Jakarta RESTful Web Services resource method invoked as a response if the job was completed. If the job has not been completed, the GET returns a 202 Accepted response code. Invoking GET does not remove the job, so it can be called multiple times. POST does a read of the job response and removes the job if it has been completed. DELETE is called to manually clean up the job queue. Note When the job queue is full, it evicts the earliest job from memory automatically without needing to call DELETE. The GET and POST operations allow for the maximum wait time to be defined, using the wait and nowait query parameters. If the wait parameter is not specified, the operation will default to nowait=true , and will not wait at all if the job is not complete. The wait parameter is defined in milliseconds. RESTEasy supports fire and forget jobs, using the oneway query parameter. The example above returns a 202 Accepted response, but no job is created. Note The configuration parameters for the Asynchronous Job Service can be found in the RESTEasy Asynchronous Job Service Configuration Parameters section in the appendix. 2.17. RESTEasy JavaScript API 2.17.1. About the RESTEasy JavaScript API RESTEasy can generate a JavaScript API that uses AJAX calls to invoke Jakarta RESTful Web Services operations. Each Jakarta RESTful Web Services resource class will generate a JavaScript object of the same name as the declaring class or interface. The JavaScript object contains each Jakarta RESTful Web Services method as properties. @Path("foo") public class Foo { @Path("{id}") @GET public String get(@QueryParam("order") String order, @HeaderParam("X-Foo") String header, @MatrixParam("colour") String colour, @CookieParam("Foo-Cookie") String cookie) { } @POST public void post(String text) { } } The following JavaScript code uses the Jakarta RESTful Web Services API that was generated in the example. var text = Foo.get({order: 'desc', 'X-Foo': 'hello', colour: 'blue', 'Foo-Cookie': 123987235444}); Foo.post({USDentity: text}); Each JavaScript API method takes an optional object as single parameter where each property is a cookie, header, path, query or form parameter as identified by its name, or the API parameter properties. For details about the API parameter properties see RESTEasy Javascript API Parameters appendix. 2.17.1.1. Enable the RESTEasy JavaScript API Servlet The RESTEasy JavaScript API is disabled by default. Follow these steps to enable it by updating the web.xml file. Open the web.xml file of the application in a text editor. Add the following configuration to the file, inside the web-app tags: <servlet> <servlet-name>RESTEasy JSAPI</servlet-name> <servlet-class>org.jboss.resteasy.jsapi.JSAPIServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>RESTEasy JSAPI</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> 2.17.1.2. Build AJAX Queries The RESTEasy JavaScript API can be used to manually construct requests. The following are some examples of this behavior. Example: REST Object Used to Override RESTEasy JavaScript API Client Behavior // Change the base URL used by the API: REST.apiURL = "http://api.service.com"; // log everything in a div element REST.log = function(text) { jQuery("#log-div").append(text); }; The REST object contains the following read-write properties: apiURL : Set by default to the Jakarta RESTful Web Services root URL. Used by every JavaScript client API functions when constructing the requests. log : Set to function(string) in order to receive RESTEasy client API logs. This is useful if you want to debug your client API and place the logs where you can see them. Example: Class Using REST.Request() Method to Build Custom Requests var r = new REST.Request(); r.setURI("http://api.service.com/orders/23/json"); r.setMethod("PUT"); r.setContentType("application/json"); r.setEntity({id: "23"}); r.addMatrixParameter("JSESSIONID", "12309812378123"); r.execute(function(status, request, entity) { log("Response is " + status); }); 2.18. RESTEasy SPI to Modify Resource Metadata JBoss EAP provides a RESTEasy service provider interface (SPI) to modify resource class metadata, which is created using ResourceBuilder . When processing Jakarta RESTful Web Services deployments, RESTEasy uses ResourceBuilder to create metadata for each Jakarta RESTful Web Services resource. Such metadata is defined using the metadata SPI in package org.jboss.resteasy.spi.metadata , in particular the ResourceClass interface: package org.jboss.resteasy.spi.metadata; public interface ResourceClass { String getPath(); Class<?> getClazz(); ResourceConstructor getConstructor(); FieldParameter[] getFields(); SetterParameter[] getSetters(); ResourceMethod[] getResourceMethods(); ResourceLocator[] getResourceLocators(); } RESTEasy allows customizing the metadata generation by providing implementations of the ResourceClassProcessor interface. The following example illustrates the usage of this SPI: package org.jboss.resteasy.test.core.spi.resource; import org.jboss.logging.Logger; import org.jboss.resteasy.spi.metadata.ResourceClass; import org.jboss.resteasy.spi.metadata.ResourceClassProcessor; import javax.ws.rs.ext.Provider; @Provider public class ResourceClassProcessorImplementation implements ResourceClassProcessor { protected static final Logger logger = Logger.getLogger(ResourceClassProcessorImplementation.class.getName()); @Override public ResourceClass process(ResourceClass clazz) { logger.info(String.format("ResourceClassProcessorImplementation#process method called on class %s", clazz.getClazz().getSimpleName())); String clazzName = clazz.getClazz().getSimpleName(); if (clazzName.startsWith("ResourceClassProcessorEndPoint") || clazzName.equals("ResourceClassProcessorProxy") || clazzName.equals("ResourceClassProcessorProxyEndPoint")) { return new ResourceClassProcessorClass(clazz); } return clazz; } The new processors, which are stored using the ResteasyProviderFactory class, are resolved as regular Jakarta RESTful Web Services annotated providers. They allow wrapping resource metadata classes with custom versions that you can use for various advanced scenarios, such as: Adding additional resource method or locators to the resource. Modifying the HTTP methods. Modifying the @Produces or the @Consumes media types. 2.19. MicroProfile REST Client Important MicroProfile REST client is provided as Technology Preview only. Technology Preview features are not supported with Red Hat production service level agreements (SLAs), might not be functionally complete, and Red Hat does not recommend to use them for production. These features provide early access to upcoming product features, enabling customers to test functionality and provide feedback during the development process. See Technology Preview Features Support Scope on the Red Hat Customer Portal for information about the support scope for Technology Preview features. JBoss EAP 7.4 supports the MicroProfile REST client 1.4.x that builds on Jakarta RESTful Web Services 2.1 client APIs to provide a type-safe approach to invoke RESTful services over HTTP. The MicroProfile Type Safe REST clients are defined as Java interfaces. With the MicroProfile REST clients, you can write client applications with executable code. The MicroProfile REST client enables: An intuitive syntax Programmatic registration of providers Declarative registration of providers Declarative specification of headers Propagation of headers on the server ResponseExceptionMapper Jakarta Contexts and Dependency Injection integration 2.19.1. Intuitive Syntax The MicroProfile REST client enables a version of distributed object communication, which is also implemented in CORBA, Java Remote Method Invocation (RMI), the JBoss Remoting Project, and RESTEasy. For example, consider the resource: @Path("resource") public class TestResource { @Path("test") @GET String test() { return "test"; } } The Jakarta RESTful Web Services native way of accessing the TestResource class is: Client client = ClientBuilder.newClient(); String response = client.target("http://localhost:8081/test").request().get(String.class); However, Microprofile REST client supports a more intuitive syntax by directly calling the test() method: @Path("resource") public interface TestResourceIntf { @Path("test") @GET public String test(); } TestResourceIntf service = RestClientBuilder.newBuilder() .baseUrl(http://localhost:8081/)) .build(TestResourceIntf.class); String s = service.test(); In the example above, making calls on the TestResource class becomes much easier with the TestResourceIntf class, as illustrated by the call service.test() . The following example is a more elaborate version of the TestResourceIntf class: @Path("resource") public interface TestResourceIntf2 { @Path("test/{path}")mes("text/plain") @Produces("text/html") @POST public String test(@PathParam("path") String path, @QueryParam("query") String query, String entity); } Calling the service.test("p", "q", "e") method results in an HTTP message that looks like: POST /resource/test/p/?query=q HTTP/1.1 Accept: text/html Content-Type: text/plain Content-Length: 1 e 2.19.2. Programmatic Registration of Providers With the MicroProfile REST client, you can also configure the client environment by registering providers. For example: TestResourceIntf service = RestClientBuilder.newBuilder() .baseUrl(http://localhost:8081/)) .register(MyClientResponseFilter.class) .register(MyMessageBodyReader.class) .build(TestResourceIntf.class); 2.19.3. Declarative Registration of Providers You can also register providers declaratively by adding the org.eclipse.microprofile.rest.client.annotation.RegisterProvider annotation to the target interface as shown below: @Path("resource") @RegisterProvider(MyClientResponseFilter.class) @RegisterProvider(MyMessageBodyReader.class) public interface TestResourceIntf2 { @Path("test/{path}") @Consumes("text/plain") @Produces("text/html") @POST public String test(@PathParam("path") String path, @QueryParam("query") String query, String entity); } Declaring the MyClientResponseFilter class and the MyMessageBodyReader class with annotations eliminates the need to call the RestClientBuilder.register() method. 2.19.4. Declarative Specification of Headers You can specify a header for an HTTP request in the following ways: By annotating one of the resource method parameters. By declaratively using the org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam annotation. The following example illustrates setting a header by annotating one of the resource method parameters with the annotation @HeaderValue : @POST @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.TEXT_PLAIN) String contentLang(@HeaderParam(HttpHeaders.CONTENT_LANGUAGE) String contentLanguage, String subject); The following example illustrates setting a header using the org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam annotation: @POST @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.TEXT_PLAIN) @ClientHeaderParam(name=HttpHeaders.CONTENT_LANGUAGE, value="{getLanguage}") String contentLang(String subject); default String getLanguage() { return ...; } 2.19.5. Propagation of Headers on the Server An instance of org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory , if activated, can do a bulk transfer of incoming headers to an outgoing request. The default instance org.eclipse.microprofile.rest.client.ext.DefaultClientHeadersFactoryImpl returns a map consisting of those incoming headers that are listed in the comma-separated configuration property org.eclipse.microprofile.rest.client.propagateHeaders . The following are the rules for instantiating the ClientHeadersFactory interface: A ClientHeadersFactory instance invoked in the context of a Jakarta RESTful Web Services request must support injection of fields and methods annotated with @Context . A ClientHeadersFactory instance that is managed by Jakarta Contexts and Dependency Injection must use the appropriate Jakarta Contexts and Dependency Injection-managed instance. It must also support the @Inject injection. The org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory interface is defined as follows: public interface ClientHeadersFactory { /** * Updates the HTTP headers to send to the remote service. Note that providers * on the outbound processing chain could further update the headers. * * @param incomingHeaders - the map of headers from the inbound Jakarta RESTful Web Services request. This will * be an empty map if the associated client interface is not part of a Jakarta RESTful Web Services request. * @param clientOutgoingHeaders - the read-only map of header parameters specified on the * client interface. * @return a map of HTTP headers to merge with the clientOutgoingHeaders to be sent to * the remote service. */ MultivaluedMap<String, String> update(MultivaluedMap<String, String> incomingHeaders, MultivaluedMap<String, String> clientOutgoingHeaders); } For more information about the ClientHeadersFactory interface, see ClientHeadersFactory Javadoc . 2.19.6. ResponseExceptionMapper The org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper class is the client-side inverse of the javax.ws.rs.ext.ExceptionMapper class defined in Jakarta RESTful Web Services. That is, where the ExceptionMapper.toResponse() method turns an Exception class thrown during the server-side processing into a Response class, the ResponseExceptionMapper.toThrowable() method turns a Response class received on the client-side with an HTTP error status into an Exception class. You can register the ResponseExceptionMapper class either programmatically or declaratively. In the absence of a registered ResponseExceptionMapper class, a default ResponseExceptionMapper class maps any response with status >= 400 to a WebApplicationException class. 2.19.7. Jakarta Contexts and Dependency Injection Integration In MicroProfile REST client, you must annotate any interface that is managed as a Jakarta Contexts and Dependency Injection bean with the @RegisterRestClient class. For example: @Path("resource") @RegisterProvider(MyClientResponseFilter.class) public static class TestResourceImpl { @Inject TestDataBase db; @Path("test/{path}") @Consumes("text/plain") @Produces("text/html") @POST public String test(@PathParam("path") String path, @QueryParam("query") String query, String entity) { return db.getByName(query); } } @Path("database") @RegisterRestClient public interface TestDataBase { @Path("") @POST public String getByName(String name); } Here, the MicroProfile REST client implementation creates a client for a TestDataBase class service, allowing easy access by the TestResourceImpl class. However, it does not include the information about the path to the TestDataBase class implementation. This information can be supplied by the optional @Reg- isterProvider parameter baseUri : @Path("database") @RegisterRestClient(baseUri="https://localhost:8080/webapp") public interface TestDataBase { @Path("") @POST public String getByName(String name); } This indicates that you can access the implementation of TestDataBase at https://localhost:8080/webapp . You can also supply the information externally with the following system variable: <fully qualified name of TestDataBase>/mp-rest/url=<URL> For example, the following indicates that you can access an implementation of the com.bluemonkeydiamond.TestDatabase class at https://localhost:8080/webapp: com.bluemonkeydiamond.TestDatabase/mp-rest/url=https://localhost:8080/webapp 2.20. Support for the CompletionStage Type The Jakarta RESTful Web Services 2.1 specification supports declaring asynchronous resource methods by returning a CompletionStage instead of using the @Suspended annotation. Whenever a resource method returns a CompletionStage that it subscribed to, the request is suspended. The request is resumed only when the CompletionStage type is: Resolved to a value, which is then treated as the return value for the method. Treated as an error case, and the exception is processed as if it were thrown by the resource method. The following is an example of asynchronous processing using CompletionStage : public class SimpleResource { @GET @Path("basic") @Produces("text/plain") public CompletionStage<Response> getBasic() throws Exception { final CompletableFuture<Response> response = new CompletableFuture<>(); Thread t = new Thread() { @Override public void run() { try { Response jaxrs = Response.ok("basic").type(MediaType.TEXT_PLAIN).build(); response.complete(jaxrs); } catch (Exception e) { response.completeExceptionally(e); } } }; t.start(); return response; } } 2.21. Extending RESTEasy Support for Asynchronous Request Processing and Reactive Return Types Important Extending RESTEasy support is provided as Technology Preview only. Technology Preview features are not supported with Red Hat production service level agreements (SLAs), might not be functionally complete, and Red Hat does not recommend to use them for production. These features provide early access to upcoming product features, enabling customers to test functionality and provide feedback during the development process. See Technology Preview Features Support Scope on the Red Hat Customer Portal for information about the support scope for Technology Preview features. 2.21.1. Pluggable Reactive Types Jakarta RESTful Web Services 2.1 is extensible to support various reactive libraries. RESTEasy's optional module resteasy-rxjava2 supports the following reactive types: io.reactivex.Single : Similar to CompletionStage , because it holds one potential value at the most. io.reactivex.Flowable : Implements io.reactivex.Publisher . io.reactivex.Observable : Similar to Flowable , except that it does not support backpressure, the ability of a subscriber to control the load it receives from a producer by calling Subscription.request() . If you import resteasy-rxjava2 , you can return these reactive types from your resource methods on the server side and receive them on the client side. The resteasy-rxjava2 module supports the following three classes to access Singles, Observables, and Flowables respectively on the client side: org.jboss.resteasy.rxjava2.SingleRxInvoker org.jboss.resteasy.rxjava2.FlowableRxInvoker org.jboss.resteasy.rxjava2.ObservableRxInvoker 2.21.2. Extensions for Additional Reactive Classes RESTEasy implements a framework that supports extensions for additional reactive classes. On the server side, when a resource method returns a CompletionStage type, RESTEasy subscribes to it using the org.jboss.resteasy.core.AsyncResponseConsumer.CompletionStageResponseConsumer class. When the CompletionStage completes, it calls CompletionStageResponseConsumer.accept() , which sends the result back to the client. Support for CompletionStage is built in to RESTEasy. We can extend that support to a class like Single by providing a mechanism for transforming a Single into a CompletionStage . In the resteasy-rxjava2 module, org.jboss.resteasy.rxjava2.SingleProvider , which implements the org.jboss.resteasy.spi.AsyncResponseProvider<Single<?>> interface provides this mechanism: public interface AsyncResponseProvider<T> { public CompletionStage toCompletionStage(T asyncResponse); } Given the SingleProvider class, RESTEasy can take a Single , transform it into a CompletionStage and then use CompletionStageResponseConsumer to handle the eventual value of the Single . Similarly, when a resource method returns a streaming reactive class like Flowable , RESTEasy subscribes to it, receives a stream of data elements, and sends them to the client. AsyncResponseConsumer has several supporting classes, each of which implements a different mode of streaming. For example, AsyncResponseConsumer.AsyncGeneralStreamingSseResponseConsumer handles general streaming and SSE streaming. Subscription is done by calling org.reactivestreams.Publisher.subscribe() , so it needs a mechanism to turn a Flowable into a Publisher , for example. That is, an implementation of org.jboss.resteasy.spi.AsyncStreamProvider<Flowable> is called for, which defines AsyncStreamProvider as shown in the following example: public interface AsyncStreamProvider<T> { public Publisher toAsyncStream(T asyncResponse); } In the resteasy-rxjava2 module, org.jboss.resteasy.FlowableProvider provides that mechanism for Flowable . That means, on the server side, you can add support for other reactive types by declaring a @Provider annotation for the AsyncStreamProvider interface for streams or the AsyncResponseProvider interface for single values. Both these interfaces have a single method to convert the new reactive type into a Publisher or a CompletionStage for streams or for single values respectively. On the client side, the Jakarta RESTful Web Services 2.1 imposes two requirements for support of the reactive classes: Support for CompletionStage as an implementation of the javax.ws.rs.client.CompletionStageRxInvoker interface. Extensibility by supporting the registration of providers that implement: public interface RxInvokerProvider<T extends RxInvoker> { public boolean isProviderFor(Class<T> clazz); public T getRxInvoker(SyncInvoker syncInvoker, ExecutorService executorService); } Once an RxInvokerProvider is registered, you can request an RxInvoker by calling the javax.ws.rs.client.Invocation.Builder method: public <T extends RxInvoker> T rx(Class<T> clazz); You can use the RxInvoker for making an invocation that returns the appropriate reactive class. For example: FlowableRxInvoker invoker = client.target(generateURL("/get/string")).request().rx(FlowableRxInvoker.class); Flowable<String> flowable = (Flowable<String>) invoker.get(); RESTEasy provides partial support for implementing RxInvokers . For example, SingleProvider , mentioned above, also implements org.jboss.resteasy.spi.AsyncClientResponseProvider<Single<?>> , where AsyncClientResponseProvider is defined as the following: public interface AsyncClientResponseProvider<T> { public T fromCompletionStage(CompletionStage<?> completionStage); } 2.21.3. Reactive Clients API RESTEasy defines a new type of invoker named RxInvoker , and a default implementation of this type named CompletionStageRxInvoker . CompletionStageRxInvoker implements Java 8's interface CompletionStage . This interface declares a large number of methods dedicated to managing asynchronous computations. 2.21.4. Asynchronous Filters If you must suspend execution of your filter until a certain resource is available, you can convert it into an asynchronous filter. Turning a request asynchronous does not require any change to your resource method declaration or the additional filter declaration. To turn a filter's execution asynchronous, you must cast: The ContainerRequestContext into SuspendableContainerRequestContext for pre and post request filters. The ContainerResponseContext into a SuspendableContainerResponseContext for response filters. These context objects can turn the current filter's execution into asynchronous by calling the suspend() method. Once asynchronous, the filter chain is suspended, and resumes only after one of the following methods is called on the context object: abortWith(Response) : Terminate the filter chain, return the given Response to the client. This applies only to ContainerRequestFilter. resume() : Resume execution of the filter chain by calling the filter. resume(Throwable) : Abort execution of the filter chain by throwing the given exception. This behaves as if the filter were synchronous and threw the given exception. 2.21.5. Proxies Proxies are a RESTEasy extension that supports an intuitive programming style, which replaces generic Jakarta RESTful Web Services invoker calls with application-specific interface calls. The proxy framework is extended to include both CompletionStage and the RxJava2 types Single , Observable , and Flowable . The two following examples illustrate how RESTEasy proxies work: Example 1: @Path("") public interface RxCompletionStageResource { @GET @Path("get/string") @Produces(MediaType.TEXT_PLAIN) public CompletionStage<String> getString(); } @Path("") public class RxCompletionStageResourceImpl { @GET @Path("get/string") @Produces(MediaType.TEXT_PLAIN) public CompletionStage<String> getString() { .... } } public class RxCompletionStageProxyTest { private static ResteasyClient client; private static RxCompletionStageResource proxy; static { client = new ResteasyClientBuilder().build(); proxy = client.target(generateURL("/")).proxy(RxCompletionStageResource.class); } @Test public void testGet() throws Exception { CompletionStage<String> completionStage = proxy.getString(); Assert.assertEquals("x", completionStage.toCompletableFuture().get()); } } Example 2: public interface Rx2FlowableResource { @GET @Path("get/string") @Produces(MediaType.TEXT_PLAIN) @Stream public Flowable<String> getFlowable(); } @Path("") public class Rx2FlowableResourceImpl { @GET @Path("get/string") @Produces(MediaType.TEXT_PLAIN) @Stream public Flowable<String> getFlowable() { ... } } public class Rx2FlowableProxyTest { private static ResteasyClient client; private static Rx2FlowableResource proxy; static { client = new ResteasyClientBuilder().build(); proxy = client.target(generateURL("/")).proxy(Rx2FlowableResource.class); } @Test public void testGet() throws Exception { Flowable<String> flowable = proxy.getFlowable(); flowable.subscribe( (String o) -> stringList.add(o), (Throwable t) -> errors.incrementAndGet(), () -> latch.countDown()); boolean waitResult = latch.await(30, TimeUnit.SECONDS); Assert.assertTrue("Waiting for event to be delivered has timed out.", waitResult); Assert.assertEquals(0, errors.get()); Assert.assertEquals(xStringList, stringList); } }

Commands:
import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/root-path") public class MyApplication extends Application { }
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <servlet> <servlet-name>javax.ws.rs.core.Application</servlet-name> </servlet> <servlet-mapping> <servlet-name>javax.ws.rs.core.Application</servlet-name> <url-pattern>/root-path/*</url-pattern> </servlet-mapping> </web-app>
Client client = ClientBuilder.newClient();
Client client = ClientBuilder.newBuilder().build(); WebTarget target = client.target("http://foo.com/resource"); Response response = target.request().get(); String value = response.readEntity(String.class); response.close(); // You should close connections!
ResteasyClient client = new ResteasyClientBuilder().build(); ResteasyWebTarget target = client.target("http://foo.com/resource");
import javx.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Client; Client client = ClientBuilder.newBuilder() .connectTimeout(100, TimeUnit.SECONDS) .readTimeout(2, TimeUnit.SECONDS) .build();
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> <version> VERSION_IN_EAP </version> </dependency>
// execute request filters for (ClientRequestFilter filter : requestFilters) { filter.filter(requestContext); if (isAborted(requestContext)) { return requestContext.getAbortedResponseObject(); } } // send request over the wire response = sendRequest(request); // execute response filters for (ClientResponseFilter filter : responseFilters) { filter.filter(requestContext, responseContext); }
client = ClientBuilder.newClient(); WebTarget base = client.target(generateURL("/") + "get"); base.register(ClientExceptionsCustomClientResponseFilter.class).request("text/plain").get();
ResteasyWebTarget target = client.target(generateBaseUrl()); target.register(BrowserCacheFeature.class);
ResteasyClient client = new ResteasyClientBuilder().build(); ResteasyWebTarget target = client.target("http://localhost:8081/test"); target.setChunked(b.booleanValue()); Invocation.Builder request = target.request();
ResteasyClient client = new ResteasyClientBuilder().build(); ResteasyWebTarget target = client.target("http://localhost:8081/test"); ClientInvocationBuilder request = (ClientInvocationBuilder) target.request(); request.setChunked(b);
ResteasyClient client = new ResteasyClientBuilder().httpEngine(engine).build();
// Configure HttpClient to authenticate preemptively // by prepopulating the authentication data cache. // 1. Create AuthCache instance AuthCache authCache = new BasicAuthCache(); // 2. Generate BASIC scheme object and add it to the local auth cache AuthScheme basicAuth = new BasicScheme(); authCache.put(new HttpHost("sippycups.bluemonkeydiamond.com"), basicAuth); // 3. Add AuthCache to the execution context BasicHttpContext localContext = new BasicHttpContext(); localContext.setAttribute(ClientContext.AUTH_CACHE, authCache); // 4. Create client executor and proxy HttpClient httpClient = HttpClientBuilder.create().build(); ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient, localContext); ResteasyClient client = new ResteasyClientBuilder().httpEngine(engine).build();
WebTarget target = client.target("http://localhost:8081/customer/123"); Response response = target.request().get(); System.out.println(response.getStatus()); response.close();
HttpClient httpClient = new HttpClientBuilder.create().build(); ApacheHttpClient4Engine executor = new ApacheHttpClient4Engine(httpClient); httpClient.getConnectionManager().shutdown();
ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(); engine.setFollowRedirects(true); Client client = new ResteasyClientBuilder().httpEngine(engine).build();
Client asyncClient = new ResteasyClientBuilder().useAsyncHttpEngine() .build(); Future<Response> future = asyncClient .target("http://locahost:8080/test").request() .async().get(); Response res = future.get(); Assert.assertEquals(HttpResponseCodes.SC_OK, res.getStatus()); String entity = res.readEntity(String.class);
import javax.ws.rs.container.Suspended; import javax.ws.rs.container.AsyncResponse; @Path("/") public class SimpleResource { @GET @Path("basic") @Produces("text/plain") public void getBasic(@Suspended final AsyncResponse response) throws Exception { Thread t = new Thread() { @Override public void run() { try { Response jaxrs = Response.ok("basic").type(MediaType.TEXT_PLAIN).build(); response.resume(jaxrs); } catch (Exception e) { e.printStackTrace(); } } }; t.start(); } }
import java.util.concurrent.Future; import javax.ws.rs.client.Client; @Test public void AsyncGetTest() throws Exception { Client client = ClientBuilder.newClient(); Future<String> future = client.target(generateURL("/test")).request().async().get(String.class); String entity = future.get(); Assert.assertEquals("get", entity); }
import javax.ws.rs.client.InvocationCallback; @Test public void AsyncCallbackGetTest() throws Exception { Client client = ClientBuilder.newClient(); final CountDownLatch latch = new CountDownLatch(1); Future<Response> future = client.target(generateURL("/test")).request().async().get(new InvocationCallback<Response>() { @Override public void completed(Response response) { String entity = response.readEntity(String.class); Assert.assertEquals("get", entity); latch.countDown(); } @Override public void failed(Throwable error) { } }); Response res = future.get(); Assert.assertEquals(HttpResponseCodes.SC_OK, res.getStatus()); Assert.assertTrue("Asynchronous invocation didn't use custom implemented Invocation callback", latch.await(5, imeUnit.SECONDS)); }
<properties> <maven.compiler.parameters>true</maven.compiler.parameters> </properties>
import org.jboss.resteasy.annotations.jaxrs.*; @Path("/library") public class Library { @GET @Path("/book/{isbn}") public String getBook(@PathParam String isbn) { // search my database and get a string representation and return it } }
import org.jboss.resteasy.annotations.jaxrs.*; @Path("/library") public class Library { @GET @Path("/book/{isbn}") public String getBook(@PathParam("isbn") String id) { // search my database and get a string representation and return it } }
/deployment= DEPLOYMENT_NAME /subsystem=jaxrs/rest-resource=org.jboss.as.quickstarts.rshelloworld.HelloWorld:read-resource(include-runtime=true) { "outcome" => "success", "result" => { "resource-class" => "org.jboss.as.quickstarts.rshelloworld.HelloWorld", "rest-resource-paths" => [ { "resource-path" => "/hello/json", "consumes" => undefined, "produces" => [ "application/json", "text/plain" ], "java-method" => "java.lang.String org.jboss.as.quickstarts.rshelloworld.HelloWorld.getHelloWorldJSON()", "resource-methods" => [ "POST /wildfly-helloworld-rs/rest/hello/json", "GET /wildfly-helloworld-rs/rest/hello/json" ] }, { "resource-path" => "/hello/xml", "consumes" => undefined, "produces" => ["application/xml"], "java-method" => "java.lang.String org.jboss.as.quickstarts.rshelloworld.HelloWorld.getHelloWorldXML(@QueryParam java.lang.String name = 'LGAO')", "resource-methods" => ["GET /wildfly-helloworld-rs/rest/hello/xml"] } ], "sub-resource-locators" => [{ "resource-class" => "org.jboss.as.quickstarts.rshelloworld.SubHelloWorld", "rest-resource-paths" => [ { "resource-path" => "/hello/subMessage/", "consumes" => undefined, "produces" => undefined, "java-method" => "java.lang.String org.jboss.as.quickstarts.rshelloworld.SubHelloWorld.helloInSub()", "resource-methods" => ["GET /wildfly-helloworld-rs/rest/hello/subMessage/"] }, { "resource-path" => "/hello/subMessage/subPath", "consumes" => undefined, "produces" => undefined, "java-method" => "java.lang.String org.jboss.as.quickstarts.rshelloworld.SubHelloWorld.subPath()", "resource-methods" => ["GET /wildfly-helloworld-rs/rest/hello/subMessage/subPath"] } ], "sub-resource-locators" => undefined }] } }
/deployment= DEPLOYMENT_NAME /subsystem=jaxrs:read-resource(include-runtime=true,recursive=true)
<context-param> <param-name>resteasy.resources</param-name> <param-value>org.jboss.resteasy.plugins.stats.RegistryStatsResource</param-value> </context-param>
curl http://localhost:8080/ {APPLICATION_PREFIX_URL} /resteasy/registry
curl http://localhost:8080/ {APPLICATION_PREFIX_URL} /resteasy/registry -H "Accept: application/json"
http://localhost:8080/ {APPLICATION_PREFIX_URL} /resteasy/registry
<context-param> <param-name>resteasy.media.type.mappings</param-name> <param-value>html : text/html, json : application/json, xml : application/xml</param-value> </context-param>
<context-param> <param-name>resteasy.language.mappings</param-name> <param-value> en : en-US, es : es, fr : fr</param-name> </context-param>
public interface Providers { <T> MessageBodyReader<T> getMessageBodyReader(Class<T> type, Type genericType, Annotation annotations[], MediaType mediaType); <T> MessageBodyWriter<T> getMessageBodyWriter(Class<T> type, Type genericType, Annotation annotations[], MediaType mediaType); <T extends="" throwable=""> ExceptionMapper<T> getExceptionMapper(Class<T> type); <T> ContextResolver<T> getContextResolver(Class<T> contextType, MediaType mediaType); }
@Provider @Consumes("multipart/fixed") public class MultipartProvider implements MessageBodyReader { private @Context Providers providers; }
<!--?xml version="1.0"?--> <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> <search> <user>bill</user> <file>&xxe;<file> </search>
<context-param> <param-name>resteasy.document.expand.entity.references</param-name> <param-value>true</param-value> </context-param>
<context-param> <param-name>resteasy.document.secure.disableDTDs</param-name> <param-value>false</param-value> </context-param>
<!--ENTITY foo 'foo'--> <!--ENTITY foo1 '&foo;&foo;&foo;&foo;&foo;&foo;&foo;&foo;&foo;&foo;'--> <!--ENTITY foo2 '&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;'--> <!--ENTITY foo3 '&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;'--> <!--ENTITY foo4 '&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;'--> <!--ENTITY foo5 '&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;'--> <!--ENTITY foo6 '&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;'-->
<context-param> <param-name>resteasy.document.secure.processing.feature</param-name> <param-value>false</param-value> </context-param>
@Path("manipulateMap") @POST @Consumes("application/x-www-form-urlencoded") @Produces("application/x-www-form-urlencoded") public MultivaluedMap<String, String> manipulateMap(MultivaluedMap<String, String> map) { //do something return map; }
MultivaluedMap<String, String> map = new MultivaluedHashMap<String, String>(); //add values to the map Response response = client.target(generateURL("/manipulateMap")) .request(MediaType.APPLICATION_FORM_URLENCODED_TYPE) .post(Entity.entity(map, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); String data = response.readEntity(String.class); //handle data
public class POJO { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
public class POJOConverter implements ParamConverter<POJO> { public POJO fromString(String str) { System.out.println("FROM STRNG: " + str); POJO pojo = new POJO(); pojo.setName(str); return pojo; } public String toString(POJO value) { return value.getName(); } }
public class POJOConverterProvider implements ParamConverterProvider { @Override public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) { if (!POJO.class.equals(rawType)) return null; return (ParamConverter<T>)new POJOConverter(); } }
@Path("/") public class MyResource { @Path("{pojo}") @PUT public void put(@QueryParam("pojo") POJO q, @PathParam("pojo") POJO pp, @MatrixParam("pojo") POJO mp, @HeaderParam("pojo") POJO hp) { } }
@Path("queryParam") public static class TestResource { @GET @Path("") public Response conversion(@QueryParam("q") List<String> list) { return Response.ok(stringify(list)).build(); } } private static <T> String stringify(List<T> list) { StringBuffer sb = new StringBuffer(); for (T s : list) { sb.append(s).append(','); } return sb.toString(); }
@Test public void testQueryParamStandard() throws Exception { ResteasyClient client = new ResteasyClientBuilder().build(); Invocation.Builder request = client.target("http://localhost:8081/queryParam?q=20161217&q=20161218&q=20161219").request(); Response response = request.get(); System.out.println("response: " + response.readEntity(String.class)); }
public static class MultiValuedParamConverterProvider implements ParamConverterProvider @SuppressWarnings("unchecked") @Override public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) { if (List.class.isAssignableFrom(rawType)) { return (ParamConverter<T>) new MultiValuedParamConverter(); } return null; } } public static class MultiValuedParamConverter implements ParamConverter<List<?>> { @Override public List<?> fromString(String param) { if (param == null || param.trim().isEmpty()) { return null; } return parse(param.split(",")); } @Override public String toString(List<?> list) { if (list == null || list.isEmpty()) { return null; } return stringify(list); } private static List<String> parse(String[] params) { List<String> list = new ArrayList<String>(); for (String param : params) { list.add(param); } return list; } }
@Test public void testQueryParamCustom() throws Exception { ResteasyClient client = new ResteasyClientBuilder().build(); Invocation.Builder request = client.target("http://localhost:8081/queryParam?q=20161217,20161218,20161219").request(); Response response = request.get(); System.out.println("response: " + response.readEntity(String.class)); }
@Path("queryParam") public static class TestResource { @GET @Path("") public Response conversion(@QueryParam("q") ArrayList<String> list) { return Response.ok(stringify(list)).build(); } }
public static class Foo { private String foo; public Foo(String foo) { this.foo = foo; } public String getFoo() { return foo; } } public static class FooArrayParamConverter implements ParamConverter < Foo[] > { @Override public Foo[] fromString(String value) { String[] ss = value.split(","); Foo[] fs = new Foo[ss.length]; int i = 0; for (String s: ss) { fs[i++] = new Foo(s); } return fs; } @Override public String toString(Foo[] values) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < values.length; i++) { sb.append(values[i].getFoo()).append(","); } if (sb.length() > 0) { sb.deleteCharAt(sb.length() - 1); } return sb.toString(); } } @Provider public static class FooArrayParamConverterProvider implements ParamConverterProvider { @SuppressWarnings("unchecked") @Override public < T > ParamConverter < T > getConverter(Class < T > rawType, Type genericType, Annotation[] annotations) { if (rawType.equals(Foo[].class)); return (ParamConverter < T > ) new FooArrayParamConverter(); } } @Path("") public static class ParamConverterResource { @GET @Path("test") public Response test(@QueryParam("foos") Foo[] foos) { return Response.ok(new FooArrayParamConverter().toString(foos)).build(); } }
@Path("/double") @GET public String optDouble(@QueryParam("value") OptionalDouble value) { return Double.toString(value.orElse(4242.0)); }
@JsonFilter(value="nameFilter") public class Jackson2Product { protected String name; protected int id; public Jackson2Product() { } public Jackson2Product(final int id, final String name) { this.id = id; this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } }
public class ObjectFilterModifier extends ObjectWriterModifier { public ObjectFilterModifier() { } @Override public ObjectWriter modify(EndpointConfigBase<?> endpoint, MultivaluedMap<String, Object> httpHeaders, Object valueToWrite, ObjectWriter w, JsonGenerator jg) throws IOException { FilterProvider filterProvider = new SimpleFilterProvider().addFilter( "nameFilter", SimpleBeanPropertyFilter.filterOutAllExcept("name")); return w.with(filterProvider); } }
@Provider public class JsonFilterWriteInterceptor implements WriterInterceptor{ private ObjectFilterModifier modifier = new ObjectFilterModifier(); @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { //set a threadlocal modifier ObjectWriterInjector.set(modifier); context.proceed(); } }
public class ObjectWriterModifierFilter implements Filter { private static ObjectFilterModifier modifier = new ObjectFilterModifier(); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ObjectWriterInjector.set(modifier); chain.doFilter(request, response); } @Override public void destroy() { } }
import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; @Provider @Produces(MediaType.APPLICATION_JSON) public class JacksonDatatypeJacksonProducer implements ContextResolver<ObjectMapper> { private final ObjectMapper json; public JacksonDatatypeJacksonProducer() throws Exception { this.json = JsonMapper.builder() .findAndAddModules() .build(); } @Override public ObjectMapper getContext(Class<?> objectType) { return json; } }
@XmlRootElement public static class Thing { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } @Path("/test") public static class TestService { @GET @Path("/header") @Produces("application/xml") @XmlHeader("<?xml-stylesheet type='text/xsl' href='USD{baseuri}foo.xsl' ?>") public Thing get() { Thing thing = new Thing(); thing.setName("bill"); return thing; } }
@XmlRootElement public static class Thing { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } @Path("/test") public static class TestService { @GET @Path("/stylesheet") @Produces("application/xml") @Stylesheet(type="text/css", href="USD{basepath}foo.xsl") @Junk public Thing getStyle() { Thing thing = new Thing(); thing.setName("bill"); return thing; } }
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jackson2-provider</artifactId> <version>USD{version.org.jboss.resteasy}</version> <scope>provided</scope> </dependency>
@XmlRootElement public static class Thing { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } @Path("/test") public static class TestService { @GET @Path("/thing") @Produces("application/json") public Thing get() { Thing thing = new Thing(); thing.setName("the thing"); return thing; } }
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jdk8</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency>
ObjectMapper mapper = new ObjectMapper(); mapper.findAndRegisterModules();
ObjectMapper mapper = new ObjectMapper() .registerModule(new ParameterNamesModule()) .registerModule(new Jdk8Module()) .registerModule(new JavaTimeModule());
@GET @Path("/duration") @Produces(MediaType.APPLICATION_JSON) public Duration getDuration() { return Duration.ofSeconds(5, 6); }
@GET @Path("/optional/{nullParam}") @Produces(MediaType.APPLICATION_JSON) public Optional<String> getOptional(@PathParam("nullParam") boolean nullParameter) { return nullParameter ? Optional.<String>empty() : Optional.of("info@example.com"); }
@Provider @Produces(MediaType.APPLICATION_JSON) public class JacksonDatatypeJacksonProducer implements ContextResolver<ObjectMapper> { private final ObjectMapper json; public JacksonDatatypeJacksonProducer() throws Exception { this.json = new ObjectMapper() .findAndRegisterModules() .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } @Override public ObjectMapper getContext(Class<?> objectType) { return json; } }
<?xml version="1.0" encoding="UTF-8"?> <jboss-deployment-structure> <deployment> <exclusions> <module name="org.jboss.resteasy.resteasy-jackson2-provider"/> </exclusions> <dependencies> <module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/> </dependencies> </deployment> </jboss-deployment-structure>
import org.jboss.resteasy.core.interception.DecoratorProcessor; import org.jboss.resteasy.annotations.DecorateTypes; import javax.xml.bind.Marshaller; import javax.xml.bind.PropertyException; import javax.ws.rs.core.MediaType; import javax.ws.rs.Produces; import java.lang.annotation.Annotation; @DecorateTypes({"text/*+xml", "application/*+xml"}) public class PrettyProcessor implements DecoratorProcessor<Marshaller, Pretty> { public Marshaller decorate(Marshaller target, Pretty annotation, Class type, Annotation[] annotations, MediaType mediaType) { target.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); } }
import org.jboss.resteasy.annotations.Decorator; @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Decorator(processor = PrettyProcessor.class, target = Marshaller.class) public @interface Pretty {}
package org.jboss.resteasy.plugins.providers.multipart; public interface MultipartInput { List<InputPart> getParts(); String getPreamble(); // You must call close to delete any temporary files created // Otherwise they will be deleted on garbage collection or on JVM exit void close(); } public interface InputPart { MultivaluedMap<String, String> getHeaders(); String getBodyAsString(); <T> T getBody(Class<T> type, Type genericType) throws IOException; <T> T getBody(org.jboss.resteasy.util.GenericType<T> type) throws IOException; MediaType getMediaType(); boolean isContentTypeFromMessage(); }
@Path("/multipart") public class MyService { @PUT @Consumes("multipart/mixed") public void put(MultipartInput input) { List<Customer> customers = new ArrayList...; for (InputPart part : input.getParts()) { Customer cust = part.getBody(Customer.class, null); customers.add(cust); } input.close(); } }
@Path("/multipart") public class MyService { @PUT @Consumes("multipart/mixed") public void put(MultipartInput input) { for (InputPart part : input.getParts()) { List<Customer> cust = part.getBody(new GenericType<List<Customer>>() {}); } input.close(); } }
@Path("/multipart") public class MyService { @PUT @Consumes("multipart/mixed") public void put(List<Customer> customers) { } }
public interface MultipartFormDataInput extends MultipartInput { @Deprecated Map<String, InputPart> getFormData(); Map<String, List<InputPart>> getFormDataMap(); <T> T getFormDataPart(String key, Class<T> rawType, Type genericType) throws IOException; <T> T getFormDataPart(String key, GenericType<T> type) throws IOException; }
@Path("/multipart") public class MyService { @PUT @Consumes("multipart/form-data") public void put(Map<String, Customer> customers) { } }
public interface MultipartRelatedInput extends MultipartInput { String getType(); String getStart(); String getStartInfo(); InputPart getRootPart(); Map<String, InputPart> getRelatedMap(); }
package org.jboss.resteasy.plugins.providers.multipart; public class MultipartOutput { public OutputPart addPart(Object entity, MediaType mediaType) public OutputPart addPart(Object entity, GenericType type, MediaType mediaType) public OutputPart addPart(Object entity, Class type, Type genericType, MediaType mediaType) public List<OutputPart> getParts() public String getBoundary() public void setBoundary(String boundary) } public class OutputPart { public MultivaluedMap<String, Object> getHeaders() public Object getEntity() public Class getType() public Type getGenericType() public MediaType getMediaType() }
@Path("/multipart") public class MyService { @GET @Produces("multipart/mixed") public MultipartOutput get() { MultipartOutput output = new MultipartOutput(); output.addPart(new Customer("bill"), MediaType.APPLICATION_XML_TYPE); output.addPart(new Customer("monica"), MediaType.APPLICATION_XML_TYPE); return output; } }
@Path("/multipart") public class MyService { @GET @Produces("multipart/mixed") @PartType("application/xml") public List<Customer> get(){ } }
package org.jboss.resteasy.plugins.providers.multipart; public class MultipartFormDataOutput extends MultipartOutput { public OutputPart addFormData(String key, Object entity, MediaType mediaType) public OutputPart addFormData(String key, Object entity, GenericType type, MediaType mediaType) public OutputPart addFormData(String key, Object entity, Class type, Type genericType, MediaType mediaType) public Map<String, OutputPart> getFormData() }
@Path("/form") public class MyService { @GET @Produces("multipart/form-data") public MultipartFormDataOutput get() { MultipartFormDataOutput output = new MultipartFormDataOutput(); output.addPart("bill", new Customer("bill"), MediaType.APPLICATION_XML_TYPE); output.addPart("monica", new Customer("monica"), MediaType.APPLICATION_XML_TYPE); return output; } }
@Path("/multipart") public class MyService { @GET @Produces("multipart/form-data") @PartType("application/xml") public Map<String, Customer> get() { } }
package org.jboss.resteasy.plugins.providers.multipart; public class MultipartRelatedOutput extends MultipartOutput { public OutputPart getRootPart() public OutputPart addPart(Object entity, MediaType mediaType, String contentId, String contentTransferEncoding) public String getStartInfo() public void setStartInfo(String startInfo) }
@Path("/related") public class MyService { @GET @Produces("multipart/related") public MultipartRelatedOutput get() { MultipartRelatedOutput output = new MultipartRelatedOutput(); output.setStartInfo("text/html"); Map<String, String> mediaTypeParameters = new LinkedHashMap<String, String>(); mediaTypeParameters.put("charset", "UTF-8"); mediaTypeParameters.put("type", "text/html"); output.addPart( "<html><body>\n" + "This is me: <img src='cid:http://example.org/me.png' />\n" + "<br />This is you: <img src='cid:http://example.org/you.png' />\n" + "</body></html>", new MediaType("text", "html", mediaTypeParameters), "<mymessage.xml@example.org>", "8bit"); output.addPart("// binary octets for me png", new MediaType("image", "png"), "<http://example.org/me.png>", "binary"); output.addPart("// binary octets for you png", new MediaType( "image", "png"), "<http://example.org/you.png>", "binary"); client.putRelated(output); return output; } }
public class CustomerProblemForm { @FormParam("customer") @PartType("application/xml") private Customer customer; @FormParam("problem") @PartType("text/plain") private String problem; public Customer getCustomer() { return customer; } public void setCustomer(Customer cust) { this.customer = cust; } public String getProblem() { return problem; } public void setProblem(String problem) { this.problem = problem; } }
@Path("portal") public interface CustomerPortal { @Path("issues/{id}") @Consumes("multipart/form-data") @PUT public void putProblem(@MultipartForm CustomerProblemForm, @PathParam("id") int id); } // Somewhere using it: { CustomerPortal portal = ProxyFactory.create(CustomerPortal.class, "http://example.com"); CustomerProblemForm form = new CustomerProblemForm(); form.setCustomer(...); form.setProblem(...); portal.putProblem(form, 333); }
@Path("portal") public class CustomerPortalServer { @Path("issues/{id}) @Consumes("multipart/form-data") @PUT public void putIssue(@MultipartForm CustomerProblemForm, @PathParam("id") int id) { ... write to database } }
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public static class Xop { private Customer bill; private Customer monica; @XmlMimeType(MediaType.APPLICATION_OCTET_STREAM) private byte[] myBinary; @XmlMimeType(MediaType.APPLICATION_OCTET_STREAM) private DataHandler myDataHandler; // methods, other fields }
// our client interface: @Path("mime") public static interface MultipartClient { @Path("xop") @PUT @Consumes(MultipartConstants.MULTIPART_RELATED) public void putXop(@XopWithMultipartRelated Xop bean); } // Somewhere using it: { MultipartClient client = ProxyFactory.create(MultipartClient.class, "http://www.example.org"); Xop xop = new Xop(new Customer("bill"), new Customer("monica"), "Hello Xop World!".getBytes("UTF-8"), new DataHandler(new ByteArrayDataSource("Hello Xop World!".getBytes("UTF-8"), MediaType.APPLICATION_OCTET_STREAM))); client.putXop(xop); }
@Path("/mime") public class XopService { @PUT @Path("xop") @Consumes(MultipartConstants.MULTIPART_RELATED) public void putXopWithMultipartRelated(@XopWithMultipartRelated Xop xop) { // do very important things here } }
import org.jboss.resteasy.plugins.providers.multipart.InputPart; @Provider @ServerInterceptor public class ContentTypeSetterPreProcessorInterceptor implements PreProcessInterceptor { public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException { request.setAttribute(InputPart.DEFAULT_CONTENT_TYPE_PROPERTY, "*/*; charset=UTF-8"); return null; } }
@POST @Path("query") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.TEXT_PLAIN) public Response setMediaType(MultipartInput input) throws IOException { List<InputPart> parts = input.getParts(); InputPart part = parts.get(0); part.setMediaType(MediaType.valueOf("application/foo+xml")); String s = part.getBody(String.class, null); }
import org.jboss.resteasy.plugins.providers.multipart.InputPart; @Provider @ServerInterceptor public class ContentTypeSetterPreProcessorInterceptor implements PreProcessInterceptor { public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException { request.setAttribute(InputPart.DEFAULT_CHARSET_PROPERTY, "UTF-8"); return null; } }
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> <version>USD{version.org.jboss.resteasy}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-multipart-provider</artifactId> <version>USD{version.org.jboss.resteasy}</version> <scope>provided</scope> </dependency>
ResteasyClient client = new ResteasyClientBuilder().build(); ResteasyWebTarget target = client.target("http://foo.com/resource"); MultipartFormDataOutput formOutputData = new MultipartFormDataOutput(); formOutputData.addFormData("part1", "this is part 1", MediaType.TEXT_PLAIN); formOutputData.addFormData("part2", "this is part 2", MediaType.TEXT_PLAIN); GenericEntity<MultipartFormDataOutput> data = new GenericEntity<MultipartFormDataOutput>(formOutputData) { }; Response response = target.request().put(Entity.entity(data, MediaType.MULTIPART_FORM_DATA_TYPE)); response.close();
import org.jboss.resteasy.plugins.providers.atom.Content; import org.jboss.resteasy.plugins.providers.atom.Entry; import org.jboss.resteasy.plugins.providers.atom.Feed; import org.jboss.resteasy.plugins.providers.atom.Link; import org.jboss.resteasy.plugins.providers.atom.Person; @Path("atom") public class MyAtomService { @GET @Path("feed") @Produces("application/atom+xml") public Feed getFeed() throws URISyntaxException { Feed feed = new Feed(); feed.setId(new URI("http://example.com/42")); feed.setTitle("My Feed"); feed.setUpdated(new Date()); Link link = new Link(); link.setHref(new URI("http://localhost")); link.setRel("edit"); feed.getLinks().add(link); feed.getAuthors().add(new Person("John Brown")); Entry entry = new Entry(); entry.setTitle("Hello World"); Content content = new Content(); content.setType(MediaType.TEXT_HTML_TYPE); content.setText("Nothing much"); entry.setContent(content); feed.getEntries().add(entry); return feed; } }
@XmlRootElement(namespace = "http://jboss.org/Customer") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlElement private String name; public Customer() { } public Customer(String name) { this.name = name; } public String getName() { return name; } } @Path("atom") public static class AtomServer { @GET @Path("entry") @Produces("application/atom+xml") public Entry getEntry() { Entry entry = new Entry(); entry.setTitle("Hello World"); Content content = new Content(); content.setJAXBObject(new Customer("bill")); entry.setContent(content); return entry; } }
@Path("atom") public static class AtomServer { @PUT @Path("entry") @Produces("application/atom+xml") public void putCustomer(Entry entry) { Content content = entry.getContent(); Customer cust = content.getJAXBObject(Customer.class); } }
org.jboss.resteasy.plugins.providers.YamlProvider
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-yaml-provider</artifactId> <version>USD{version.org.jboss.resteasy}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>USD{version.org.yaml.snakeyaml}</version> </dependency>
import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; @Path("/yaml") public class YamlResource { @GET @Produces("text/x-yaml") public MyObject getMyObject() { return createMyObject(); } }
JsonObject obj = Json.createObjectBuilder().add("name", "Bill").build();
name: Bill
JsonArray array = Json.createArrayBuilder() .add(Json.createObjectBuilder().add("name", "Bill").build()) .add(Json.createObjectBuilder().add("name", "Monica").build()).build();
Bill, Monica
JsonObject obj = Json.createObjectBuilder().add("name", "Bill").build(); JsonArray array = Json.createArrayBuilder() .add(Json.createObjectBuilder().add("name", "Bill").build()) .add(Json.createObjectBuilder().add("name", "Monica").build()).build(); JsonStructure sObj = (JsonStructure) obj; JsonStructure sArray = (JsonStructure) array;
@Path("object") @POST @Produces("application/json") @Consumes("application/json") public JsonObject object(JsonObject obj) { // do something return obj; } @Path("array") @POST @Produces("application/json") @Consumes("application/json") public JsonArray array(JsonArray array) { // do something return array; } @Path("structure") @POST @Produces("application/json") @Consumes("application/json") public JsonStructure structure(JsonStructure structure) { // do something return structure; }
WebTarget target = client.target(...); JsonObject obj = Json.createObjectBuilder().add("name", "Bill").build(); JsonObject newObj = target.request().post(Entity.json(obj), JsonObject.class);
@Local @Path("/Library") public interface Library { @GET @Path("/books/{isbn}") public String getBook(@PathParam("isbn") String isbn); } @Stateless public class LibraryBean implements Library { }
<web-app> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>resteasy.jndi.resources</param-name> <param-value>java:module/LibraryBean!org.app.Library</param-value> </context-param> <listener> <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class> </listener> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
<web-app> <display-name>Archetype Created Web Application</display-name> <listener> <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class> </listener> <listener> <listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
public class RoleBasedSecurityFilter implements ContainerRequestFilter { protected String[] rolesAllowed; protected boolean denyAll; protected boolean permitAll; public RoleBasedSecurityFilter(String[] rolesAllowed, boolean denyAll, boolean permitAll) { this.rolesAllowed = rolesAllowed; this.denyAll = denyAll; this.permitAll = permitAll; } @Override public void filter(ContainerRequestContext requestContext) throws IOException { if (denyAll) { requestContext.abortWith(Response.status(403).entity("Access forbidden: role not allowed").build()); return; } if (permitAll) return; if (rolesAllowed != null) { SecurityContext context = ResteasyProviderFactory.getContextData(SecurityContext.class); if (context != null) { for (String role : rolesAllowed) { if (context.isUserInRole(role)) return; } requestContext.abortWith(Response.status(403).entity("Access forbidden: role not allowed").build()); return; } } return; } }
public class CacheControlFilter implements ContainerResponseFilter { private int maxAge; public CacheControlFilter(int maxAge) { this.maxAge = maxAge; } public void filter(ContainerRequestContext req, ContainerResponseContext res) throws IOException { if (req.getMethod().equals("GET")) { CacheControl cc = new CacheControl(); cc.setMaxAge(this.maxAge); res.getHeaders().add("Cache-Control", cc); } } }
@Provider public class BookReaderInterceptor implements ReaderInterceptor { @Inject private Logger log; @Override @ReaderInterceptorBinding public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException { log.info("*** Intercepting call in BookReaderInterceptor.aroundReadFrom()"); VisitList.add(this); Object result = context.proceed(); log.info("*** Back from intercepting call in BookReaderInterceptor.aroundReadFrom()"); return result; } }
@Provider public class BookWriterInterceptor implements WriterInterceptor { @Inject private Logger log; @Override @WriterInterceptorBinding public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { log.info("*** Intercepting call in BookWriterInterceptor.aroundWriteTo()"); VisitList.add(this); context.proceed(); log.info("*** Back from intercepting call in BookWriterInterceptor.aroundWriteTo()"); } }
<context-param> <param-name>resteasy.providers</param-name> <param-value>my.app.CustomInterceptor</paramvalue> </context-param>
package org.jboss.resteasy.example; import javax.ws.rs.core.Application; import java.util.HashSet; import java.util.Set; public class MyApp extends Application { public java.util.Set<java.lang.Class<?>> getClasses() { Set<Class<?>> resources = new HashSet<Class<?>>(); resources.add(MyResource.class); resources.add(MyProvider.class); return resources; } }
package org.jboss.resteasy.example; import javax.ws.rs.core.Application; import java.util.HashSet; import java.util.Set; public class MyApp extends Application { protected Set<Object> singletons = new HashSet<Object>(); public MyApp() { singletons.add(new MyResource()); singletons.add(new MyProvider()); } @Override public Set<Object> getSingletons() { return singletons; } }
@Path("/") public interface MyProxy { @Consumes("application/xml") @PUT public void put(@GZIP Order order); }
@Path("/") public class MyService { @GET @Produces("application/xml") @GZIP public String getData() {...} }
Client client = new ResteasyClientBuilder() // Activate gzip compression on client: .register(AcceptEncodingGZIPFilter.class) .register(GZIPDecodingInterceptor.class) .register(GZIPEncodingInterceptor.class) .build();
Client client = new ResteasyClientBuilder() // Activate gzip compression on client: .register(AcceptEncodingGZIPFilter.class) .register(new GZIPDecodingInterceptor(256)) .register(GZIPEncodingInterceptor.class) .build();
@Provider public class AnimalTypeFeature implements DynamicFeature { @Override public void configure(ResourceInfo info, FeatureContext context) { if (info.getResourceMethod().getAnnotation(GET.class) != null) AnimalFilter filter = new AnimalFilter(); context.register(filter); } } }
@NameBinding public @interface DoIt {} @DoIt public class MyFilter implements ContainerRequestFilter {...} @Path("/root") public class MyResource { @GET @DoIt public String get() {...} }
/subsystem=logging/console-handler=CONSOLE:write-attribute(name=level,value=ALL) /subsystem=logging/logger=org.jboss.resteasy:add(level=ALL) /subsystem=logging/logger=javax.xml.bind:add(level=ALL) /subsystem=logging/logger=com.fasterxml.jackson:add(level=ALL)
@Provider public class EJBExceptionMapper implements ExceptionMapper<javax.ejb.EJBException> { public Response toResponse(EJBException exception) { return Response.status(500).build(); } }
<context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param>
<security-role> <role-name> ROLE_NAME </role-name> </security-role> <security-role> <role-name> ROLE_NAME </role-name> </security-role>
<security-constraint> <web-resource-collection> <web-resource-name>Resteasy</web-resource-name> <url-pattern> /PATH </url-pattern> </web-resource-collection> <auth-constraint> <role-name> ROLE_NAME </role-name> <role-name> ROLE_NAME </role-name> </auth-constraint> </security-constraint>
<login-config> <auth-method>BASIC</auth-method> <realm-name>jaxrs</realm-name> </login-config>
<web-app> <context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param> <security-constraint> <web-resource-collection> <web-resource-name>Resteasy</web-resource-name> <url-pattern>/security</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> <role-name>user</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>jaxrs</realm-name> </login-config> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>user</role-name> </security-role> </web-app>
@RolesAllowed("admin") @Path("/test") public class TestService { }
public interface SecurityContext { public Principal getUserPrincipal(); public boolean isUserInRole(String role); public boolean isSecure(); public String getAuthenticationScheme(); }
@Path("test") public class SecurityContextResource { @Context SecurityContext securityContext; @GET @Produces("text/plain") public String get() { if (!securityContext.isUserInRole("admin")) { throw new WebApplicationException(Response.serverError().status(HttpResponseCodes.SC_UNAUTHORIZED) .entity("User " + securityContext.getUserPrincipal().getName() + " is not authorized").build()); } return "Good user " + securityContext.getUserPrincipal().getName(); } }
<context-param> <param-name>resteasy.async.job.service.enabled</param-name> <param-value>true</param-value> </context-param>
POST http://example.com/myservice?asynch=true
HTTP/1.1 202 Accepted Location: http://example.com/asynch/jobs/3332334
/asynch/jobs/{job-id}?wait={milliseconds}|nowait=true
POST http://example.com/asynch/jobs/122?wait=3000
POST http://example.com/myservice?oneway=true
@Path("foo") public class Foo { @Path("{id}") @GET public String get(@QueryParam("order") String order, @HeaderParam("X-Foo") String header, @MatrixParam("colour") String colour, @CookieParam("Foo-Cookie") String cookie) { } @POST public void post(String text) { } }
var text = Foo.get({order: 'desc', 'X-Foo': 'hello', colour: 'blue', 'Foo-Cookie': 123987235444}); Foo.post({USDentity: text});
<servlet> <servlet-name>RESTEasy JSAPI</servlet-name> <servlet-class>org.jboss.resteasy.jsapi.JSAPIServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>RESTEasy JSAPI</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
// Change the base URL used by the API: REST.apiURL = "http://api.service.com"; // log everything in a div element REST.log = function(text) { jQuery("#log-div").append(text); };
var r = new REST.Request(); r.setURI("http://api.service.com/orders/23/json"); r.setMethod("PUT"); r.setContentType("application/json"); r.setEntity({id: "23"}); r.addMatrixParameter("JSESSIONID", "12309812378123"); r.execute(function(status, request, entity) { log("Response is " + status); });
package org.jboss.resteasy.spi.metadata; public interface ResourceClass { String getPath(); Class<?> getClazz(); ResourceConstructor getConstructor(); FieldParameter[] getFields(); SetterParameter[] getSetters(); ResourceMethod[] getResourceMethods(); ResourceLocator[] getResourceLocators(); }
package org.jboss.resteasy.test.core.spi.resource; import org.jboss.logging.Logger; import org.jboss.resteasy.spi.metadata.ResourceClass; import org.jboss.resteasy.spi.metadata.ResourceClassProcessor; import javax.ws.rs.ext.Provider; @Provider public class ResourceClassProcessorImplementation implements ResourceClassProcessor { protected static final Logger logger = Logger.getLogger(ResourceClassProcessorImplementation.class.getName()); @Override public ResourceClass process(ResourceClass clazz) { logger.info(String.format("ResourceClassProcessorImplementation#process method called on class %s", clazz.getClazz().getSimpleName())); String clazzName = clazz.getClazz().getSimpleName(); if (clazzName.startsWith("ResourceClassProcessorEndPoint") || clazzName.equals("ResourceClassProcessorProxy") || clazzName.equals("ResourceClassProcessorProxyEndPoint")) { return new ResourceClassProcessorClass(clazz); } return clazz; }
@Path("resource") public class TestResource { @Path("test") @GET String test() { return "test"; } }
Client client = ClientBuilder.newClient(); String response = client.target("http://localhost:8081/test").request().get(String.class);
@Path("resource") public interface TestResourceIntf { @Path("test") @GET public String test(); } TestResourceIntf service = RestClientBuilder.newBuilder() .baseUrl(http://localhost:8081/)) .build(TestResourceIntf.class); String s = service.test();
@Path("resource") public interface TestResourceIntf2 { @Path("test/{path}")mes("text/plain") @Produces("text/html") @POST public String test(@PathParam("path") String path, @QueryParam("query") String query, String entity); }
POST /resource/test/p/?query=q HTTP/1.1 Accept: text/html Content-Type: text/plain Content-Length: 1 e
TestResourceIntf service = RestClientBuilder.newBuilder() .baseUrl(http://localhost:8081/)) .register(MyClientResponseFilter.class) .register(MyMessageBodyReader.class) .build(TestResourceIntf.class);
@Path("resource") @RegisterProvider(MyClientResponseFilter.class) @RegisterProvider(MyMessageBodyReader.class) public interface TestResourceIntf2 { @Path("test/{path}") @Consumes("text/plain") @Produces("text/html") @POST public String test(@PathParam("path") String path, @QueryParam("query") String query, String entity); }
@POST @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.TEXT_PLAIN) String contentLang(@HeaderParam(HttpHeaders.CONTENT_LANGUAGE) String contentLanguage, String subject);
@POST @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.TEXT_PLAIN) @ClientHeaderParam(name=HttpHeaders.CONTENT_LANGUAGE, value="{getLanguage}") String contentLang(String subject); default String getLanguage() { return ...; }
public interface ClientHeadersFactory { /** * Updates the HTTP headers to send to the remote service. Note that providers * on the outbound processing chain could further update the headers. * * @param incomingHeaders - the map of headers from the inbound Jakarta RESTful Web Services request. This will * be an empty map if the associated client interface is not part of a Jakarta RESTful Web Services request. * @param clientOutgoingHeaders - the read-only map of header parameters specified on the * client interface. * @return a map of HTTP headers to merge with the clientOutgoingHeaders to be sent to * the remote service. */ MultivaluedMap<String, String> update(MultivaluedMap<String, String> incomingHeaders, MultivaluedMap<String, String> clientOutgoingHeaders); }
@Path("resource") @RegisterProvider(MyClientResponseFilter.class) public static class TestResourceImpl { @Inject TestDataBase db; @Path("test/{path}") @Consumes("text/plain") @Produces("text/html") @POST public String test(@PathParam("path") String path, @QueryParam("query") String query, String entity) { return db.getByName(query); } } @Path("database") @RegisterRestClient public interface TestDataBase { @Path("") @POST public String getByName(String name); }
@Path("database") @RegisterRestClient(baseUri="https://localhost:8080/webapp") public interface TestDataBase { @Path("") @POST public String getByName(String name); }
<fully qualified name of TestDataBase>/mp-rest/url=<URL>
com.bluemonkeydiamond.TestDatabase/mp-rest/url=https://localhost:8080/webapp
public class SimpleResource { @GET @Path("basic") @Produces("text/plain") public CompletionStage<Response> getBasic() throws Exception { final CompletableFuture<Response> response = new CompletableFuture<>(); Thread t = new Thread() { @Override public void run() { try { Response jaxrs = Response.ok("basic").type(MediaType.TEXT_PLAIN).build(); response.complete(jaxrs); } catch (Exception e) { response.completeExceptionally(e); } } }; t.start(); return response; } }
public interface AsyncResponseProvider<T> { public CompletionStage toCompletionStage(T asyncResponse); }
public interface AsyncStreamProvider<T> { public Publisher toAsyncStream(T asyncResponse); }
public interface RxInvokerProvider<T extends RxInvoker> { public boolean isProviderFor(Class<T> clazz); public T getRxInvoker(SyncInvoker syncInvoker, ExecutorService executorService); }
public <T extends RxInvoker> T rx(Class<T> clazz);
FlowableRxInvoker invoker = client.target(generateURL("/get/string")).request().rx(FlowableRxInvoker.class); Flowable<String> flowable = (Flowable<String>) invoker.get();
public interface AsyncClientResponseProvider<T> { public T fromCompletionStage(CompletionStage<?> completionStage); }
@Path("") public interface RxCompletionStageResource { @GET @Path("get/string") @Produces(MediaType.TEXT_PLAIN) public CompletionStage<String> getString(); } @Path("") public class RxCompletionStageResourceImpl { @GET @Path("get/string") @Produces(MediaType.TEXT_PLAIN) public CompletionStage<String> getString() { .... } } public class RxCompletionStageProxyTest { private static ResteasyClient client; private static RxCompletionStageResource proxy; static { client = new ResteasyClientBuilder().build(); proxy = client.target(generateURL("/")).proxy(RxCompletionStageResource.class); } @Test public void testGet() throws Exception { CompletionStage<String> completionStage = proxy.getString(); Assert.assertEquals("x", completionStage.toCompletableFuture().get()); } }
public interface Rx2FlowableResource { @GET @Path("get/string") @Produces(MediaType.TEXT_PLAIN) @Stream public Flowable<String> getFlowable(); } @Path("") public class Rx2FlowableResourceImpl { @GET @Path("get/string") @Produces(MediaType.TEXT_PLAIN) @Stream public Flowable<String> getFlowable() { ... } } public class Rx2FlowableProxyTest { private static ResteasyClient client; private static Rx2FlowableResource proxy; static { client = new ResteasyClientBuilder().build(); proxy = client.target(generateURL("/")).proxy(Rx2FlowableResource.class); } @Test public void testGet() throws Exception { Flowable<String> flowable = proxy.getFlowable(); flowable.subscribe( (String o) -> stringList.add(o), (Throwable t) -> errors.incrementAndGet(), () -> latch.countDown()); boolean waitResult = latch.await(30, TimeUnit.SECONDS); Assert.assertTrue("Waiting for event to be delivered has timed out.", waitResult); Assert.assertEquals(0, errors.get()); Assert.assertEquals(xStringList, stringList); } }