87

I deploy a web-services component to JBoss Application Server 7 using the RESTEasy JAX-RS implementation.

Is there an annotation available to declare required, mandatory @QueryParam parameters in JAX-RS ? And, if not, what is the 'standard' way to deal with situations where such parameters are missing?

My web service (resource) methods return JSON-stringified results when properly invoked with all the mandatory arguments, but I'm not sure what is the best way to indicate to the caller that a required parameter was missing.

Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277
Marcus Junius Brutus
  • 26,087
  • 41
  • 189
  • 331
  • 4
    You could add a `@DefaultValue` annotation and set the parameter to an appropriate value whenever it's missing. If you can't have a default value and the parameter is really important, perhaps you should check the param for `null` and return a `400 Bad request` status code. – toniedzwiedz Dec 20 '12 at 08:52

4 Answers4

83

Good question. Unfortunately (or maybe fortunately) there is no mechanism in JAX-RS to make any params mandatory. If a parameter is not supplied it's value will be NULL and your resource should deal with it accordingly. I would recommend to use WebApplicationException to inform your users:

@GET
@Path("/some-path")
public String read(@QueryParam("name") String name) {
  if (name == null) {
    throw new WebApplicationException(
      Response.status(Response.Status.BAD_REQUEST)
        .entity("name parameter is mandatory")
        .build()
    );
  }
  // continue with a normal flow
}
bekce
  • 3,782
  • 29
  • 30
yegor256
  • 102,010
  • 123
  • 446
  • 597
  • 16
    The [documentation for JAX-RS 1.0](https://wikis.oracle.com/display/Jersey/Overview+of+JAX-RS+1.0+Features) says that it won't always be null. It will be "an empty collection for List, Set or SortedSet, null for other object types, and the Java-defined default for primitive types." – hotshot309 Mar 12 '13 at 20:26
  • 3
    `String` is not a primitive type, so it's "null for other object types" – yegor256 Mar 13 '13 at 09:40
  • 12
    Also suggest not using HttpURLConnection.HTTP_BAD_REQUEST, but rather javax.ws.rs.core.Response.Status.BAD_REQUEST to stay in keeping with the method's expected parameters. – cmonkey Nov 04 '13 at 19:22
  • 8
    **Note from the distant future:** There is a [`BadRequestException`](https://docs.oracle.com/javaee/7/api/javax/ws/rs/BadRequestException.html) which can be thrown and which does more or less what the code above does but also allows you to specifically catch this exception programmatically. – errantlinguist Jun 13 '16 at 10:38
77

You can use javax.validation annotations to enforce that the parameters are mandatory by annotating them with @javax.validation.constraints.NotNull. See an example for Jersey and one for RESTeasy.

So your method would simply become:

@GET
@Path("/some-path")
public String read(@NotNull @QueryParam("name") String name) {
  String something = 
  // implementation
  return something;
}

Note that the exception gets then translated by the JAX-RS provider to some error code. It can usually be overridden by registering your own implementation of javax.ws.rs.ext.ExceptionMapper<javax.validation.ValidationException>.

This provides a centralized way to translate mandatory parameter to error responses and no code duplication is necessary.

Giovanni Botta
  • 9,626
  • 5
  • 51
  • 94
  • 13
    One issue with this approach is that the error message doesn't specify the name of absent parameter, something like "arg1 may not be null". Luckily Bean Validation spec introduced interface javax.validation.ParameterNameProvider. For JAX-RS we can use annotations QueryParam and PathParam to get the names (as reflection doesn't allow fetching parameter names). An example can be found here: http://stackoverflow.com/q/22496527/998772 – pavel_kazlou May 03 '14 at 12:33
  • Yeah I went through that pain and asked a [question about it](http://stackoverflow.com/questions/22567097/jersey-bean-validation-parameternameprovider). It's doable, just a little bit more code to write. – Giovanni Botta May 04 '14 at 15:45
  • 1
    I am trying to do similar thing but @NotNull is not detecting it even if I omit this query parameter from URL. I have started a thread here [link](http://stackoverflow.com/questions/25737837/notnull-annotation-is-not-checking-null-queryparameter-in-jersey-rest-resource) – Bruso Sep 09 '14 at 08:14
  • 1
    For anyone reading in 2018, the @NotNull annotation now results in a javax.validation.ConstraintViolationException, not a ValidationException, so you'll need a different ExceptionMapper if you want to handle it yourself. See the accepted answer to this question https://stackoverflow.com/questions/18015630/binding-jax-rs-bean-validation-error-messages-to-the-view – Martin Charlesworth Sep 20 '18 at 13:38
18

I ran into the same problem and decided that I did not want a gazillion boilerplate null checks scattered across my REST code, so this this is what I decided to do:

  1. Create an annotation that causes an exception to be thrown when a required parameter is not specified.
  2. Handle the thrown exception the same way I handle all other exceptions thrown in my REST code.

For 1), i implemented the following annotation:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Required
{
    // This is just a marker annotation, so nothing in here.
}

... and the following JAX-RS ContainerRequestFilter to enforce it:

import java.lang.reflect.Parameter;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;

@Provider
public class RequiredParameterFilter implements ContainerRequestFilter
{
    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext requestContext)
    {
        // Loop through each parameter
        for (Parameter parameter : resourceInfo.getResourceMethod().getParameters())
        {
            // Check is this parameter is a query parameter
            QueryParam queryAnnotation = parameter.getAnnotation(QueryParam.class);

            // ... and whether it is a required one
            if (queryAnnotation != null && parameter.isAnnotationPresent(Required.class))
            {
                // ... and whether it was not specified
                if (!requestContext.getUriInfo().getQueryParameters().containsKey(queryAnnotation.value()))
                {
                    // We pass the query variable name to the constructor so that the exception can generate a meaningful error message
                    throw new YourCustomRuntimeException(queryAnnotation.value());
                }
            }
        }
    }
}

You need to register the ContainerRequestFilter in the same way you would register your other @Provider classes with your JAX-RS library. Maybe RESTEasy does it for you automatically.

For 2), I handle all runtime exceptions using a generic JAX-RS ExceptionMapper:

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class MyExceptionMapper implements ExceptionMapper<RuntimeException>
{
    @Override
    public Response toResponse(RuntimeException ex)
    {
        // In this example, we just return the .toString() of the exception. 
        // You might want to wrap this in a JSON structure if this is a JSON API, for example.
        return Response
            .status(Response.Status.BAD_REQUEST)
            .entity(ex.toString())
            .build();
    }
}

As before, remember to register the class with your JAX-RS library.

Zero3
  • 594
  • 2
  • 11
  • 18
  • 2
    Does this offer something that @javax.validation.constraints.NotNull wouldn't do? – Michael Haefele Mar 20 '17 at 14:07
  • 2
    @MichaelHaefele It retains the parameter name, which is useful for showing a meaningful error message. The parameter name is lost if you use the `NotNull` annotation, which is unfortunate. That was the issue that made me write me own annotation. But also see https://stackoverflow.com/questions/13968261/required-queryparam-in-jax-rs-and-what-to-do-in-their-absence/31893626?noredirect=1#comment35934905_21920512. Things might have changed since I wrote this code. – Zero3 Mar 20 '17 at 16:08
  • One of the key features of this solution for me is to have fine grained control over what you want to show in the exception or not. – alexander Apr 15 '20 at 09:23
3

Probably the easiest way is to use @Nonnull from javax.annotation to achieve this. It's super simple to use as all you have to do is add it before @QueryParam as shown below.

However, keep in mind that this will throw an IllegalArgumentException when the parameter is null so the response you send back will be whatever you do for an exception. If you don't intercept it it's going to be a 500 Server Error even though the correct thing to send back would be a 400 Bad Request. You can intercept IllegalArgumentException and process it to return a proper response.


Example:

import javax.annotation.Nonnull;
...

    @GET
    @Path("/your-path")
    public Response get(@Nonnull @QueryParam("paramName") String paramName) {
        ... 
    }

The default error message returned to the caller looks like this:

{"timestamp":1536152114437,"status":500,"error":"Internal Server Error","exception":"java.lang.IllegalArgumentException","message":"Argument for @Nonnull parameter 'paramName' of com/example/YourClass.get must not be null","path":"/path/to/your-path"}

Vlad Schnakovszki
  • 8,434
  • 6
  • 80
  • 114