Skip navigation
This project is read only now. Read more.
1 2 3 Previous Next

RESTEasy

39 posts

# RESTEasy WADL Grammar Support

RESTEasy has added WADL grammar support by this PR:

 

 

- [RESTEASY-1695 Add GRAMMARS into RESTEasy WADL by liweinan · Pull Request #1649 · resteasy/Resteasy · GitHub](https://github.com/resteasy/Resteasy/pull/1649)

 

 

The major change is that `ResteasyWadlGrammar` is added into `ResteasyWadlWriter`:

 

 

 

In addition, the `ResteasyWadlWriter` is rewritten now, and all the static methods are now instance methods. It means users need to create an instance of `ResteasyWadlWriter` and put it into per-deployment scope.

 

 

To avoid people to write the boilerplate code, the `ResteasyWadlDefaultResource` can be used for deployment, and it can be put into the `getSingleton()` method of `Application` class:

 

 

```java

@Provider

@ApplicationPath("/")

public class WadlTestApplication extends Application {

   public static Set<Class<?>> classes = new HashSet<Class<?>>();

   public static Set<Object> singletons = new HashSet<Object>();

...

   @Override

   public Set<Object> getSingletons() {

   ...

         ResteasyWadlDefaultResource defaultResource = new ResteasyWadlDefaultResource();

         singletons.add(defaultResource);

      }

      return singletons;

   }

}

```

 

 

And then the URL `/application.xml` is enabled by the methods inside `ResteasyWadlDefaultResource`.

 

 

To enable the WADL Grammar support, users need to create an instance of `ResteasyWadlGrammar` and put it into the instance of `ResteasyWadlWriter`.

 

 

The recommended way is to use it with `ResteasyWadlDefaultResource`. Here is an example:

 

 

```java

ResteasyWadlDefaultResource defaultResource = new ResteasyWadlDefaultResource();

 

 

ResteasyWadlWriter.ResteasyWadlGrammar wadlGrammar = new ResteasyWadlWriter.ResteasyWadlGrammar();

wadlGrammar.enableSchemaGeneration();

 

 

defaultResource.getWadlWriter().setWadlGrammar(wadlGrammar);

```

 

 

As the code shown above, we have created an instance of `ResteasyWadlGrammar`, and it’s injected into the `ResteasyWadlWriter` instance included by `ResteasyWadlDefaultResource` instance.

 

 

In addition, we have called the `wadlGrammar.enableSchemaGeneration()` method, and it will scan the resources classes, and generate grammar files for JAXB annotated classes. Suppose we have entity class like this:

 

 

```java

import javax.xml.bind.annotation.XmlElement;

import javax.xml.bind.annotation.XmlRootElement;

import java.util.List;

 

 

@XmlRootElement(name = "listType")

public class ListType {

 

 

    private List<String> values;

 

 

    @XmlElement

    public List<String> getValues() {

        return values;

    }

 

 

    public void setValues(List<String> values) {

        this.values = values;

    }

}

```

 

 

And it’s used in resource class:

 

 

```java

import javax.ws.rs.Consumes;

import javax.ws.rs.POST;

import javax.ws.rs.Path;

 

 

@Path("/extended")

public class ExtendedResource {

 

 

    @POST

    @Consumes({"application/xml"})

    public String post(ListType income) {

        return "foo";

    }

}

```

 

 

And if we enable the grammar generation as shown above, then we will get sample output from `/application.xml` like this:

 

 

```xml

$ http http://localhost:8081/application.xml

HTTP/1.1 200 OK

Content-type: application/xml;charset=UTF-8

Date: Wed, 31 Oct 2018 07:57:38 GMT

Transfer-encoding: chunked

 

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<application xmlns="http://wadl.dev.java.net/2009/02">

    <grammars>

        <include href="xsd0.xsd">

            <doc title="Generated" xml:lang="en"/>

        </include>

    </grammars>

</application>

```

 

 

The above output shows that a grammar file is genrated, and it is called `xsd0.xsd`. The instance of `ResteasyWadlDefaultResource` will prepare a URL link named `/wadl-extended`, and it will serve the generated grammar file. Here is the example:

 

 

```xml

$ http http://localhost:8081/wadl-extended/xsd0.xsd

HTTP/1.1 200 OK

Content-type: application/xml;charset=UTF-8

Date: Wed, 31 Oct 2018 07:58:53 GMT

Transfer-encoding: chunked

 

 

<?xml version="1.0" standalone="yes"?>

<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

 

 

  <xs:element name="listType" type="listType"/>

 

 

  <xs:complexType name="listType">

    <xs:sequence>

      <xs:element name="values" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>

    </xs:sequence>

  </xs:complexType>

</xs:schema>

```

 

 

As shown in the above example, the grammar is generated for `ListType` entity class. If you don’t want RESTEasy to generate the grammar file for you, you can use the `includeGrammars()` method provided by the instance of `ResteasyWadlGrammar`. Here is an example:

 

 

```java

ResteasyWadlWriter.ResteasyWadlGrammar wadlGrammar = new ResteasyWadlWriter.ResteasyWadlGrammar();

wadlGrammar.includeGrammars(“application-grammars.xml”);

...

```

 

 

The `application-grammars.xml` file is grammar descriptor file provided by yourself, and it should be put into your project’s classpath. Here is a sample of `application-grammars.xml`:

 

 

```xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<grammars xmlns="http://wadl.dev.java.net/2009/02"

    xmlns:xsd="http://www.w3.org/2001/XMLSchema"

    xmlns:xi="http://www.w3.org/1999/XML/xinclude">

    <include href="schema.xsd" />

</grammars>

```

 

 

From above file we can see `schema.xsd` is the included schema file, and it should also be provided by yourself. Here is a sample of `schema.xsd`:

 

 

```xml

<?xml version=“1.0” encoding=“UTF-8” standalone=“yes”?>

<xs:schema version=“1.0” xmlns:xs=“http://www.w3.org/2001/XMLSchema”>

 

 

    <xs:element name=“listType” type=“listType”/>

 

 

    <xs:complexType name=“listType”>

        <xs:sequence>

            <xs:element name=“values” type=“xs:string” minOccurs=“0” maxOccurs=“unbounded”/>

        </xs:sequence>

    </xs:complexType>

</xs:schema>

```

 

 

And if you have called `wadlGrammar.includeGrammars(“application-grammars.xml”)`, then you will get the included section in `/application.xml`:

 

 

```xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<application xmlns="http://wadl.dev.java.net/2009/02">

    <grammars>

        <include href="schema.xsd"/>

    </grammars>

...

</application>

```

 

 

As the example shown above, the `schema.xsd` is included, and it can be fetched by using `/wadl-extended/schema.xsd` if you have registered the instance of `ResteasyWadlDefaultResource` into your `Application` and setup `ResteasyWadlGrammar` properly:

 

 

```xml

$ http http://localhost:8081/wadl-extended/schema.xsd

HTTP/1.1 200 OK

Content-type: application/xml;charset=UTF-8

Date: Wed, 31 Oct 2018 08:12:56 GMT

Transfer-encoding: chunked

 

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

 

 

    <xs:element name="listType" type="listType"/>

 

 

    <xs:complexType name="listType">

        <xs:sequence>

            <xs:element name="values" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>

        </xs:sequence>

    </xs:complexType>

</xs:schema>

```

 

 

Above is the description about the RESTEasy WADL Grammar feature.

 

 

The other change is that `ResteasyWadlServlet` and `ResteasyWadlServletWriter` is now deprecated, because it doesn’t support the grammar feature, and these two classes will be removed from master branch in the future. Using `ResteasyWadlDefaultResource` is recommended to enable the WADL feature.

Previously, the RESTEasy tracing feature just support the pre-formatted, text-based information like this:

 

$ curl -i http://localhost:8081/foo | head
...
X-RESTEasy-Tracing-000: START       [ ---- /  ---- ms |  ---- %] baseUri=[http://localhost:8081/] requestUri=[http://localhost:8081/foo] method=[GET] authScheme=[n/a] accept=*/* accept-encoding=n/a accept-charset=n/a accept-language=n/a content-type=n/a content-length=n/a
X-RESTEasy-Tracing-001: START       [ ---- /  0.60 ms |  ---- %] Other request headers: Accept=[*/*] Host=[localhost:8081] User-Agent=[curl/7.55.1]
1X-RESTEasy-Tracing-002: PRE-MATCH   [ 0.01 /  2.54 ms |  0.00 %] PreMatchRequest summary: 0 filters
0X-RESTEasy-Tracing-003: REQ-FILTER  [ 0.02 /  5.35 ms |  0.00 %] Filter by [io.weli.tracing.HttpMethodOverride @4b8ca04a]
0X-RESTEasy-Tracing-004: REQ-FILTER  [ 0.03 /  5.66 ms |  0.00 %] Filter by [org.jboss.resteasy.core.AcceptHeaderByFileSuffixFilter @4d5e22c1]
X-RESTEasy-Tracing-005: REQ-FILTER  [ 0.73 /  5.88 ms |  0.00 %] Request summary: 2 filters
  X-RESTEasy-Tracing-006: MATCH       [ ---- /  6.44 ms |  ---- %] Matched resource: template=[[org.jboss.resteasy.core.registry.ClassExpression @7e528471]] regexp=[\Q\E(.*)] matches=[[org.jboss.resteasy.core.registry.SegmentNode @5072e501]] from=[]
  X-RESTEasy-Tracing-007: MATCH       [ ---- /  6.60 ms |  ---- %] Matching path [/foo]
...

 

This kind of tracing info is easy for the human to read, but it’s hard for program to process. Especially when it’s used in a distributed environemnt when the tracing info need to be passed across applications.

 

So now we add the feature to let tracing info to be returned in JSON format. To use this feature, you need to add the tracing-api as dependency firstly:

 

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-tracing-api</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

 

As shown above, now the tracing-api is a standalone project and it belongs to the resteasy-extensions project:

 


https://github.com/resteasy/resteasy-extensions

 

After including the above basic package, then we need to choose a JSON provider for tracing module to generate JSON formatted info.

 

There are two JSON providers you can choose from and they both support the tracing feature.

 

One is the jackson2 provider:

 

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-jackson2-provider</artifactId>
    <version>4.0.0-SNAPSHOT</version>
</dependency>

 

The other option is to use the json-binding provider:

 

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-json-binding-provider</artifactId>
    <version>4.0.0-SNAPSHOT</version>
</dependency>

 

After including either of the above module, now we can request the resource server for JSON formatted data. The method is to send a format request header:

 

X-RESTEasy-Tracing-Accept-Format: JSON

 

Here is a request example:

 

$ curl -H "X-RESTEasy-Tracing-Accept-Format: JSON" -i http://localhost:8081/foo

 

And the response will be like this:

 

X-RESTEasy-Tracing-000:
[{"duration":0,"event":"START","text":"baseUri=[http://localhost:8081/]
requestUri=[http://localhost:8081/foo] method=[GET] authScheme=[n/a] accept=*/*
accept-encoding=n/a accept-charset=n/a accept-language=n/a content-type=n/a
content-length=n/a
","timestamp":46600869878437},{"duration":0,"event":"START_HEADERS","text":"Other
request headers: Accept=[*/*] Host=[localhost:8081] User-Agent=[curl/7.55.1]
X-RESTEasy-Tracing-Accept-Format=[JSON]
","timestamp":46600870751617},{"duration":18336,"event":"PRE_MATCH_SUMMARY","tex
t":"PreMatchRequest summary: 0
filters","timestamp":46600872781491},{"duration":20724,"event":"REQUEST_FILTER",
"text":"Filter by [io.weli.tracing.HttpMethodOverride
@585b0020]","timestamp":46600876716467},{"duration":19414,"event":"REQUEST_FILTE
R","text":"Filter by [org.jboss.resteasy.core.AcceptHeaderByFileSuffixFilter
@3779d352]","timestamp":46600877017341},{"duration":657192,"event":"REQUEST_FILT
ER_SUMMARY","text":"Request summary: 2 filters",
...

 

After reformat the above response, we can see the response structure is like this:

 

[
{
  “duration”: 0,
  “event”: “START”,
  “text”: “baseUri=[http://localhost:8081/] requestUri=[http://localhost:8081/foo] method=[GET] authScheme=[n/a] accept=*/* accept-encoding=n/a accept-charset=n/a accept-language=n/a content-type=n/a content-length=n/a “,
  “timestamp”: 46600869878437
},
{
  “duration”: 0,
  “event”: “START_HEADERS”,
  “text”: “Other request headers: Accept=[*/*] Host=[localhost:8081] User-Agent=[curl/7.55.1] X-RESTEasy-Tracing-Accept-Format=[JSON] “,
  “timestamp”: 46600870751617
},
{
  “duration”: 18336,
  “event”: “PRE_MATCH_SUMMARY”,
  “text”: “PreMatchRequest summary: 0 filters”,
  “timestamp”: 46600872781491
},
{
  “duration”: 20724,
  “event”: “REQUEST_FILTER”,
  “text”: “Filter by [io.weli.tracing.HttpMethodOverride @585b0020]”,
  “timestamp”: 46600876716467
},
...
]

 

The timestamp is the event start time, and the other fields are quite straightforward.

 

The JSON formatted data should be more suitable to be parsed by code.

 

This feature is currently just in 4.0.0-SNAPSHOT, and haven’t been officially released yet.

asoldano

RESTEasy release week

Posted by asoldano Aug 9, 2018

It's been a busy week... but before leaving for the week-end I owe you an update ;-) Three new releases have been completed in the last days:

 

As usual, give them a try while they're hot, feedback is welcome!

A couple of months ago, we announced the move of the RESTEasy Spring Boot starter to RESTEasy organization on GitHub. We also mentioned that soon a new version of the starter would have been available... and here we are today, presenting release 2.0.0.Final, which

  • integrates Spring Boot 2 (currently 2.0.3.RELEASE version)
  • relies on the latest RESTEasy 3.x series (currently 3.6.0.Final version).

The artifacts are available on Maven central repository, as well as the JBoss repository. Feel free to pull them and give them a try.

Enjoy!

Now that we've seen RxJava support in RESTEasy, we're ready to build on more reactive applications to illustrate common reactive use-cases.

 

Let's create an application with several types of requests, that we collect statistics on, such as the number of times each request has been called. And let's store those statistics in a Redis instance. For that we will use the Vert.x Redis client because it supports RxJava out of the box.

 

A sample application

 

So we will be importing the following Maven modules:

 

<!-- For RESTEasy -->
<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-jaxrs</artifactId>
    <version>4.0.0-SNAPSHOT</version>
</dependency>
<!-- For RESTEasy's support of RxJava 2 -->
<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-rxjava2</artifactId>
    <version>4.0.0-SNAPSHOT</version>
</dependency>
<!-- For the Vert.x Redis client -->
<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-redis-client</artifactId>
    <version>3.5.3</version>
</dependency>
<!-- For the Vert.x RxJava 2 support -->
<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-rx-java2</artifactId>
    <version>3.5.3</version>
</dependency>

 

Now, in order to make sure I get a single Redis client for my application, I will have to make it injectable by RESTEasy with the @Context annotation, and there's no support for pluggable injection in JAX-RS so it's a little convoluted, but we can achieve that with the help of this custom Feature:

 

@Provider
public class RedisFeature implements Feature {

  private RedisClient redis;

  public RedisFeature(){
    // connect to the local Redis
    redis = RedisClient.create(Vertx.vertx());
  }

  public boolean configure(FeatureContext context) {
    // this is tied to the deployment, which is what we want for the redis client
    if(context.getConfiguration().getRuntimeType() == RuntimeType.CLIENT)
      return false;
    Dispatcher dispatcher = ResteasyProviderFactory.getContextData(Dispatcher.class);
    if(dispatcher == null) {
      // this can happen, but it means we're not able to find a deployment
      return false;
    }
    dispatcher.getDefaultContextObjects().put(RedisClient.class, redis);
    return true;
  }
}

 

We can now write our three requests that collect usage statistics (they inject the RedisClient):

 

@Path("/")
public class Resource {
  @Context
  private RedisClient redis;

  @Path("req1")
  @GET
  public Single<String> req1() {
    return redis.rxIncr("req1.count").map(count -> "Req1 count: "+count);
  }

  @Path("req2")
  @GET
  public Single<String> req2() {
    return redis.rxIncr("req2.count").map(count -> "Req2 count: "+count);
  }

  @Path("req3")
  @GET
  public Single<String> req3() {
    return redis.rxIncr("req3.count").map(count -> "Req3 count: "+count);
  }
}

 

As you can see we count usage in the Redis keys req1.count, req2.count and req3.count.

 

Now, if we want to display them, we have to get all three values, which either means a lot of nesting with RxJava, or (better) using the Single.zip operator:

 

@GET
public Single<String> info(){
  return Single.zip(redis.rxGet("req1.count"), redis.rxGet("req2.count"), redis.rxGet("req3.count"),
    (req1, req2, req3) -> "Request 1: "+req1+"\nRequest 2: "+req2+"\nRequest 2: "+req3);  
}

 

As you can see, with RxJava, getting several values is a little more verbose than if we were doing it in blocking style. In fact, in real applications it is very common to start most requests with actions that depend on resolving a few asynchronous values. They can be waiting for database results, querying caches, or even obtaining permission lists, but eventually, lots of your requests will start with a Single.zip call to get the values you need in your request. That's annoying, and when they are often the same values, that's just plain boilerplate.

 

The solution

 

What if RESTEasy could take all those async values that you need, and resolve them before it called your resource method? This is called asynchronous injection, and the latest RESTEasy does just that.

 

The async values we want to be resolved are originally of type Single<String>, so their resolved value is of type String. In order to get async injection, we annotate our injected resolved value with @Context, and RESTEasy will look up a ContextInjector that is declared to resolve values to String. In our case, we declare our ContextInjector to resolve values from type Single<String> to String, but any async type is supported, thanks to the existing support for pluggable async types.

 

Once we've declared our ContextInjector, RESTEasy will call it to get the Single<String> that provides the String we want asynchronously injected, and will wait for the async value to be resolved, and only then proceed to inject it in your resource method. This way, when you start your resource method, you already have all your async values resolved!

 

For our example, we're going to describe our redis queries with the @RedisQuery annotation:

 

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisQuery {
  String value();
}

 

Now we can write our new resource method that wants these redis queries injected:

 

@Path("inject")
@GET
public String infoInjection(@Context @RedisQuery("req1.count") String req1,
    @Context @RedisQuery("req2.count") String req2,
    @Context @RedisQuery("req3.count") String req3){
  return "Request 1: "+req1+"\nRequest 2: "+req2+"\nRequest 2: "+req3;
}

 

And all we have to do for that async injection to work is to declare our ContextInjector:

 

@Provider
public class RedisInjector implements ContextInjector<Single<String>, String> {

  @Override
  public Single<String> resolve(Class<?> rawType, Type genericType, Annotation[] annotations) {
    RedisClient redisClient = ResteasyProviderFactory.getContextData(RedisClient.class);
    for (Annotation annotation : annotations) {
      if(annotation.annotationType() == RedisQuery.class) {
        String query = ((RedisQuery) annotation).value();
         // let's inject !
        return redisClient.rxGet(query);
      }
    }
    // not for us: try other injectors
    return null;
  }
}

 

As you can see, we just have to declare that our ContextInjector can provide values of type String via an async type Single<String>, and check the annotations on the injection point to figure out what query to run.

 

As I mentioned previously, this is not limited to async values of type Single, because any async value type is supported via plugins, and in fact only CompletionStage is supported by default (Single being provided by the resteasy-rxjava2 module we're using).

 

Conclusion

We've removed yet another common cause of boilerplate: rejoice!

 

Async injection was added in the latest 4.0.0.Beta4 release (RESTEASY-1905). Go ahead and try it out while it's fresh!

You might have recently read about the very interesting new features being developed these days in RESTEasy... great, the time has come to deliver them in a couple of releases!

Last week, RESTEasy 3.6.0.Final and 4.0.0.Beta4 have been tagged, built and published; here is a list of the most relevant additions coming with them:

  • client and server side reactive extensions [1][2]
  • parameter annotations with default names [3][4]
  • JettyClientEngine to use jetty-client with JAX-RS Client [5]
  • SPI to modify resource metadata [6]

 

Moreover, 4.0.0.Beta4, also feature the first part of the changes for the tracing system [7] and asynchronous container filters [8] support.

Both releases are available on the usual Maven repository, feel free to try them out!

3.6.0.Final is also being pulled into WildFly master, targetting inclusion in WildFly 14 release.

Enjoy!

 

[1] Asynchronous, reactive, rxjava and beyond!

[2] Chapter 39. Reactive programming support

[3] New DRY-er annotations for parameters

[4] Chapter 13. Improved @…Param annotations

[5] Chapter 50. RESTEasy Client API

[6] Chapter 18. Resources metadata configuration

[7] A brief introduction to the RESTEasy Tracing Feature

[8] New: Asynchronous container filters

JAX-RS 2.0 shipped with support for filtering requests and responses, which enabled a lot of great use-cases for delegating duplicated code away from resources and into filters that would do the same processing for every resource method.

 

Request filters work by overriding the ContainerRequestFilter.filter method and observe or modify the given context object, or abort the filter chain with a response if the filter already has a response and the other filters and resource method are not required. Simply returning from the filter method will cause the next filter to be called, or when we have run all the filters, it will invoke the resource method.

 

Response filters are very similar, but execute after the resource method has been executed and produced an entity, status code, headers, which the filter can then modify if required, or simply return to let the next filters run, or the response be sent to the client.

 

This is all great, but how does it work in an asynchronous ecosystem ? It doesn't, really, because even though JAX-RS supports suspending the request, it only supports it within the resource method: filters are too early (for request filters), or too late (for response filters).

 

In RESTEasy 3.5 and 4.0.0, we introduced the ability to suspend the request in filters. To do that, write your request or response filter as usual, but then cast your context object down to SuspendableContainerRequestContext or SuspendableContainerResponseContext (for response filters), and you can then:

 

- suspend the request with SuspendableContainerRequestContext.suspend()

- resume it normally with SuspendableContainerRequestContext.resume(), to proceed to the next filter or resource method

- resume it with a response with the standard ContainerRequestContext.abortWith(), to directly send that response to the client

- resume it with an exception with SuspendableContainerRequestContext.resume(Throwable)

 

Similarly, for response filters, you can:

 

- suspend the request with SuspendableContainerResponseContext.suspend()

- resume it normally with SuspendableContainerResponseContext.resume(), to proceed to the next filter or return the response to the client

- resume it with an exception with SuspendableContainerResponseContext.resume(Throwable)

 

Of course, the resume() methods only work after you've called suspend(), but otherwise you can call resume() right after suspend(), before returning from the filter, in which case the request will not even be made asynchronous, or you can call resume() later after you return from the method, or even from another thread entirely, in which case the request will become asynchronous.

 

The fact that filters may turn requests asynchronous has no impact at all on the rest of your code: non-asynchronous and asynchronous resource methods continue to work exactly as normal, regardless of the asynchronous status of the request, so you don't need to modify your code to accommodate for asynchronous filters.

 

Asynchronous rate-limiter example with Redis

Asynchronous filters are useful for plugging in anything that requires asynchrony, such as reactive security frameworks, async response processing or async caching. We will illustrate how to use asynchronous filters with a rate-limiter example.

 

For that, we will use RateLimitJ for Redis, which uses Redis to store rate-limiting information for your API. This is very useful for sharing rate-limit between your API server cluster, because you can store that info in a Redis cluster, and you don't have to worry about blocking clients while you're waiting for Redis to give you the info: you just become asynchronous until you have an answer from Redis.

 

We will first import the right Maven dependency for RateLimitJ:

 

<dependency>
  <groupId>es.moki.ratelimitj</groupId>
  <artifactId>ratelimitj-redis</artifactId>
  <version>0.4.2</version>
</dependency>

 

And let's not forget to install and run a local Redis cluster.

 

We will start by declaring a @RateLimit annotation that we can use on our resource methods or classes to indicate we want rate limiting:

 

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface RateLimit {
 /**
 * Number of {@link #unit()} that defines our sliding window.
 */
 int duration();
 /**
 * Unit used for the sliding window {@link #duration()}.
 */
 TimeUnit unit();
 /**
 * Maximum number of requests to allow during our sliding window.
 */
 int maxRequest();
}

 

And we have to declare a DynamicFeature that enables the filter on annotated methods and classes:

 

@Provider
public class RateLimitFeature implements DynamicFeature {

  private StatefulRedisConnection<string,string> connection;

  public RateLimitFeature(){
    // connect to the local Redis
    connection = RedisClient.create("redis://localhost").connect();
  } 

  public void configure(ResourceInfo resourceInfo, FeatureContext context) {
    // See if we're rate-limiting
    RateLimit limit = resourceInfo.getResourceMethod().getAnnotation(RateLimit.class);
    if(limit == null)
    limit = resourceInfo.getResourceClass().getAnnotation(RateLimit.class);
    if(limit != null) {
      // add the rate-limiting filter
      Set rules = new HashSet<>();
      rules.add(RequestLimitRule.of(limit.duration(), limit.unit(), limit.maxRequest()));
      
      context.register(new RateLimitFilter(new RedisSlidingWindowRequestRateLimiter(connection, rules)));
    }
  }
}

 

And this is how we implement our asynchronous filter:

 

public class RateLimitFilter implements ContainerRequestFilter {

  private RedisSlidingWindowRequestRateLimiter requestRateLimiter;

  public RateLimitFilter(RedisSlidingWindowRequestRateLimiter requestRateLimiter) {
    this.requestRateLimiter = requestRateLimiter;
  }

  public void filter(ContainerRequestContext requestContext) throws IOException {
    // Get access to the remote address
    HttpServletRequest servletRequestContext = ResteasyProviderFactory.getContextData(HttpServletRequest.class);

    // Suspend the request
    SuspendableContainerRequestContext suspendableRequestContext = (SuspendableContainerRequestContext) requestContext;
    suspendableRequestContext.suspend();

    // Query and increment by remote IP
    requestRateLimiter.overLimitAsync("ip:"+servletRequestContext.getRemoteAddr())
      .whenComplete((overlimit, error) -> {
        // Error case
        if(error != null)
          suspendableRequestContext.resume(error);
        // Over limit
        else if(overlimit)
          suspendableRequestContext.abortWith(Response.status(429).build());
        // Good to go!
        else
          suspendableRequestContext.resume();
      });
  }
}

Now all we have left to do is to implement a resource with rate-limiting:

 

@Path("/")
public class Resource {

  @Path("free")
  @GET
  public String free() {
    return "Hello Free World";
  }

  @RateLimit(duration = 10, unit = TimeUnit.SECONDS, maxRequest = 2)
  @Path("limited")
  @GET
  public String limited() {
    return "Hello Limited World";
  }
}

 

If you go to /free you will get an unlimited number of requests, while if you go to /limited you will get two requests allowed every 10 seconds. The rest of the time you will get an HTTP response of Too Many Requests (429).

 

If you have the need for asynchronous request or response filters, don't hesitate to give RESTEasy 3.5.1.Final or 4.0.0.Beta2 a try.

Tracing feature is a way for the users of the RESTEasy to understand what's going on internally in the container when a request is processed. It's different from the pure logging system or profiling feature, which provide more general information about the request/response info, etc.

 

 

On the other hand, the tracing feature provides more internal states of the JAX-RS container. For example, it could be able to show what filters a request is going through, or how long time a request is processed, etc.

 

Introduction to the design of tracing feature

Currently it doesn't have a standard or spec to define the tracing feature, so the tracing feature is tightly coupled with the concrete JAX-RS implementation itself.

 

 

The RESTEasy tracing feature supports three working modes:

 

- OFF
- ON_DEMAND
- ALL

 

ALL will enable the tracing feature. ON_DEMAND mode will give the control to client side: A client can send a tracing request via HTTP header and get the tracing info back from response headers. OFF mode will disable the tracing feature, and this is the default mode.

 

 

On the other aspect, the tracing feature has different tracing logging levels. Here is the list of the levels:

 

- SUMMARY
- TRACE
- VERBOSE

 

The SUMMARY level will emit some brief tracing information. The TRACE level will produce more detailed tracing information, and the VERBOSE level will generate extremely detailed tracing information. Because there are no specs on these tracing levels yet, so the level of the tracing info is currently defined by RESTEasy internally.

 

 

The tracing feature uses the JBoss Logging framework to output the trace log, so the jboss logger configuration controls the final output of the tracing info. If you enable the tracing feature but disable the jboss logger output, you still can't get the tracing info you want. In addition, the tracing logging levels are mapped to jboss logger log levels, which means the jboss logger controls is the actual place to control the tracing level threshold.

 

Examples of using tracing feature

 

By default, the tracing feature is turned off. If you want to enable the tracing feature, you can set the tracing mode and tracing level via the context-param parameters in your web project’s web.xml file. Here is an example of the setting:

 

  resteasy.server.tracing.type
  ALL


  resteasy.server.tracing.threshold
  SUMMARY

 

With above setting, we have enabled the server tracing, and put the tracing level to summary. If the underlying jboss logger’s output threadshold is higher than the tracing level setting, then the users can start to get the tracing info from server side and from response headers.

 

 

Here is some sample text of the server side tracing log:

 

16:06:40,794 INFO  [general] PRE_MATCH_SUMMARY PreMatchRequest
summary: 0 filters [ 0.03 ms]
16:06:40,797 DEBUG [general] REQUEST_FILTER Filter by
[io.weli.tracing.HttpMethodOverride @50d53072] [ 0.09 ms]
16:06:40,797 DEBUG [general] REQUEST_FILTER Filter by
[org.jboss.resteasy.core.AcceptHeaderByFileSuffixFilter @7e6bde58] [
0.03 ms]
16:06:40,798 INFO  [general] REQUEST_FILTER_SUMMARY Request summary: 2
filters [ 1.24 ms]
16:06:40,804 DEBUG [general] REQUEST_FILTER Filter by
[org.jboss.resteasy.plugins.providers.sse.SseEventSinkInterceptor
@27930ef8 #2147483647] [ 0.50 ms]
16:06:40,804 INFO  [general] REQUEST_FILTER_SUMMARY Request summary: 1
filters [ 0.93 ms]
16:06:40,813 INFO  [general] METHOD_INVOKE Resource [SINGLETON|class
io.weli.tracing.TracingConfigResource|io.weli.tracing.TracingConfigResource@7a1234bf]
method=[public java.lang.String
io.weli.tracing.TracingConfigResource.type(org.jboss.resteasy.spi.ResteasyDeployment)]
[10.67 ms]
16:06:40,813 DEBUG [general] DISPATCH_RESPONSE Response:
[org.jboss.resteasy.specimpl.BuiltResponse @28a0b6dc
<200/SUCCESSFUL|OK|java.lang.String @52a345f7>] [ ---- ms]
16:06:40,814 INFO  [general] FINISHED Response status: 200 [ ---- ms]
16:06:40,827 DEBUG [general] RESPONSE_FILTER Filter by
[org.jboss.resteasy.plugins.interceptors.MessageSanitizerContainerResponseFilter
@35bb27cd #4000] [ 0.02 ms]
16:06:40,832 INFO  [general] RESPONSE_FILTER_SUMMARY Response summary:
2782639920301360 filters [2782639925.90 ms]

 

 

 

For client side, here is some sample text in response header:

 

16:06:40,938 FINE  [headers] http-outgoing-0 << HTTP/1.1 200 OK
16:06:40,939 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-008: FINISHED    [ ---- /  3.16 ms |  ---- %]
Response status: 200
16:06:40,939 FINE  [headers] http-outgoing-0 << Date: Fri, 08 Jun 2018
08:06:40 GMT
16:06:40,939 FINE  [headers] http-outgoing-0 << Connection: keep-alive
16:06:40,939 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-000: PRE-MATCH   [ 0.00 /  0.00 ms |  0.12 %]
PreMatchRequest summary: 0 filters
16:06:40,939 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-001: REQ-FILTER  [ 0.01 /  0.33 ms |  0.23 %]
Filter by [io.weli.tracing.HttpMethodOverride @50d53072]
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-002: REQ-FILTER  [ 0.01 /  0.60 ms |  0.18 %]
Filter by [org.jboss.resteasy.core.AcceptHeaderByFileSuffixFilter
@7e6bde58]
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-003: REQ-FILTER  [ 0.54 /  0.85 ms | 17.03 %]
Request summary: 2 filters
16:06:40,940 FINE  [headers] http-outgoing-0 << Content-Type:
application/octet-stream
16:06:40,940 FINE  [headers] http-outgoing-0 << Content-Length: 48
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-004: REQ-FILTER  [ 0.01 /  1.47 ms |  0.28 %]
Filter by [org.jboss.resteasy.plugins.providers.sse.SseEventSinkInterceptor
@27930ef8 #2147483647]
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-005: REQ-FILTER  [ 0.26 /  1.71 ms |  8.34 %]
Request summary: 1 filters
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-006: INVOKE      [ 0.76 /  2.23 ms | 24.08 %]
Resource [SINGLETON|class
io.weli.tracing.TracingConfigResource|io.weli.tracing.TracingConfigResource@7a1234bf]
method=[public java.lang.String
io.weli.tracing.TracingConfigResource.logger(org.jboss.resteasy.spi.HttpRequest)
throws java.lang.NoSuchMethodException]
16:06:40,941 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-007: INVOKE      [ ---- /  2.96 ms |  ---- %]
Response: [org.jboss.resteasy.specimpl.BuiltResponse @7534fda1
<200/SUCCESSFUL|OK|java.lang.String @7bf31d77>]

 

 

Above is some sample tracing text. Currently the tracing feature is still under development, and more tracing info entries will be provided to the users. And the formal document will be provided as the development is going on.

 

If you’d like to have a look at the tracing feature in action, you can current see the simple test case in RESTEasy master branch:

 

https://github.com/resteasy/Resteasy/blob/master/testsuite/integration-tests/src/test/java/org/jboss/resteasy/test/tracing/BasicTracingTest.java

 

In above is the brief description of the RESTEasy document.

JAX-RS 1.0: The blocking origins

 

Originally, JAX-RS code use to be blocking and straightforward, like this resource for example:

 

@Path("/hello")
public class HelloResource {

    @Path("classic")
    @GET
    public String classic() {
        return "Hello World";
    }

}

 

Here, the resource method would return a value immediately. If it would take a long time producing that value, the server would make one of its thread block for the entire duration of the request until it was returned, but more often than not, producing values took negligible time.

 

I say negligible, but with the appearance of micro-services, this became much less true. Let's take for example a micro-service that runs parallel to our first service, and delegates to it:

 

@Path("/service")
public class ServiceResource {


    @Context 
    private UriInfo uriInfo;
    
    private URI getUri(Class klass, String method) {
        return uriInfo.getBaseUriBuilder().path(klass).path(klass, method).build();
    }
    
    @Path("classic")
    @GET
    public String classic() {
        Client client = ClientBuilder.newClient();
        try {
            URI uri = getUri(HelloResource.class, "classic");
            String entity = client.target(uri).request().get(String.class);
            return "Service got: "+entity;
        } finally {
            client.close();
        }
    }
}

 

Here we're using the JAX-RS 2.0 REST client to make a blocking HTTP call to our classic hello resource, and then we return it. This is already a heavier class of resource method: one where the thread delegated to it will have to wait for IO for the web-service call to complete, before the response is returned to the client. So it will be more thread-intensive, where those threads will mostly sit around waiting for IO, which is less than optimal.

 

JAX-RS 2.0: First taste of asynchrony

 

JAX-RS 2.0 saw the writing on the wall and added support for asynchronous processing: you could now tell the container to suspend the request until you were ready to resume it. You could then run your heavy computation in another thread until you were ready to resume it without blocking the resource method threads:

 

@Path("/hello")
public class HelloResource {


    @Path("suspended")
    @GET
    public void suspended(@Suspended AsyncResponse response) {
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            response.resume("Hello World (resumed)");
        }).start();
    }
}

Naturally this is a bit contrived, with sleep as an example of a heavy task, and creating a new unmanaged thread, but in reality this allowed you to launch your IO requests in an IO pool that would be more efficient and would not block the resource method threads, and once you were done with your IO you could then resume it:

 

@Path("/service")
public class ServiceResource {

//...

    @Path("suspended")
    @GET
    public void suspended(@Suspended AsyncResponse response) {
        Client client = ClientBuilder.newClient();
        URI uri = getUri(HelloResource.class, "suspended");
        client.target(uri).request().async().get(new InvocationCallback() {


            @Override
            public void completed(String entity) {
                response.resume("Service got: "+entity);
            }


            @Override
            public void failed(Throwable throwable) {
                response.resume(throwable);
            }
        });
        response.register((CompletionCallback)t -> client.close());
    }
}

 

In this last example, we used the asynchronous support of the JAX-RS client to offload the IO processing to an IO worker, and notify us when we have a result to send back to our client by resuming our suspended request. Notice how everything got more complex than the original blocking example? We had to suspend the request, register listeners for success and failure, and a custom finally block.

 

JAX-RS 2.1: Getting Reactive with CompletionStage

 

JAX-RS 2.1 shipped with awesome features, such as support for the Promise-like JDK CompletionStage for dealing with asynchronous code without the associated Callback Hell.

 

Callback Hell

 

It became simpler to write asynchronous code because you didn't have to manually suspend the request: your return type of CompletionStage indicated that the request should be suspended until that CompletionStage was completed (successfully or not):

 

@Path("/hello")
public class HelloResource {

    @Path("completion-stage")
    @GET
    public CompletionStage completionStage() {
        CompletableFuture future = new CompletableFuture<>();
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                future.completeExceptionally(e);
                return;
            }
            future.complete("Hello World (resumed from CompletionStage)");
        }).start();
        return future;
    }
}

 

In this (yet again) contrived example, you can see we no longer had to use the @Suspended annotation or deal with the request in any way: we simply returned a CompletableFuture (which implements CompletionStage), and the container is responsible for registering a listener on it. Meanwhile, when we're ready to send our return value, we complete the CompletableFuture and that will resume the request.

 

Naturally, again in this case the benefits shine more when it comes to composition, because JAX-RS 2.1 also provides a Reactive REST Client interface via the rx() method, which allows us to get a CompletionStage for the client request, which becomes much easier to compose and return now:

 

@Path("/service")
public class ServiceResource {

  // ...

    @Path("completion-stage")
    @GET
    public CompletionStage completionStage() {
        Client client = ClientBuilder.newClient();
        URI uri = getUri(HelloResource.class, "completionStage");
        CompletionStage future = client.target(uri).request().rx().get(String.class);
        return future
                .thenApply(entity -> "Service got: "+entity)
                .whenComplete((entity, throwable) -> client.close());
    }
}

 

Here we really see the benefits of using reactive: we can provide a pipeline of work, on which we can register operations (thenApply) and even our finally block (whenComplete) that we just pass along to the container without doing any manual plumbing.

 

New in Resteasy: pluggable support for reactive libraries

It may have sounded like we were at the end of the adventure and had solved every issue, but in reality CompletionStage is great, but not that rich: libraries such as Reactive Streams and RxJava 1 and 2 provide types that are much richer than CompletionStage, which allow you to compose many more operations in an easier manner.

 

JAX-RS 2.1 added support for pluggable reactive libraries in the Reactive REST Client, via the rx(RxInvoker) method, but did not ship with any default implementation of it short of the default CompletionStage. But annoyingly it is not symmetrical because there is no support for pluggable resource method return types! You can make JAX-RS 2.1 support RxJava in the Reactive REST Client, but you cannot return RxJava values from your resource methods.

 

The latest Resteasy snapshots fix both issues:

 

Those mean that you can now implement your hello resource using RxJava 2:

 

@Path("/hello")
public class HelloResource {
    
    @Path("rx")
    @GET
    public Single rx() {
        return Single.just("Hello World (resumed from rx)")
                .delay(1, TimeUnit.SECONDS);
    }
}

 

And our service similarly:

 

@Path("/service")
public class ServiceResource {

  // ...
    
    @Path("rx")
    @GET
    public Single rx() {
        Client client = ClientBuilder.newClient();
        URI uri = getUri(HelloResource.class, "rx");
        Single ret = client.target(uri).request().rx(SingleRxInvoker.class).get(String.class);
        return ret.map(entity -> "Service got: "+entity)
                .doFinally(() -> client.close());
    }   
}

 

As you can see, it's now much easier to build Reactive pipelines in Resteasy.

 

Note that although the pluggable support for reactive types landed in Resteasy 3.5.0.Final and 4.0.0.Beta2 releases, the RxInvoker implementations are only available in the latest 4.0.0-SNAPSHOT snapshots (they're likely being added to 3.6 branch soon, though).

You know DRY (Don't Repeat Yourself) and you wish every feature would be reviewed according to this criteria?

Good news everyone!

I've always found it annoying to repeat the name of the parameter variable in my JAX-RS resources:

 

@Path("param/{userId}")
public class ParamResource {
    
    @PathParam("userId")
    private String userId;
    
    @GET
    @Path("{orderId}")
    public String getOrder(@PathParam("orderId") String orderId) {
        return "User "+userId+" / Order "+orderId;
    }
}

 

I mean: surely with all the magic that JAX-RS does to help me, it could have figured out how to not make me duplicate, every single time, the parameter name I'm injecting? In JAX-RS there are six parameter types you can inject from the request: path, query, matrix, cookie, header and form (respectively @PathParam, @QueryParam, @MatrixParam, @CookieParam and @FormParam). These annotations can be placed on fields, JavaBean property setters, or resource method parameters. Using Java reflection, it's pretty trivial to extract the field or JavaBean property name, but until Java 8, it has always been problematic to extract the resource method parameter name. This is probably why duplicating the parameter name in the annotation has always been required.

 

With Java 8, the Java compiler can actually record method parameter names in the bytecode. OK, ok, you can argue that we've always had those parameter names in the bytecode since the first Java release, because they're present in debugging symbols, which most people enable. But those debugging symbols are slightly annoying to get at because they require more than just reflection. In any case, with Java 8 it's become really trivial, so we decided to add new parameter annotations that would allow you to avoid specifying the name if it's the same as the variable you're annotating. Don't worry: if you want to have a different parameter name, you can always specify it (it's just optional).

 

We've kept the annotation names the same, and of course, the JAX-RS annotations are still supported, but if you want, you can now import annotations from the org.jboss.resteasy.annotations.jaxrs package, and start removing all those duplicated parameter names from the annotations, leading to much DRY-er code like this:

 

import org.jboss.resteasy.annotations.jaxrs.*;


@Path("param/{userId}")
public class ParamResource {
    
    @PathParam
    private String userId;
    
    @GET
    @Path("{orderId}")
    public String getOrder(@PathParam String orderId) {
        return "User "+userId+" / Order "+orderId;
    }
}

 

Although this will work out of the box for fields and JavaBean property setters, if you want it to work for resource method parameters, don't forget to tell your Java compiler to add parameter names using one or more of the following methods:

 

  • Using javac: add the -parameters flag.
  • Using Maven: add the maven.compiler.parameters property:
<properties>
  <maven.compiler.parameters>true</maven.compiler.parameters>
</properties>
  • Using Eclipse: Go to Project > Properties > Java Compiler and select Store information about method parameters (usable via reflection).
  • Using IntelliJ IDEA: Go to Build, Execution, Deployment > Compiler > Java Compiler > Additional command line parameters and add the -parameters flag.

 

Note that you will need to use the 4.0.0-SNAPSHOT or 3.6.0-SNAPSHOT versions of RESTEasy to try this, because the feature hasn't been included in a release yet.

Back in August last year, Fabio Carvalho from PayPal has blogged here on how to use RESTEasy in a Spring Boot application, relying on a Spring Boot starter project that he basically developed. What happened in the following months is that PayPal decided to donate the project to Red Hat.

So here I am today, publicly thanking Fabio and PayPal and announcing the first final release of the rebranded RESTEasy Spring Boot starter.

The new project sources are on GitHub under the RESTEasy organization; the issue tracker is the same as the main RESTEasy project and previously existing issues have been migrated there.

Compared to the latest release from PayPal repository, the first Red Hat release, 1.0.0.Final, brings an update to Spring Boot 1.5.12.RELEASE version and explicit support for Java 9 & 10.

In the next future, the plan is to move forward embracing Spring Boot 2 and integrating more recent versions of the core RESTEasy (3.5 / 3.6 streams).

So please stay tuned, play with the latest release and feel free to contribute!

Two months after last round of releases, here we are again with two RESTEasy bug fix versions, 4.0.0.Beta3 and 3.5.1.Final.

After we completed the implementation of JAX-RS 2.1 and shipped it few months ago within WildFly 12, we started getting some feedback about it. I'm really happy about the interest coming from the community, thanks and please keep on playing with RESTEasy ;-) Anyway, thanks to users contributions and some additional internal testing, we've fixed a bunch of issues especially around JSON-B support and SSE.

On top of that, with JDK 10 having been officially released, we had to ensure that the project can be successfully built and tested with it. So if you're running with Java 10, upgrading to RESTEasy 3.5.1.Final / 4.0.0.Beta3 is really something you should evaluate.

The full release notes are available here. The Maven artifacts are available  for both releases on the usual repository.

Next round of releases will most likely include few new and interesting features, stay tuned... ;-)

Three months after previous announcement, here we are with another step towards RESTEasy 4! Among the achievements in 4.0.0.Beta2 version we have:

The full release notes are available on jira, the artifacts are built and published on Maven repositories and the sources are on GitHub as usual ;-)

 

To align with the WildFly roadmap and increase adoption, during the last month the team has worked hard on backporting most of the new features and all bug fixes contributed so far on master to a new dev stream forked from the 3.0.x branch. The result is today's release of RESTEasy 3.5.0.Final, which is helping WildFly on the road to Java EE 8 compliance by providing JAX-RS 2.1 implementation while ensuring full backward compatibility with the RESTEasy versions it previously shipped with.

RESTEasy 3.5.0.Final basically allows easy access to the 4.0.0.Beta1 / 4.0.0.Beta2 stable features by offering a painless upgrade path to the 3.0.x users.

The natural upgrade path for users already on RESTEasy 3.1 series is straight to RESTEasy 4.0.0.Beta2 instead.

 

RESTEasy 3.5.0.Final binaries and sources are available on resteasy.jboss.org, together with the official documentation. Release notes on jira and Maven artifacts on the repository, as usual.

Please consider trying the latest release and providing feedback!

Enjoy :-)

Back at the end of July I mentioned that the team would have focussed on JSR-370... and here I am few months later, happy to announce that the first Beta release of RESTEasy 4 has been tagged over the weekend and it features JAX-RS 2.1 implementation.

However this beta comes with more additions... besides for the implementation of the latest specification for RESTful webservices in Java, here is a brief list of what's new:

The full release notes are available on jira, the artifacts are built and published on Maven repositories and the sources are on GitHub as usual ;-)

Please consider trying the latest release and providing feedback!

Thanks

Spring Boot

 

Spring Boot is Spring "convention-over-configuration" solution for creating stand-alone, production-grade Spring-based Applications, usually contained in a single and executable jar. It is also an opinionated framework, allowing Spring platform and third-party libraries to be auto-configured, minimizing the need for boilerplate code.

 

One of the most important concepts in Spring Boot is the notion of "starter". According to Spring Boot reference guide, "starters are a set of convenient dependency descriptors that you can include in your application". Another way to see starters is as "the building blocks that leverage auto-configuration when extending your application with Spring or other third-party components".

 

Because of all features Spring Boot offers, it ends up being a very convenient platform to build Java microservices applications, especially considering that Spring Boot has also starters for embedded Servlet containers, such as Tomcat and Jetty.

 

RESTEasy and Spring Boot

 

When it comes to JAX-RS applications, Spring Boot applications can also be RESTEasy applications, and that is possible by using RESTEasy Spring Boot starter, which is an open source project initially developed by PayPal, and endorsed by RESTEasy team.

 

Using RESTEasy in a Spring Boot application

 

Using RESTEasy in a Spring Boot application is very simple, just follow the steps below.

  1. Add dependency com.paypal.springboot:resteasy-spring-boot-starter to your Spring Boot application (it is recommended to use the latest version).
  2. Optionally, register one or more JAX-RS Application classes. To do so, just define it as a Spring bean, and it will be automatically registered.
  3. Finally, to register JAX-RS resources and providers, just define them as Spring beans, and they will be automatically registered.

 

Notice that JAX-RS resources can be singleton or request scoped, while JAX-RS providers must be singletons.

 

See this RESTEasy Spring Boot sample application as an example.

 

References

 

  1. RESTEasy Spring Boot Starter
  2. Spring Boot
  3. Spring Framework - Wikipedia
  4. Spring Boot Reference Guide
  5. Spring Initializr
  6. Microservices in a Nutshell

Filter Blog

By date:
By tag: