Modern Spring Notes

Russell Bateman
September 2017
last update:

I was enthusiastic about Spring circa 2009, but by 2012, found it ugly-heavy and burdensome. I'm getting a little more excited by the truly useful stuff I see it helping me with in the future. I hope I can exclude the heaviness, maybe via Java 9's superior module capability.

Using Spring's @RequestMapping annotation...

Spring offers this annotation for use in configuring web requests. It can be applied at the class- or method level. This class is known as the "controller class" as marked by the @RestController method.

@RestController
@RequestMapping( "/home" )
public class IndexController
{
  @RequestMapping( "/" )
  String get()
  {
    // mapped to hostname:port/home/
    return "Hello from get";
  }

  @RequestMapping( "/index" )
  String index()
  {
    // mapped to hostname:port/home/index/
    return "Hello from index";
  }
}

Requests to /home will go to get() while to /home/index will go to index().

Multiple request mappings for a single method:

@RestController
@RequestMapping( "/home" )
public class IndexController
{
  @RequestMapping( value =
  {
    "",
    "/page",
    "page*",
    "view/*,**/msg"
  } )
  String indexMultipleMapping()
  {
    return "Hello from index multiple mapping.";
  }
}

In the above, the following URLs will be covered by method indexMultipleMapping():

Using @RequestParam...

In the highlighted line, the request (query) parameter id will be mapped to the method's personId argument. In the second method, getId(), note that the annotation's parameter list is unused—not needing the verbosity shown in the first method because the internal name of the value argument to @RequestParam is identical to that of the method argument it's annotating.

@RestController
@RequestMapping( "/home" )
public class IndexController
{
  @RequestMapping( value = "/id" )
  String getIdByValue( @RequestParam( "id" ) String personId )
  {
    System.out.println( "ID is " + personId );
    return "Get ID from query string of URL with value element";
  }

  @RequestMapping( value = "/personId" )
  String getId( @RequestParam String personId )
  {
    System.out.println( "ID is " + personId );
    return "Get ID from query string of URL without value element";
  }
}

Moreover, you can make the parameter optional by expanding this annotation:

@RestController
@RequestMapping( "/home" )
public class IndexController
{
  @RequestMapping( value = "/id" )
  String getIdByValue( @RequestParam( value = "id", required = false ) String personId )
  {
    System.out.println( "ID is " + personId );
    return "Get ID from query string of URL with value element";
  }

  @RequestMapping( value = "/personId" )
  String getId( @RequestParam String personId )
  {
    System.out.println( "ID is " + personId );
    return "Get ID from query string of URL without value element";
  }
}

A default value may also be specified. This example is silly, but it gets the idea across:

@RestController
@RequestMapping( "/home" )
public class IndexController
{
  @RequestMapping( value = "/name" )
  String getName( @RequestParam( value = "person", defaultValue = "John" ) String personName )
  {
    return "Required element of request param";
  }
}

You can impose mapping of HTTP method to (Java-) coded methods:

@RestController
@RequestMapping( "/home" )
public class IndexController
{
  @RequestMapping( method = RequestMethod.GET )
  String get()
  {
    return "Hello from get";
  }

  @RequestMapping( method = RequestMethod.DELETE )
  String delete()
  {
    return "Hello from delete";
  }

  @RequestMapping( method = RequestMethod.POST )
  String post()
  {
    return "Hello from post";
  }

  @RequestMapping( method = RequestMethod.PUT )
  String put()
  {
    return "Hello from put";
  }

  @RequestMapping( method = RequestMethod.PATCH )
  String patch()
  {
    return "Hello from patch";
  }
}

Handling Accept and ContentType...

Control how the in-coming payload is consumed and also how the out-going response is produced using consumes (Accepts) and produces (ContentType). Below, the first method will put out application/json while the second will accept either application/json or application/xml:

Note: in order to impose production of the object in the requested media type, you must use @RequestMapping in combination with annotation @ResponseBody!

@RestController
@RequestMapping( "/home" )
public class IndexController
{
  @RequestMapping( value = "/prod",
               produces = { "application/json" } )
  @ResponseBody
  String getProduces()
  {
    return "Produces attribute";
  }

  @RequestMapping( value = "/cons",
               consumes = { "application/json", "application/xml" } )
  String getConsumes()
  {
    return "Consumes attribute";
  }
}

Handling headers

The @RequestMapping annotation provides a head element to narrow down rather more overtly the request mapping based on what's in the actual request header. Using the one below, the post() method will handle requests to /home/head whose content is text/plain (or text/html).

@RestController
@RequestMapping( "/home" )
public class IndexController
{
  @RequestMapping( value = "/head",
               headers = { "Content-Type=text/plain", "Content-Type=text/html" } )
  String post()
  {
    return "Mapping applied along with headers";
  }
}

Splitting on params values to different methods...

@RestController
@RequestMapping( "/home" )
public class IndexController
{
  @RequestMapping( value = "/fetch", params = { "personId=10" } )
  String getParams( @RequestParam( "personId" ) String id )
  {
    return "Fetched parameter using params attribute = " + id;
  }

  @RequestMapping( value = "/fetch", params = { "personId=20" } )
  String getParamsDifferent( @RequestParam( "personId" ) String id )
  {
    return "Fetched parameter using params attribute = " + id;
  }
}

Dynamic URIs...

Note that an exception will be thrown if processing executes the GetDynamicUriValueRegex() and the URI fails to match the indicated regular expression.

@PathVariable works differently from @RequestParam. It's used to obtain the values of the query parameters from the URI while @RequestParam obtains the parameter values from the URI "template."

@RestController
@RequestMapping( "/home" )
public class IndexController
{
  @RequestMapping( value = "/fetch/{id}", method = RequestMethod.GET )
  String getDynamicUriValue( @PathVariable String id )
  {
    System.out.println( "ID is " + id );
    return "(Simple) dynamic URI parameter fetched";
  }

  @RequestMapping( value = "/fetch/{id:[a-z]+}/{name}", method = RequestMethod.GET )
  String getDynamicUriValueRegex( @PathVariable( "name" ) String name )
  {
    System.out.println( "Name is " + name );
    return "Dynamic URI parameter fetched using a regular expression";
  }
}

Default @RequestMapping...

A default handler (in a controller class) can be coded:

@RestController
@RequestMapping( "/home" )
public class IndexController
{
  @RequestMapping()
  String default()
  {
    return "This is a default method for the class";
  }

  ...more methods...
}

I suspect some trouble in which this method is over-applied based on the careful specificity of additional methods coded.

Useful wrapping of the @RequestMapping annotation...

In Spring 4.2, method-level variants for @RequestMapping (@GetMapping et al.) were introduced to express the semantics better. These have become standard (best) practice because clearer, more readable and the they reduce the configuration metadata the code must provide.

@RestController
@RequestMapping( "/home" )
public class IndexController
{
  @GetMapping( "/person" )
  public @ResponseBody ResponseEntity< String > getPerson()
  {
    return new ResponseEntity< String > ( "Response from GET", HttpStatus.OK );
  }

  @GetMapping( "/person/{id}" )
  public @ResponseBody ResponseEntity< String > getPersonById( @PathVariable String id )
  {
    return new ResponseEntity< String > ( "Response from GET with id " + id, HttpStatus.OK );
  }

  @PostMapping( "/person" )
  public @ResponseBody ResponseEntity< String > postPerson()
  {
    return new ResponseEntity< String > ( "Response from POST method", HttpStatus.OK );
  }

  @PutMapping( "/person" )
  public @ResponseBody ResponseEntity< String > putPerson()
  {
    return new ResponseEntity< String > ( "Response from PUT method", HttpStatus.OK );
  }

  @DeleteMapping( "/person" )
  public @ResponseBody ResponseEntity< String > deletePerson()
  {
    return new ResponseEntity< String > ( "Response from DELETE method", HttpStatus.OK );
  }

  @PatchMapping( "/person" )
  public @ResponseBody ResponseEntity< String > patchPerson()
  {
    return new ResponseEntity< String > ( "Response from PATCH method", HttpStatus.OK );
  }
}