GithubHelp home page GithubHelp logo

spring-guides / tut-rest Goto Github PK

View Code? Open in Web Editor NEW
498.0 41.0 406.0 436 KB

Building REST services with Spring :: Learn how to easily build RESTful services with Spring

Home Page: https://spring.io/guides/tutorials/rest/

Java 100.00%

tut-rest's Introduction

tags projects
rest
hateoas
hypermedia
security
testing
oauth
spring-framework
spring-hateoas
spring-security
spring-security-oauth

Building REST Services with Spring

REST has quickly become the de-facto standard for building web services on the web because they’re easy to build and easy to consume.

There’s a much larger discussion to be had about how REST fits in the world of microservices, but — for this tutorial — let’s just look at building RESTful services.

Why REST? REST embraces the precepts of the web, including its architecture, benefits, and everything else. This is no surprise given its author, Roy Fielding, was involved in probably a dozen specs which govern how the web operates.

What benefits? The web and its core protocol, HTTP, provide a stack of features:

  • Suitable actions (GET, POST, PUT, DELETE, …​)

  • Caching

  • Redirection and forwarding

  • Security (encryption and authentication)

These are all critical factors on building resilient services. But that is not all. The web is built out of lots of tiny specs, hence it’s been able to evolve easily, without getting bogged down in "standards wars".

Developers are able to draw upon 3rd party toolkits that implement these diverse specs and instantly have both client and server technology at their fingertips.

By building on top of HTTP, REST APIs provide the means to build:

  • Backwards compatible APIs

  • Evolvable APIs

  • Scaleable services

  • Securable services

  • A spectrum of stateless to stateful services

What’s important to realize is that REST, however ubiquitous, is not a standard, per se, but an approach, a style, a set of constraints on your architecture that can help you build web-scale systems. In this tutorial we will use the Spring portfolio to build a RESTful service while leveraging the stackless features of REST.

Getting Started

To get started, you will need:

As we work through this tutorial, we’ll use Spring Boot. Go to Spring Initializr and add the following dependencies to a project:

  • Spring Web

  • Spring Data JPA

  • H2 Database

Change the Name to "Payroll" and then choose "Generate Project". A .zip will download. Unzip it. Inside you’ll find a simple, Maven-based project including a pom.xml build file (NOTE: You can use Gradle. The examples in this tutorial will be Maven-based.)

To complete the tutorial you could start a new project from scratch or you could look to the solution repository in GitHub.

If you choose to create your own blank project, this tutorial will walk you through building your application sequentially.You will not have multiple modules.

Rather than providing a single, final solution, the completed GitHub repository uses modules to separate the solution into four parts. The modules in the GitHub solution repository build on one another, with the links module containing the final solution. The modules map to the following headers:

The Story so Far

Note
This tutorial starts by building up the code in the nonrest module.

Let’s start off with the simplest thing we can construct. In fact, to make it as simple as possible, we can even leave out the concepts of REST. (Later on, we’ll add REST to understand the difference.)

Big picture: We’re going to create a simple payroll service that manages the employees of a company. We’ll store employee objects in a (H2 in-memory) database, and access them (via something called JPA). Then we’ll wrap that with something that will allow access over the internet (called the Spring MVC layer).

The following code defines an Employee in our system.

nonrest/src/main/java/payroll/Employee.java
link:nonrest/src/main/java/payroll/Employee.java[role=include]

Despite being small, this Java class contains much:

  • @Entity is a JPA annotation to make this object ready for storage in a JPA-based data store.

  • id, name, and role are attributes of our Employee domain object. id is marked with more JPA annotations to indicate it’s the primary key and automatically populated by the JPA provider.

  • A custom constructor is created when we need to create a new instance, but don’t yet have an id.

With this domain object definition, we can now turn to Spring Data JPA to handle the tedious database interactions.

Spring Data JPA repositories are interfaces with methods supporting creating, reading, updating, and deleting records against a back end data store. Some repositories also support data paging, and sorting, where appropriate. Spring Data synthesizes implementations based on conventions found in the naming of the methods in the interface.

Note
There are multiple repository implementations besides JPA. You can use Spring Data MongoDB, Spring Data Cassandra, etc. For this tutorial, we’ll stick with JPA.

Spring makes accessing data easy. By simply declaring the following EmployeeRepository interface we automatically will be able to

  • Create new Employees

  • Update existing ones

  • Delete Employees

  • Find Employees (one, all, or search by simple or complex properties)

nonrest/src/main/java/payroll/EmployeeRepository.java
link:nonrest/src/main/java/payroll/EmployeeRepository.java[role=include]

To get all this free functionality, all we had to do was declare an interface which extends Spring Data JPA’s JpaRepository, specifying the domain type as Employee and the id type as Long.

Spring Data’s repository solution makes it possible to sidestep data store specifics and instead solve a majority of problems using domain-specific terminology.

Believe it or not, this is enough to launch an application! A Spring Boot application is, at a minimum, a public static void main entry-point and the @SpringBootApplication annotation. This tells Spring Boot to help out, wherever possible.

nonrest/src/main/java/payroll/PayrollApplication.java
link:nonrest/src/main/java/payroll/PayrollApplication.java[role=include]

@SpringBootApplication is a meta-annotation that pulls in component scanning, autoconfiguration, and property support. We won’t dive into the details of Spring Boot in this tutorial, but in essence, it will fire up a servlet container and serve up our service.

Nevertheless, an application with no data isn’t very interesting, so let’s preload it. The following class will get loaded automatically by Spring:

nonrest/src/main/java/payroll/LoadDatabase.java
link:nonrest/src/main/java/payroll/LoadDatabase.java[role=include]

What happens when it gets loaded?

  • Spring Boot will run ALL CommandLineRunner beans once the application context is loaded.

  • This runner will request a copy of the EmployeeRepository you just created.

  • Using it, it will create two entities and store them.

Right-click and Run PayRollApplication, and this is what you get:

Fragment of console output showing preloading of data
...
20yy-08-09 11:36:26.169  INFO 74611 --- [main] payroll.LoadDatabase : Preloading Employee(id=1, name=Bilbo Baggins, role=burglar)
20yy-08-09 11:36:26.174  INFO 74611 --- [main] payroll.LoadDatabase : Preloading Employee(id=2, name=Frodo Baggins, role=thief)
...

This isn’t the whole log, but just the key bits of preloading data. (Indeed, check out the whole console. It’s glorious.)

HTTP is the Platform

To wrap your repository with a web layer, you must turn to Spring MVC. Thanks to Spring Boot, there is little in infrastructure to code. Instead, we can focus on actions:

nonrest/src/main/java/payroll/EmployeeController.java
link:nonrest/src/main/java/payroll/EmployeeController.java[role=include]
  • @RestController indicates that the data returned by each method will be written straight into the response body instead of rendering a template.

  • An EmployeeRepository is injected by constructor into the controller.

  • We have routes for each operation (@GetMapping, @PostMapping, @PutMapping and @DeleteMapping, corresponding to HTTP GET, POST, PUT, and DELETE calls). (NOTE: It’s useful to read each method and understand what they do.)

  • EmployeeNotFoundException is an exception used to indicate when an employee is looked up but not found.

nonrest/src/main/java/payroll/EmployeeNotFoundException.java
link:nonrest/src/main/java/payroll/EmployeeNotFoundException.java[role=include]

When an EmployeeNotFoundException is thrown, this extra tidbit of Spring MVC configuration is used to render an HTTP 404:

nonrest/src/main/java/payroll/EmployeeNotFoundAdvice.java
link:nonrest/src/main/java/payroll/EmployeeNotFoundAdvice.java[role=include]
  • @RestControllerAdvice signals that this advice is rendered straight into the response body.

  • @ExceptionHandler configures the advice to only respond if an EmployeeNotFoundException is thrown.

  • @ResponseStatus says to issue an HttpStatus.NOT_FOUND, i.e. an HTTP 404.

  • The body of the advice generates the content. In this case, it gives the message of the exception.

To launch the application, either right-click the public static void main in PayRollApplication and select Run from your IDE, or:

Spring Initializr uses maven wrapper so type this:

$ ./mvnw clean spring-boot:run

Alternatively using your installed maven version type this:

$ mvn clean spring-boot:run

When the app starts, we can immediately interrogate it.

$ curl -v localhost:8080/employees

This will yield:

Details
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /employees HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Thu, 09 Aug 20yy 17:58:00 GMT
<
* Connection #0 to host localhost left intact
[{"id":1,"name":"Bilbo Baggins","role":"burglar"},{"id":2,"name":"Frodo Baggins","role":"thief"}]

Here you can see the pre-loaded data, in a compacted format.

If you try and query a user that doesn’t exist…​

$ curl -v localhost:8080/employees/99

You get…​

Details
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /employees/99 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 404
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 26
< Date: Thu, 09 Aug 20yy 18:00:56 GMT
<
* Connection #0 to host localhost left intact
Could not find employee 99

This message nicely shows an HTTP 404 error with the custom message Could not find employee 99.

It’s not hard to show the currently coded interactions…​

Caution

If you are using Windows Command Prompt to issue cURL commands, chances are the below command won’t work properly. You must either pick a terminal that support single quoted arguments, or use double quotes and then escape the ones inside the JSON.

To create a new Employee record we use the following command in a terminal—the $ at the beginning signifies that what follows it is a terminal command:

$ curl -X POST localhost:8080/employees -H 'Content-type:application/json' -d '{"name": "Samwise Gamgee", "role": "gardener"}'

Then it stores newly created employee and sends it back to us:

{"id":3,"name":"Samwise Gamgee","role":"gardener"}

You can update the user. Let’s change his role.

$ curl -X PUT localhost:8080/employees/3 -H 'Content-type:application/json' -d '{"name": "Samwise Gamgee", "role": "ring bearer"}'

And we can see the change reflected in the output.

{"id":3,"name":"Samwise Gamgee","role":"ring bearer"}
Warning
The way you construct your service can have significant impacts. In this situation, we said update, but replace is a better description. For example, if the name was NOT provided, it would instead get nulled out.

Finally, you can delete users like this:

$ curl -X DELETE localhost:8080/employees/3

# Now if we look again, it's gone
$ curl localhost:8080/employees/3
Could not find employee 3

This is all well and good, but do we have a RESTful service yet? (If you didn’t catch the hint, the answer is no.)

What’s missing?

What makes something RESTful?

So far, you have a web-based service that handles the core operations involving employee data. But that’s not enough to make things "RESTful".

  • Pretty URLs like /employees/3 aren’t REST.

  • Merely using GET, POST, etc. isn’t REST.

  • Having all the CRUD operations laid out isn’t REST.

In fact, what we have built so far is better described as RPC (Remote Procedure Call). That’s because there is no way to know how to interact with this service. If you published this today, you’d also have to write a document or host a developer’s portal somewhere with all the details.

This statement of Roy Fielding’s may further lend a clue to the difference between REST and RPC:

I am getting frustrated by the number of people calling any HTTP-based interface a REST API. Today’s example is the SocialSite REST API. That is RPC. It screams RPC. There is so much coupling on display that it should be given an X rating.

What needs to be done to make the REST architectural style clear on the notion that hypertext is a constraint? In other words, if the engine of application state (and hence the API) is not being driven by hypertext, then it cannot be RESTful and cannot be a REST API. Period. Is there some broken manual somewhere that needs to be fixed?

The side effect of NOT including hypermedia in our representations is that clients MUST hard code URIs to navigate the API. This leads to the same brittle nature that predated the rise of e-commerce on the web. It’s a signal that our JSON output needs a little help.

Spring HATEOAS

Introducing Spring HATEOAS, a Spring project aimed at helping you write hypermedia-driven outputs. To upgrade your service to being RESTful, add this to your build:

Note
If you are following along in the solution repository, the next section switches to the rest module.
Adding Spring HATEOAS to dependencies section of pom.xml
link:rest/pom.xml[role=include]

This tiny library will give us the constructs to define a RESTful service and then render it in an acceptable format for client consumption.

A critical ingredient to any RESTful service is adding links to relevant operations. To make your controller more RESTful, add links like this to the existing one method in EmployeeController:

Getting a single item resource
link:rest/src/main/java/payroll/EmployeeController.java[role=include]

You will also need to include new imports:

Details
import org.springframework.hateoas.EntityModel;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
Note

This tutorial is based on Spring MVC and uses the static helper methods from WebMvcLinkBuilder to build these links. If you are using Spring WebFlux in your project, you must instead use WebFluxLinkBuilder.

This is very similar to what we had before, but a few things have changed:

  • The return type of the method has changed from Employee to EntityModel<Employee>. EntityModel<T> is a generic container from Spring HATEOAS that includes not only the data but a collection of links.

  • linkTo(methodOn(EmployeeController.class).one(id)).withSelfRel() asks that Spring HATEOAS build a link to the EmployeeController 's one() method, and flag it as a self link.

  • linkTo(methodOn(EmployeeController.class).all()).withRel("employees") asks Spring HATEOAS to build a link to the aggregate root, all(), and call it "employees".

What do we mean by "build a link"? One of Spring HATEOAS’s core types is Link. It includes a URI and a rel (relation). Links are what empower the web. Before the World Wide Web, other document systems would render information or links, but it was the linking of documents WITH this kind of relationship metadata that stitched the web together.

Roy Fielding encourages building APIs with the same techniques that made the web successful, and links are one of them.

If you restart the application and query the employee record of Bilbo, you’ll get a slightly different response than earlier:

Tip
Curling prettier

When your curl output gets more complex it can become hard to read. Use this or other tips to prettify the json returned by curl:

# The indicated part pipes the output to json_pp and asks it to make your JSON pretty. (Or use whatever tool you like!)
#                                  v------------------v
curl -v localhost:8080/employees/1 | json_pp
RESTful representation of a single employee
{
  "id": 1,
  "name": "Bilbo Baggins",
  "role": "burglar",
  "_links": {
    "self": {
      "href": "http://localhost:8080/employees/1"
    },
    "employees": {
      "href": "http://localhost:8080/employees"
    }
  }
}

This decompressed output shows not only the data elements you saw earlier (id, name and role), but also a _links entry containing two URIs. This entire document is formatted using HAL.

HAL is a lightweight mediatype that allows encoding not just data but also hypermedia controls, alerting consumers to other parts of the API they can navigate toward. In this case, there is a "self" link (kind of like a this statement in code) along with a link back to the aggregate root.

To make the aggregate root ALSO more RESTful, you want to include top level links while ALSO including any RESTful components within.

So we turn this (located in the nonrest module of the completed code):

Getting an aggregate root
link:nonrest/src/main/java/payroll/EmployeeController.java[role=include]

into this (located in the rest module of the completed code):

Getting an aggregate root resource
link:rest/src/main/java/payroll/EmployeeController.java[role=include]

Wow! That method, which used to just be repository.findAll(), is all grown up! Not to worry. Let’s unpack it.

CollectionModel<> is another Spring HATEOAS container; it’s aimed at encapsulating collections of resources—instead of a single resource entity, like EntityModel<> from earlier. CollectionModel<>, too, lets you include links.

Don’t let that first statement slip by. What does "encapsulating collections" mean? Collections of employees?

Not quite.

Since we’re talking REST, it should encapsulate collections of employee resources.

That’s why you fetch all the employees, but then transform them into a list of EntityModel<Employee> objects. (Thanks Java 8 Streams!)

If you restart the application and fetch the aggregate root, you can see what it looks like now.

curl -v localhost:8080/employees | json_pp
RESTful representation of a collection of employee resources
{
  "_embedded": {
    "employeeList": [
      {
        "id": 1,
        "name": "Bilbo Baggins",
        "role": "burglar",
        "_links": {
          "self": {
            "href": "http://localhost:8080/employees/1"
          },
          "employees": {
            "href": "http://localhost:8080/employees"
          }
        }
      },
      {
        "id": 2,
        "name": "Frodo Baggins",
        "role": "thief",
        "_links": {
          "self": {
            "href": "http://localhost:8080/employees/2"
          },
          "employees": {
            "href": "http://localhost:8080/employees"
          }
        }
      }
    ]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/employees"
    }
  }
}

For this aggregate root, which serves up a collection of employee resources, there is a top-level "self" link. The "collection" is listed underneath the "_embedded" section; this is how HAL represents collections.

And each individual member of the collection has their information as well as related links.

What is the point of adding all these links? It makes it possible to evolve REST services over time. Existing links can be maintained while new links can be added in the future. Newer clients may take advantage of the new links, while legacy clients can sustain themselves on the old links. This is especially helpful if services get relocated and moved around. As long as the link structure is maintained, clients can STILL find and interact with things.

Note
If you are following along in the solution repository, the next section switches to the evolution module.

In the code earlier, did you notice the repetition in single employee link creation? The code to provide a single link to an employee, as well as to create an "employees" link to the aggregate root, was shown twice. If that raised your concern, good! There’s a solution.

Simply put, you need to define a function that converts Employee objects to EntityModel<Employee> objects. While you could easily code this method yourself, there are benefits down the road of implementing Spring HATEOAS’s RepresentationModelAssembler interface—which will do the work for you. Create a new class EmployeeModelAssembler as defined below:

evolution/src/main/java/payroll/EmployeeModelAssembler.java
link:evolution/src/main/java/payroll/EmployeeModelAssembler.java[role=include]

This simple interface has one method: toModel(). It is based on converting a non-model object (Employee) into a model-based object (EntityModel<Employee>).

All the code you saw earlier in the controller can be moved into this class. And by applying Spring Framework’s @Component annotation, the assembler will be automatically created when the app starts.

Note
Spring HATEOAS’s abstract base class for all models is RepresentationModel. But for simplicity, I recommend using EntityModel<T> as your mechanism to easily wrap all POJOs as models.

To leverage this assembler, you only have to alter the EmployeeController by injecting the assembler in the constructor.

Injecting EmployeeModelAssembler into the controller
link:evolution/src/main/java/payroll/EmployeeController.java[role=include]

  ...

}

From here, you can use that assembler in the single-item employee method one that already exists in EmployeeController:

Getting single item resource using the assembler
link:evolution/src/main/java/payroll/EmployeeController.java[role=include]

This code is almost the same, except instead of creating the EntityModel<Employee> instance here, you delegate it to the assembler. Maybe that doesn’t look like much.

Applying the same thing in the aggregate root controller method is more impressive. This change is also to the EmployeeController class:

Getting aggregate root resource using the assembler
link:evolution/src/main/java/payroll/EmployeeController.java[role=include]

The code is, again, almost the same, however you get to replace all that EntityModel<Employee> creation logic with map(assembler::toModel). Thanks to Java 8 method references, it’s super easy to plug it in and simplify your controller.

Important
A key design goal of Spring HATEOAS is to make it easier to do The Right Thing™. In this scenario: adding hypermedia to your service without hard coding a thing.

At this stage, you’ve created a Spring MVC REST controller that actually produces hypermedia-powered content! Clients that don’t speak HAL can ignore the extra bits while consuming the pure data. Clients that DO speak HAL can navigate your empowered API.

But that is not the only thing needed to build a truly RESTful service with Spring.

Evolving REST APIs

With one additional library and a few lines of extra code, you have added hypermedia to your application. But that is not the only thing needed to make your service RESTful. An important facet of REST is the fact that it’s neither a technology stack nor a single standard.

REST is a collection of architectural constraints that when adopted make your application much more resilient. A key factor of resilience is that when you make upgrades to your services, your clients don’t suffer from downtime.

In the "olden" days, upgrades were notorious for breaking clients. In other words, an upgrade to the server required an update to the client. In this day and age, hours or even minutes of downtime spent doing an upgrade can cost millions in lost revenue.

Some companies require that you present management with a plan to minimize downtime. In the past, you could get away with upgrading at 2:00 a.m. on a Sunday when load was at a minimum. But in today’s Internet-based e-commerce with international customers in other time zones, such strategies are not as effective.

SOAP-based services and CORBA-based services were incredibly brittle. It was hard to roll out a server that could support both old and new clients. With REST-based practices, it’s much easier. Especially using the Spring stack.

Supporting changes to the API

Imagine this design problem: You’ve rolled out a system with this Employee-based record. The system is a major hit. You’ve sold your system to countless enterprises. Suddenly, the need for an employee’s name to be split into firstName and lastName arises.

Uh oh. Didn’t think of that.

Before you open up the Employee class and replace the single field name with firstName and lastName, stop and think for a second. Will that break any clients? How long will it take to upgrade them. Do you even control all the clients accessing your services?

Downtime = lost money. Is management ready for that?

There is an old strategy that precedes REST by years.

Never delete a column in a database.
— Unknown

You can always add columns (fields) to a database table. But don’t take one away. The principle in RESTful services is the same.

Add new fields to your JSON representations, but don’t take any away. Like this:

JSON that supports multiple clients
{
  "id": 1,
  "firstName": "Bilbo",
  "lastName": "Baggins",
  "role": "burglar",
  "name": "Bilbo Baggins",
  "_links": {
    "self": {
      "href": "http://localhost:8080/employees/1"
    },
    "employees": {
      "href": "http://localhost:8080/employees"
    }
  }
}

Notice how this format shows firstName, lastName, AND name? While it sports duplication of information, the purpose is to support both old and new clients. That means you can upgrade the server without requiring clients upgrade at the same time. A good move that should reduce downtime.

And not only should you show this information in both the "old way" and the "new way", you should also process incoming data both ways.

How? Simple. Like this:

Employee record that handles both "old" and "new" clients
link:evolution/src/main/java/payroll/Employee.java[role=include]

This class is very similar to the previous version of Employee. Let’s go over the changes:

  • Field name has been replaced by firstName and lastName.

  • A "virtual" getter for the old name property, getName() is defined. It uses the firstName and lastName fields to produce a value.

  • A "virtual" setter for the old name property is also defined, setName(). It parses an incoming string and stores it into the proper fields.

Of course not EVERY change to your API is as simple as splitting a string or merging two strings. But it’s surely not impossible to come up with a set of transforms for most scenarios, right?

Important

Don’t forget to go and change how you preload your database (in LoadDatabase) to use this new constructor.

link:evolution/src/main/java/payroll/LoadDatabase.java[role=include]

Proper Responses

Another step in the right direction involves ensuring that each of your REST methods returns a proper response. Update the POST method newEmployee in the EmployeeController to look like this:

POST that handles "old" and "new" client requests
link:evolution/src/main/java/payroll/EmployeeController.java[role=include]

You will also need to add the imports:

Details
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.http.ResponseEntity;
  • The new Employee object is saved as before. But the resulting object is wrapped using the EmployeeModelAssembler.

  • Spring MVC’s ResponseEntity is used to create an HTTP 201 Created status message. This type of response typically includes a Location response header, and we use the URI derived from the model’s self-related link.

  • Additionally, return the model-based version of the saved object.

With these tweaks in place, you can use the same endpoint to create a new employee resource, and use the legacy name field:

$ curl -v -X POST localhost:8080/employees -H 'Content-Type:application/json' -d '{"name": "Samwise Gamgee", "role": "gardener"}' | json_pp

The output is shown below:

Details
> POST /employees HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Content-Length: 46
>
< Location: http://localhost:8080/employees/3
< Content-Type: application/hal+json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 10 Aug 20yy 19:44:43 GMT
<
{
  "id": 3,
  "firstName": "Samwise",
  "lastName": "Gamgee",
  "role": "gardener",
  "name": "Samwise Gamgee",
  "_links": {
    "self": {
      "href": "http://localhost:8080/employees/3"
    },
    "employees": {
      "href": "http://localhost:8080/employees"
    }
  }
}

This not only has the resulting object rendered in HAL (both name as well as firstName/lastName), but also the Location header populated with http://localhost:8080/employees/3. A hypermedia powered client could opt to "surf" to this new resource and proceed to interact with it.

The PUT controller method replaceEmployee in EmployeeController needs similar tweaks:

Handling a PUT for different clients
link:evolution/src/main/java/payroll/EmployeeController.java[role=include]

The Employee object built from the save() operation is then wrapped using the EmployeeModelAssembler into an EntityModel<Employee> object. Using the getRequiredLink() method, you can retrieve the Link created by the EmployeeModelAssembler with a SELF rel. This method returns a Link which must be turned into a URI with the toUri method.

Since we want a more detailed HTTP response code than 200 OK, we will use Spring MVC’s ResponseEntity wrapper. It has a handy static method created() where we can plug in the resource’s URI. It’s debatable if HTTP 201 Created carries the right semantics since we aren’t necessarily "creating" a new resource. But it comes pre-loaded with a Location response header, so run with it. Restart your application, run the following command and observe the results:

$ curl -v -X PUT localhost:8080/employees/3 -H 'Content-Type:application/json' -d '{"name": "Samwise Gamgee", "role": "ring bearer"}' | json_pp
Details
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> PUT /employees/3 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Content-Length: 49
>
< HTTP/1.1 201
< Location: http://localhost:8080/employees/3
< Content-Type: application/hal+json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 10 Aug 20yy 19:52:56 GMT
{
	"id": 3,
	"firstName": "Samwise",
	"lastName": "Gamgee",
	"role": "ring bearer",
	"name": "Samwise Gamgee",
	"_links": {
		"self": {
			"href": "http://localhost:8080/employees/3"
		},
		"employees": {
			"href": "http://localhost:8080/employees"
		}
	}
}

That employee resource has now been updated and the location URI sent back. Finally, update the DELETE operation deleteEmployee in EmployeeController suitably:

Handling DELETE requests
link:evolution/src/main/java/payroll/EmployeeController.java[role=include]

This returns an HTTP 204 No Content response. Restart your application, run the following command and observe the results:

$ curl -v -X DELETE localhost:8080/employees/1
Details
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> DELETE /employees/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 204
< Date: Fri, 10 Aug 20yy 21:30:26 GMT
Important
Making changes to the fields in the Employee class will require coordination with your database team, so that they can properly migrate existing content into the new columns.

You are now ready for an upgrade that will NOT disturb existing clients while newer clients can take advantage of the enhancements!

By the way, are you worried about sending too much information over the wire? In some systems where every byte counts, evolution of APIs may need to take a backseat. But don’t pursue such premature optimization until you measure.

Note
If you are following along in the solution repository, the next section switches to the links module.

So far, you’ve built an evolvable API with bare bones links. To grow your API and better serve your clients, you need to embrace the concept of Hypermedia as the Engine of Application State.

What does that mean? In this section, you’ll explore it in detail.

Business logic inevitably builds up rules that involve processes. The risk of such systems is we often carry such server-side logic into clients and build up strong coupling. REST is about breaking down such connections and minimizing such coupling.

To show how to cope with state changes without triggering breaking changes in clients, imagine adding a system that fulfills orders.

As a first step, define a new Order record:

links/src/main/java/payroll/Order.java
link:links/src/main/java/payroll/Order.java[role=include]
  • The class requires a JPA @Table annotation changing the table’s name to CUSTOMER_ORDER because ORDER is not a valid name for table.

  • It includes a description field as well as a status field.

Orders must go through a certain series of state transitions from the time a customer submits an order and it is either fulfilled or cancelled. This can be captured as a Java enum called Status:

links/src/main/java/payroll/Status.java
link:links/src/main/java/payroll/Status.java[role=include]

This enum captures the various states an Order can occupy. For this tutorial, let’s keep it simple.

To support interacting with orders in the database, you must define a corresponding Spring Data repository called OrderRepository:

Spring Data JPA’s JpaRepository base interface
interface OrderRepository extends JpaRepository<Order, Long> {
}

We also need to create a new exception class OrderNotFoundException:

Details
link:links/src/main/java/payroll/OrderNotFoundException.java[role=include]

With this in place, you can now define a basic OrderController with the required imports:

Import Statements
import java.util.List;
import java.util.stream.Collectors;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;

import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
links/src/main/java/payroll/OrderController.java
link:links/src/main/java/payroll/OrderController.java[role=include]
}
  • It contains the same REST controller setup as the controllers you’ve built so far.

  • It injects both an OrderRepository as well as a (not yet built) OrderModelAssembler.

  • The first two Spring MVC routes handle the aggregate root as well as a single item Order resource request.

  • The third Spring MVC route handles creating new orders, by starting them in the IN_PROGRESS state.

  • All the controller methods return one of Spring HATEOAS’s RepresentationModel subclasses to properly render hypermedia (or a wrapper around such a type).

Before building the OrderModelAssembler, let’s discuss what needs to happen. You are modeling the flow of states between Status.IN_PROGRESS, Status.COMPLETED, and Status.CANCELLED. A natural thing when serving up such data to clients is to let the clients make the decision on what it can do based on this payload.

But that would be wrong.

What happens when you introduce a new state in this flow? The placement of various buttons on the UI would probably be erroneous.

What if you changed the name of each state, perhaps while coding international support and showing locale-specific text for each state? That would most likely break all the clients.

Enter HATEOAS or Hypermedia as the Engine of Application State. Instead of clients parsing the payload, give them links to signal valid actions. Decouple state-based actions from the payload of data. In other words, when CANCEL and COMPLETE are valid actions, dynamically add them to the list of links. Clients only need show users the corresponding buttons when the links exist.

This decouples clients from having to know WHEN such actions are valid, reducing the risk of the server and its clients getting out of sync on the logic of state transitions.

Having already embraced the concept of Spring HATEOAS RepresentationModelAssembler components, putting such logic in the OrderModelAssembler would be the perfect place to capture this business rule:

links/src/main/java/payroll/OrderModelAssembler.java
link:links/src/main/java/payroll/OrderModelAssembler.java[role=include]

This resource assembler always includes the self link to the single-item resource as well as a link back to the aggregate root. But it also includes two conditional links to OrderController.cancel(id) as well as OrderController.complete(id) (not yet defined). These links are ONLY shown when the order’s status is Status.IN_PROGRESS.

If clients can adopt HAL and the ability to read links instead of simply reading the data of plain old JSON, they can trade in the need for domain knowledge about the order system. This naturally reduces coupling between client and server. And it opens the door to tuning the flow of order fulfillment without breaking clients in the process.

To round out order fulfillment, add the following to the OrderController for the cancel operation:

Creating a "cancel" operation in the OrderController
link:links/src/main/java/payroll/OrderController.java[role=include]

It checks the Order status before allowing it to be cancelled. If it’s not a valid state, it returns an RFC-7807 Problem, a hypermedia-supporting error container. If the transition is indeed valid, it transitions the Order to CANCELLED.

And add this to the OrderController as well for order completion:

Creating a "complete" operation in the OrderController
link:links/src/main/java/payroll/OrderController.java[role=include]

This implements similar logic to prevent an Order status from being completed unless in the proper state.

Let’s update LoadDatabase to pre-load some Order​s along with the Employee​s it was loading before.

Updating the database pre-loader
link:links/src/main/java/payroll/LoadDatabase.java[role=include]

Now you can test things out! Restart your application to make sure you are running the latest code changes. To use the newly minted order service, just perform a few operations:

$ curl -v http://localhost:8080/orders | json_pp
Details
{
  "_embedded": {
    "orderList": [
      {
        "id": 3,
        "description": "MacBook Pro",
        "status": "COMPLETED",
        "_links": {
          "self": {
            "href": "http://localhost:8080/orders/3"
          },
          "orders": {
            "href": "http://localhost:8080/orders"
          }
        }
      },
      {
        "id": 4,
        "description": "iPhone",
        "status": "IN_PROGRESS",
        "_links": {
          "self": {
            "href": "http://localhost:8080/orders/4"
          },
          "orders": {
            "href": "http://localhost:8080/orders"
          },
          "cancel": {
            "href": "http://localhost:8080/orders/4/cancel"
          },
          "complete": {
            "href": "http://localhost:8080/orders/4/complete"
          }
        }
      }
    ]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/orders"
    }
  }
}

This HAL document immediately shows different links for each order, based upon its present state.

  • The first order, being COMPLETED only has the navigational links. The state transition links are not shown.

  • The second order, being IN_PROGRESS additionally has the cancel link as well as the complete link.

Try cancelling an order:

$ curl -v -X DELETE http://localhost:8080/orders/4/cancel | json_pp
Note
You may need to replace the number 4 in the URL above based on the specific IDs in your database. That information can be found from the previous /orders call.
Details
> DELETE /orders/4/cancel HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: application/hal+json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Mon, 27 Aug 20yy 15:02:10 GMT
<
{
  "id": 4,
  "description": "iPhone",
  "status": "CANCELLED",
  "_links": {
    "self": {
      "href": "http://localhost:8080/orders/4"
    },
    "orders": {
      "href": "http://localhost:8080/orders"
    }
  }
}

This response shows an HTTP 200 status code indicating it was successful. The response HAL document shows that order in its new state (CANCELLED). And the state-altering links gone.

If you try the same operation again…​

$ curl -v -X DELETE http://localhost:8080/orders/4/cancel | json_pp
Details
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> DELETE /orders/4/cancel HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 405
< Content-Type: application/problem+json
< Transfer-Encoding: chunked
< Date: Mon, 27 Aug 20yy 15:03:24 GMT
<
{
  "title": "Method not allowed",
  "detail": "You can't cancel an order that is in the CANCELLED status"
}

…​you see an HTTP 405 Method Not Allowed response. DELETE has become an invalid operation. The Problem response object clearly indicates that you aren’t allowed to "cancel" an order already in the "CANCELLED" status.

Additionally, trying to complete the same order also fails:

$ curl -v -X PUT localhost:8080/orders/4/complete | json_pp
Details
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> PUT /orders/4/complete HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 405
< Content-Type: application/problem+json
< Transfer-Encoding: chunked
< Date: Mon, 27 Aug 20yy 15:05:40 GMT
<
{
  "title": "Method not allowed",
  "detail": "You can't complete an order that is in the CANCELLED status"
}

With all this in place, your order fulfillment service is capable of conditionally showing what operations are available. It also guards against invalid operations.

By leveraging the protocol of hypermedia and links, clients can be built sturdier and less likely to break simply because of a change in the data. And Spring HATEOAS eases building the hypermedia you need to serve to your clients.

Summary

Throughout this tutorial, you have engaged in various tactics to build REST APIs. As it turns out, REST isn’t just about pretty URIs and returning JSON instead of XML.

Instead, the following tactics help make your services less likely to break existing clients you may or may not control:

  • Don’t remove old fields. Instead, support them.

  • Use rel-based links so clients don’t have to hard code URIs.

  • Retain old links as long as possible. Even if you have to change the URI, keep the rels so older clients have a path onto the newer features.

  • Use links, not payload data, to instruct clients when various state-driving operations are available.

It may appear to be a bit of effort to build up RepresentationModelAssembler implementations for each resource type and to use these components in all of your controllers. But this extra bit of server-side setup (made easy thanks to Spring HATEOAS) can ensure the clients you control (and more importantly, those you don’t) can upgrade with ease as you evolve your API.

This concludes our tutorial on how to build RESTful services using Spring. Each section of this tutorial is managed as a separate subproject in a single github repo:

  • nonrest — Simple Spring MVC app with no hypermedia

  • rest — Spring MVC + Spring HATEOAS app with HAL representations of each resource

  • evolution — REST app where a field is evolved but old data is retained for backward compatibility

  • links — REST app where conditional links are used to signal valid state changes to clients

To view more examples of using Spring HATEOAS see https://github.com/spring-projects/spring-hateoas-examples.

To do some more exploring check out the following video by Spring teammate Oliver Drotbohm:

tut-rest's People

Contributors

artisancreek avatar caype avatar christian-schlichtherle avatar christos-p avatar gregturn avatar igneus avatar jayy-lmao avatar joshlong avatar miguelpayet avatar mlvandijk avatar ndziepatrickjoel avatar nhojpatrick avatar oleksiiklochko avatar robertmcnees avatar shayromayo avatar snicoll avatar srchip15 avatar thrasymache avatar ugmadevelopment avatar zaerald avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tut-rest's Issues

OPTIONS request from Angular returns HTTP 401 Unauthorized status

When I invoke POST request to http://localhost:8080/oauth/token, Angular frontend application is sending preflight OPTIONS request to http://localhost:8080/oauth/token and Authorization header is not sent and I get HTTP 401 Unauthorized status.

I have found many similar issues, but all solutions are referring to use of WebSecurityConfigurerAdapter, but your sample application is using AuthorizationServerConfigurerAdapter and GlobalAuthenticationConfigurerAdapter.

Could you add solution to your sample app so OPTIONS request is not required to send Authorization header to /oauth/token, in order to be compatible to Angular applications?

Running security version doesn't grant me access

When I spin up the security version of Application inside my IDE, I then try to curl request a token. I get a "Bad Credentials" response:

$ curl -X POST -vu android-bookmarks:123456 http://localhost:8080/oauth/token -H "Accept: application/json" -d "password=password&username=jlong&grant_type=password&scope=write&client_secret=123456&client_id=android-bookmarks"
* Adding handle: conn: 0x7f8568803a00
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7f8568803a00) send_pipe: 1, recv_pipe: 0
* About to connect() to localhost port 8080 (#0)
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
* Server auth using Basic with user 'android-bookmarks'
> POST /oauth/token HTTP/1.1
> Authorization: Basic YW5kcm9pZC1ib29rbWFya3M6MTIzNDU2
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: application/json
> Content-Length: 113
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 113 out of 113 bytes
< HTTP/1.1 400 Bad Request
* Server Apache-Coyote/1.1 is not blacklisted
< Server: Apache-Coyote/1.1
< Access-Control-Allow-Origin: http://localhost:9000
< Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
< Access-Control-Max-Age: 3600
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Headers: Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Cache-Control: no-store
< Pragma: no-cache
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Thu, 23 Oct 2014 21:30:19 GMT
< Connection: close
< 
* Closing connection 0
{"error":"invalid_grant","error_description":"Bad credentials"}

This is perplexing given that the init method clearly sets up entries:

    @Bean
    CommandLineRunner init(AccountRepository accountRepository, BookmarkRepository bookmarkRepository) {
        return (evt) ->
                Arrays.asList("jhoeller,dsyer,pwebb,ogierke,rwinch,mfisher,mpollack,jlong".split(",")).forEach(a -> {
                    Account account = accountRepository.save(new Account(a, "password"));
                    bookmarkRepository.save(new Bookmark(account, "http://bookmark.com/1/" + a, "A description"));
                    bookmarkRepository.save(new Bookmark(account, "http://bookmark.com/2/" + a, "A description"));
                });
    }

Thoughts?

Note, I also saw this from the server side logs:

2014-10-23 16:30:19.792  INFO 5839 --- [nio-8080-exec-1] o.s.s.o.provider.endpoint.TokenEndpoint  : Handling error: InvalidGrantException, Bad credentials

[run failure] Rest Directory

mvn spring-boot:run

Short:

InvocationTargetException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException: javax.xml.bind.JAXBException -> [Help 1]

All:


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.5.RELEASE)

2018-11-29 11:39:10.542  INFO 8702 --- [           main] payroll.PayrollApplication               : Starting PayrollApplication on debian with PID 8702 (/home/alex/Desktop/tut-rest/rest/target/classes started by alex in /home/alex/Desktop/tut-rest/rest)
2018-11-29 11:39:10.555  INFO 8702 --- [           main] payroll.PayrollApplication               : No active profile set, falling back to default profiles: default
2018-11-29 11:39:10.755  INFO 8702 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@3802b01d: startup date [Thu Nov 29 11:39:10 MSK 2018]; root of context hierarchy
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.cglib.core.ReflectUtils$1 (file:/home/alex/.m2/repository/org/springframework/spring-core/5.0.9.RELEASE/spring-core-5.0.9.RELEASE.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of org.springframework.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
2018-11-29 11:39:14.771  INFO 8702 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$2b149e74] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-11-29 11:39:14.898  INFO 8702 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.hateoas.config.HateoasConfiguration' of type [org.springframework.hateoas.config.HateoasConfiguration$$EnhancerBySpringCGLIB$$aa94eba6] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-11-29 11:39:15.961  INFO 8702 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2018-11-29 11:39:16.051  INFO 8702 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-11-29 11:39:16.051  INFO 8702 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.34
2018-11-29 11:39:16.077  INFO 8702 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/usr/java/packages/lib:/usr/lib/x86_64-linux-gnu/jni:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/usr/lib/jni:/lib:/usr/lib]
2018-11-29 11:39:16.324  INFO 8702 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-11-29 11:39:16.324  INFO 8702 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 5569 ms
2018-11-29 11:39:16.492  INFO 8702 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]
2018-11-29 11:39:16.506  INFO 8702 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-11-29 11:39:16.506  INFO 8702 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-11-29 11:39:16.507  INFO 8702 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-11-29 11:39:16.507  INFO 8702 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-11-29 11:39:16.888  INFO 8702 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2018-11-29 11:39:17.497  INFO 8702 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2018-11-29 11:39:17.661  INFO 8702 --- [           main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2018-11-29 11:39:17.695  INFO 8702 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
	name: default
	...]
2018-11-29 11:39:17.866  INFO 8702 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {5.2.17.Final}
2018-11-29 11:39:17.868  INFO 8702 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2018-11-29 11:39:17.902  WARN 8702 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
2018-11-29 11:39:17.911  INFO 8702 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2018-11-29 11:39:17.922  INFO 8702 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
2018-11-29 11:39:17.926  INFO 8702 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2018-11-29 11:39:18.037  INFO 8702 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2018-11-29 11:39:18.048 ERROR 8702 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1699) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:573) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1089) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:859) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:780) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:412) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:333) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1277) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1265) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
	at payroll.PayrollApplication.main(PayrollApplication.java:10) ~[classes/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
	at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:497) ~[na:na]
	at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Caused by: java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
	at org.hibernate.boot.spi.XmlMappingBinderAccess.<init>(XmlMappingBinderAccess.java:43) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
	at org.hibernate.boot.MetadataSources.<init>(MetadataSources.java:87) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:209) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:164) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
	at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:51) ~[spring-orm-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:390) ~[spring-orm-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:377) ~[spring-orm-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1758) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1695) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
	... 22 common frames omitted
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException
	at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471) ~[na:na]
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588) ~[na:na]
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
	... 33 common frames omitted

[WARNING] 
java.lang.reflect.InvocationTargetException
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:566)
    at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run (AbstractRunMojo.java:497)
    at java.lang.Thread.run (Thread.java:834)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean (AbstractAutowireCapableBeanFactory.java:1699)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean (AbstractAutowireCapableBeanFactory.java:573)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean (AbstractAutowireCapableBeanFactory.java:495)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0 (AbstractBeanFactory.java:317)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton (DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean (AbstractBeanFactory.java:315)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean (AbstractBeanFactory.java:199)
    at org.springframework.context.support.AbstractApplicationContext.getBean (AbstractApplicationContext.java:1089)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization (AbstractApplicationContext.java:859)
    at org.springframework.context.support.AbstractApplicationContext.refresh (AbstractApplicationContext.java:550)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh (ServletWebServerApplicationContext.java:140)
    at org.springframework.boot.SpringApplication.refresh (SpringApplication.java:780)
    at org.springframework.boot.SpringApplication.refreshContext (SpringApplication.java:412)
    at org.springframework.boot.SpringApplication.run (SpringApplication.java:333)
    at org.springframework.boot.SpringApplication.run (SpringApplication.java:1277)
    at org.springframework.boot.SpringApplication.run (SpringApplication.java:1265)
    at payroll.PayrollApplication.main (PayrollApplication.java:10)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:566)
    at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run (AbstractRunMojo.java:497)
    at java.lang.Thread.run (Thread.java:834)
Caused by: java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
    at org.hibernate.boot.spi.XmlMappingBinderAccess.<init> (XmlMappingBinderAccess.java:43)
    at org.hibernate.boot.MetadataSources.<init> (MetadataSources.java:87)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init> (EntityManagerFactoryBuilderImpl.java:209)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init> (EntityManagerFactoryBuilderImpl.java:164)
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory (SpringHibernateJpaPersistenceProvider.java:51)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory (LocalContainerEntityManagerFactoryBean.java:365)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory (AbstractEntityManagerFactoryBean.java:390)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet (AbstractEntityManagerFactoryBean.java:377)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet (LocalContainerEntityManagerFactoryBean.java:341)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods (AbstractAutowireCapableBeanFactory.java:1758)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean (AbstractAutowireCapableBeanFactory.java:1695)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean (AbstractAutowireCapableBeanFactory.java:573)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean (AbstractAutowireCapableBeanFactory.java:495)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0 (AbstractBeanFactory.java:317)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton (DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean (AbstractBeanFactory.java:315)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean (AbstractBeanFactory.java:199)
    at org.springframework.context.support.AbstractApplicationContext.getBean (AbstractApplicationContext.java:1089)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization (AbstractApplicationContext.java:859)
    at org.springframework.context.support.AbstractApplicationContext.refresh (AbstractApplicationContext.java:550)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh (ServletWebServerApplicationContext.java:140)
    at org.springframework.boot.SpringApplication.refresh (SpringApplication.java:780)
    at org.springframework.boot.SpringApplication.refreshContext (SpringApplication.java:412)
    at org.springframework.boot.SpringApplication.run (SpringApplication.java:333)
    at org.springframework.boot.SpringApplication.run (SpringApplication.java:1277)
    at org.springframework.boot.SpringApplication.run (SpringApplication.java:1265)
    at payroll.PayrollApplication.main (PayrollApplication.java:10)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:566)
    at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run (AbstractRunMojo.java:497)
    at java.lang.Thread.run (Thread.java:834)
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException
    at java.net.URLClassLoader.findClass (URLClassLoader.java:471)
    at java.lang.ClassLoader.loadClass (ClassLoader.java:588)
    at java.lang.ClassLoader.loadClass (ClassLoader.java:521)
    at org.hibernate.boot.spi.XmlMappingBinderAccess.<init> (XmlMappingBinderAccess.java:43)
    at org.hibernate.boot.MetadataSources.<init> (MetadataSources.java:87)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init> (EntityManagerFactoryBuilderImpl.java:209)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init> (EntityManagerFactoryBuilderImpl.java:164)
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory (SpringHibernateJpaPersistenceProvider.java:51)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory (LocalContainerEntityManagerFactoryBean.java:365)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory (AbstractEntityManagerFactoryBean.java:390)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet (AbstractEntityManagerFactoryBean.java:377)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet (LocalContainerEntityManagerFactoryBean.java:341)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods (AbstractAutowireCapableBeanFactory.java:1758)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean (AbstractAutowireCapableBeanFactory.java:1695)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean (AbstractAutowireCapableBeanFactory.java:573)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean (AbstractAutowireCapableBeanFactory.java:495)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0 (AbstractBeanFactory.java:317)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton (DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean (AbstractBeanFactory.java:315)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean (AbstractBeanFactory.java:199)
    at org.springframework.context.support.AbstractApplicationContext.getBean (AbstractApplicationContext.java:1089)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization (AbstractApplicationContext.java:859)
    at org.springframework.context.support.AbstractApplicationContext.refresh (AbstractApplicationContext.java:550)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh (ServletWebServerApplicationContext.java:140)
    at org.springframework.boot.SpringApplication.refresh (SpringApplication.java:780)
    at org.springframework.boot.SpringApplication.refreshContext (SpringApplication.java:412)
    at org.springframework.boot.SpringApplication.run (SpringApplication.java:333)
    at org.springframework.boot.SpringApplication.run (SpringApplication.java:1277)
    at org.springframework.boot.SpringApplication.run (SpringApplication.java:1265)
    at payroll.PayrollApplication.main (PayrollApplication.java:10)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:566)
    at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run (AbstractRunMojo.java:497)
    at java.lang.Thread.run (Thread.java:834)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  48.224 s
[INFO] Finished at: 2018-11-29T11:39:18+03:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.0.5.RELEASE:run (default-cli) on project rest: An exception occurred while running. null: InvocationTargetException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException: javax.xml.bind.JAXBException -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException

No Prerequisites Description

This tutorial begins with the Spring Initializr as if the user is going to be starting from scratch, and then jumps right into model/src/main/java/bookmarks/Account.java which is a folder that does not yet exist.

There are no descriptions of what someone following the tutorial must do to catch up to this point. Even if a user simply copies and pastes the code provided they run into errors with things such as @RestController not being resolved to a type.

This tutorial starts from the beginning and then jumps ahead to somewhere. It should include a disclaimer about the prerequisites for the course, or a full description of what a student needs to do in order to follow the tutorial.

Authority should be ROLE_USER not USER

In file https://github.com/spring-guides/tut-bookmarks/blob/master/security/src/main/java/bookmarks/Application.java#L143, AuthorityUtils.createAuthorityList("USER", "write") should be AuthorityUtils.createAuthorityList("ROLE_USER", "write") as Spring Security uses role ROLE_USER to check. This causes basic authentication to fail with 403 error.

Actually in the OAuth2 configuration (https://github.com/spring-guides/tut-bookmarks/blob/master/security/src/main/java/bookmarks/Application.java#L180), it's using the correct ROLE_USER.

Employee class needs default constructor

Employee class needs default constructor for JPA. Otherwise you will get an org.hibernate.InstantiationException: No default constructor for entity exception at the step to curl -v localhost:8080/employees.

findOne in interface cannot be applied to given types

Hey there, loving the tutorial - thanks for putting it together!

When I ran the first maven install I got an error regarding this line in BookmarkRestController.java:

return this.bookmarkRepository.findOne(bookmarkId);

Here's the error:

[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] /Users/is5960/Code/tutorials/demo/src/main/java/com/example/demo/BookmarkRestController.java:[55,39] method findOne in interface org.springframework.data.repository.query.QueryByExampleExecutor<T> cannot be applied to given types;
  required: org.springframework.data.domain.Example<S>
  found: java.lang.Long
  reason: cannot infer type-variable(s) S
    (argument mismatch; java.lang.Long cannot be converted to org.springframework.data.domain.Example<S>)
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.538 s
[INFO] Finished at: 2018-04-20T14:51:10-05:00
[INFO] Final Memory: 32M/383M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:compile (default-compile) on project demo: Compilation failure
[ERROR] /Users/is5960/Code/tutorials/demo/src/main/java/com/example/demo/BookmarkRestController.java:[55,39] method findOne in interface org.springframework.data.repository.query.QueryByExampleExecutor<T> cannot be applied to given types;
[ERROR]   required: org.springframework.data.domain.Example<S>
[ERROR]   found: java.lang.Long
[ERROR]   reason: cannot infer type-variable(s) S
[ERROR]     (argument mismatch; java.lang.Long cannot be converted to org.springframework.data.domain.Example<S>)
[ERROR] 
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

After digging in a little, I found this Stack Overflow question that implies maybe the tutorial should use getOne or findOneByEmail. When I change the line of code I can compile fine:

return this.bookmarkRepository.getOne(bookmarkId);

I'm a total Java and Spring newbie, so maybe I'm totally off-the-mark. Just thought I'd share this issue in case someone else runs into it.

Tutorial needs instructions for launching from command line

I don't actually know how to build or execute this application (using maven from the command line). In my derived example I'm using gradle bootRun, but the mvn spring-boot:run fails:

$ mvn spring-boot:run
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO] 
[INFO] bookmarks
[INFO] model
[INFO] rest
[INFO] security
[INFO] hateoas
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building bookmarks 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> spring-boot-maven-plugin:1.3.5.RELEASE:run (default-cli) > test-compile @ bookmarks >>>
[INFO] 
[INFO] <<< spring-boot-maven-plugin:1.3.5.RELEASE:run (default-cli) < test-compile @ bookmarks <<<
[INFO] 
[INFO] --- spring-boot-maven-plugin:1.3.5.RELEASE:run (default-cli) @ bookmarks ---
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] bookmarks .......................................... FAILURE [  0.732 s]
[INFO] model .............................................. SKIPPED
[INFO] rest ............................................... SKIPPED
[INFO] security ........................................... SKIPPED
[INFO] hateoas ............................................ SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.247 s
[INFO] Finished at: 2016-10-25T13:06:06-07:00
[INFO] Final Memory: 18M/309M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:1.3.5.RELEASE:run (default-cli) on project bookmarks: Unable to find a suitable main class, please add a 'mainClass' property -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException

Missing word POST

"Methods that don’t specify a path just inherit the path mapped at the type level. The add method responds to the URI specified at the type level, but it only responds to HTTP requests with the verb"

->

Methods that don’t specify a path just inherit the path mapped at the type level. The add method responds to the URI specified at the type level, but it only responds to HTTP requests with the verb POST.

Security sample project: Fix curl examples in Application

In class security/src/main/java/bookmarks/Application.java

At the top of it, we can see the following curl examples:
// curl -X POST -vu android-bookmarks:123456 http://localhost:8080/oauth/token -H "Accept: application/json" -d "password=password&username=jlong&grant_type=password&scope=write&client_secret=123456&client_id=android-bookmarks" // curl -v POST http://127.0.0.1:8080/bookmarks -H "Authorization: Bearer <oauth_token>""

Both are wrong.

To begin with, for a self-signed certificate like the one generated in the tutorial, you need to run curl with the --insecure optional parameter.
The second command is also wrong. Its trying to post but instead of -X is using -v, and there is no actual payload. Double quotes at the end are also wrong.

Working examples (Edit the token in the second and third example):

LOGIN
curl --insecure -X POST -vu android-bookmarks:123456 http://localhost:8080/oauth/token -H "Accept: application/json" -d "password=password&username=jlong&grant_type=password&scope=write&client_secret=123456&client_id=android-bookmarks"

POSTING A NEW BOOKMARK
curl --insecure -X POST -vu android-bookmarks:123456 -H "Authorization: Bearer <token returned in login response>" -H "Accept: application/json" -H "Content-Type: application/json" -d '{"uri": "https://spring.io", "description": "New bookmark!"}' https://127.0.0.1:8443/jlong/bookmarks

RETRIEVING ALL BOOKMARKS
curl -v --insecure -H "Accept: application/json" -H "Authorization: Bearer <token returned in login response>" -X GET https://127.0.0.1:8443/jlong/bookmarks/

HATEOAS class BookmarkRestController @RequestMapping(method = RequestMethod.POST)

Getting the error: Cannot infer type argument(s) for map(Function<? super T,? extends U>
For return accountRepository.findByUsername(userId)
.map(account -> {
Bookmark bookmark = bookmarkRepository
.save(new Bookmark(account, input.uri, input.description));

            Link forOneBookmark = new BookmarkResource(bookmark).getLink("self");

            return ResponseEntity.created(URI.create(forOneBookmark.getHref())).build();
        })

Will continue debugging.

BookmarkControllerAdvice no imports

The tutorial does not contain the imports for this class.

Is there some norm I don't know where I have to copy and paste from the source code instead of the tutorial copy?

Getting `javax.xml.bind.MarshalException` in "What makes something RESTful?" part of tutorial

I've replaced the @GetMapping("/employees/{id}") and @GetMapping("/employees") methods as specified under the "What makes something RESTful?" heading.

My application was working before I made these changes.

When I go to http://localhost:8080/employees, I get the following:

Whitelabel Error PageThis application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri Oct 19 18:23:16 CEST 2018There was an unexpected error (type=Internal Server Error, status=500).
Could not marshal [Resources { content: [Resource { content: Employee(id=1, name=Bilbo Baggins, role=burglar), links: [<http://localhost:8080/employees/1>;rel="self", <http://localhost:8080/employees>;rel="employees"] }, 
Resource { content: Employee(id=2, name=Frodo Baggins, role=thief), links: [<http://localhost:8080/employees/2>;rel="self", <http://localhost:8080/employees>;rel="employees"] }], links: [<http://localhost:8080/employees>;rel="self"] }]: null; 
nested exception is javax.xml.bind.MarshalException - with linked exception: [com.sun.istack.internal.SAXException2: unable to marshal type "org.springframework.hateoas.Resource" as an element because it is not known to this context.]

When I go to http://localhost:8080/employees/1, I get the following:

Whitelabel Error PageThis application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri Oct 19 18:33:45 CEST 2018There was an unexpected error (type=Internal Server Error, status=500).
Could not marshal [Resource { content: Employee(id=1, name=Bilbo Baggins, role=burglar), links: [<http://localhost:8080/employees/1>;rel="self", <http://localhost:8080/employees>;rel="employees"] }]: null; 
nested exception is javax.xml.bind.MarshalException - with linked exception: [com.sun.istack.internal.SAXException2: class nl.marit.springdemo.payroll.Employee nor any of its super class is known to this context. javax.xml.bind.JAXBException: class xxxxx.payroll.Employee nor any of its super class is known to this context.]

Sidenote: My IDE (IntelliJ) also wants to add imports for linkTo and methodOn, while I cannot find imports for these in rest/src/main/java/payroll/EmployeeController.java.

Link to my project.

Unable to marshal type "bookmarks.BookmarkResource"

When running the HATEOAS project as is and browsing to http://localhost:8080/jhoeller/bookmarks I get the following error:

There was an unexpected error (type=Internal Server Error, status=500).
Could not marshal [Resources { content: [links: [<http://bookmark.com/1/jhoeller>;rel="bookmark-uri", <http://localhost:8080/jhoeller/bookmarks>;rel="bookmarks", <http://localhost:8080/jhoeller/bookmarks/1>;rel="self"], links: [<http://bookmark.com/2/jhoeller>;rel="bookmark-uri", <http://localhost:8080/jhoeller/bookmarks>;rel="bookmarks", <http://localhost:8080/jhoeller/bookmarks/2>;rel="self"]], links: [] }]: null; 
nested exception is javax.xml.bind.MarshalException - with linked exception: [com.sun.istack.internal.SAXException2: 
unable to marshal type "bookmarks.BookmarkResource" as an element because it is missing an @XmlRootElement annotation]

Is there something missing here or is it just me?

Details for bookmark nested under 'bookmark' for HATEOAS

Hello,

I have been following the HATEOAS section in an attempt to familurise myself with how I can extend an existing interface with HATEOAS links.

The issue I find with the tutorial is that when I have followed it I get the following output from a single bookmark:

{
    "bookmark": {
        "id": 5,
        "uri": "http://bookmark.com/1/dsyer",
        "description": "A description"
    },
    "links": [
        {
            "rel": "bookmark-uri",
            "href": "http://bookmark.com/1/dsyer",
            "hreflang": null,
            "media": null,
            "title": null,
            "type": null,
            "deprecation": null
        },
        {
            "rel": "bookmarks",
            "href": "http://localhost:8080/dsyer/bookmarks",
            "hreflang": null,
            "media": null,
            "title": null,
            "type": null,
            "deprecation": null
        },
        {
            "rel": "self",
            "href": "http://localhost:8080/dsyer/bookmarks/5",
            "hreflang": null,
            "media": null,
            "title": null,
            "type": null,
            "deprecation": null
        }
    ]
}

What I find surprising is that the bookmark details are nested underneath the heading 'bookmark'.
Taking a look at other tutorials, they seem to suggest that the result should not be nested:
https://docs.spring.io/spring-hateoas/docs/current/reference/html/#fundamentals.resources
http://www.baeldung.com/spring-hateoas-tutorial#link

So, I would expect the result to be instead:

{
    "id": 5,
    "uri": "http://bookmark.com/1/dsyer",
    "description": "A description",
    "links": [
        {
            "rel": "bookmark-uri",
            "href": "http://bookmark.com/1/dsyer",
            "hreflang": null,
            "media": null,
            "title": null,
            "type": null,
            "deprecation": null
        },
        {
            "rel": "bookmarks",
            "href": "http://localhost:8080/dsyer/bookmarks",
            "hreflang": null,
            "media": null,
            "title": null,
            "type": null,
            "deprecation": null
        },
        {
            "rel": "self",
            "href": "http://localhost:8080/dsyer/bookmarks/5",
            "hreflang": null,
            "media": null,
            "title": null,
            "type": null,
            "deprecation": null
        }
    ]
}

If it is in fact supposed to be just an enrichment with the HATEOAS links then the second way makes a lot of sense. We already know we are in the bookmark context here so don't need to have a nested object.

Username and Password

Can someone help me with the user name and password.

I have used the keytool for creating the keystore password.

Security:curl work ,$.ajax got 401

when i use curl to get access_token
curl -X POST -vu android-bookmarks:123456 http://localhost:8080/oauth/token -H "Accept: application/json" -d "password=password&username=jlong&grant_type=password&scope=write&client_secret=123456&client_id=android-bookmarks"
it returns like
{"access_token":"8ac3b71e-9196-4f01-b128-4fe2cf99a1c6","token_type":"bearer","refresh_token":"77a8657a-02fb-4e4a-a8bc-b2af5550540d","expires_in":43199,"scope":"write"}* Connection #0 to host localhost left intact
then i use the token
curl -v http://localhost:8080/bookmarks -H "Authorization: Bearer 8ac3b71e-9196-4f01-b128-4fe2cf99a1c6"
it returns correct result curl -v http://localhost:8080/bookmarks -H "Authorization: Bearer 8ac3b71e-9196-4f01-b128-4fe2cf99a1c6"
but i use ajax in my host "localhost"
$.ajax({ url: 'http://localhost:8080/bookmarks', beforeSend: function (xhr) { xhr.setRequestHeader('Authorization', 'BEARER 8ac3b71e-9196-4f01-b128-4fe2cf99a1c6'); }, success: function (response) { console.log(response); } });
i use chrome console to run this ,it logs
XMLHttpRequest cannot load http://localhost:8080/bookmarks. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access. The response had HTTP status code 401.
i run the app append args :"--tagit.origin=*" to make Access-Control-Allow-Origin is always * ,why it still get that error?

Question: @RestController with ResponseEntity

Hi,

I was wondering if it's the best practice to use @RestController with ResponseEntity when we need more flexibility (for example to return more than one HTTP status code)?

The concrete example in the code: https://github.com/spring-guides/tut-bookmarks/blob/a5b4d43d63d3c7d8a6107873841526f92d7bad2d/rest/src/main/java/bookmarks/BookmarkRestController.java#L56

Basically, I can see that in this project @RestController is used everywhere, but also I can see that it is used with ResponseEntity (see example above).

Is it ok to do so? I am asking just to confirm my response from here: http://stackoverflow.com/questions/26549379/when-use-responseentityt-and-restcontroller-for-spring-restful-applications/40454751#40454751

Thank you very much!

json method

I am trying to follow this example in a project. Where is the json method coming from? The same packages are imported the best I can tell.

    String bookmarkJson = json(new Bookmark(
            this.account, "http://spring.io", "a bookmark to the best resource for Spring news and information"));

Several issues regarding README.adoc

  • Why does the guide use RequestMapping instead of the simpler GetMapping, PostMapping, and so on?

  • Why is the L in @PathVariable Long bookmarkId an uppercase letter?

  • The first HttpMessageConverter is missing an s at the end. The one that does have an s at the end has an extra space before the s that should be removed.

  • Some of the source code is indented with 8 spaces (way too much for presenting it in a narrow column), some is indented with 4 spaces. To save even more space, it should only be indented with 2 spaces, or at least consistently with 4 spaces.

  • The text in the request message should probably be in the response message. The current text doesn't make sense to me.

Fix the method for https://github.com/spring-projects/spring-boot/issues/1801

I looked up issue: spring-projects/spring-boot#1801. It seems to be fixed. The following code snippet still exists:

@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {
        // Workaround for https://github.com/spring-projects/spring-boot/issues/1801
        endpoints.authenticationManager(new AuthenticationManager() {
            @Override
            public Authentication authenticate(Authentication authentication)
                    throws AuthenticationException {
                return authenticationManager.getOrBuild().authenticate(authentication);
            }
        }).tokenStore(tokenStore())
          .approvalStoreDisabled();
    }

The following seems to be working with 1.3.5:

@Autowired
private AuthenticationManager auth;

@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // Workaround for
        // https://github.com/spring-projects/spring-boot/issues/1801
        endpoints.authorizationCodeServices(authorizationCodeServices())
                .authenticationManager(auth)
                .tokenStore(tokenStore())
                .approvalStoreDisabled();
    }

Tutorial has insufficient Spring initializer projects

Tutorial has insufficient information to executre, this includes Spring initializer projects, " In this case, we’re going to build a web application. So, select "Web" and then choose "Generate." A .zip will start downloading". It needs atleast JPA, some embedded DB H2 and likely others incl. HATEOS visible via advanced link below.
And there is no step for mvn spring-boot:run

cd rest

The tutorial mentions cd rest, but no mention of creating a "rest" folder was ever made before.

Required request body is missing: org.springframework.http.ResponseEntity<?>

When you create the POST method add, and you try to call it from a request, it return the next:

{
"timestamp": 1480245744194,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "Required request body is missing: org.springframework.http.ResponseEntity<?> com.garcia.bookmarks.BookmarkRestController.add(java.lang.String,com.garcia.bookmarks.Bookmark)",
"path": "/jhoeller/bookmarks"
}

All the other methods works fine, only this throw an error

RESTController does not contain imports

The code for BookmarkRESTController in the tutorial should contain what the source code here does:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.Collection;

Remove deprecated classes

I think a number of classes used in this example have been deprecated in the most recent versions of Spring. It sure would be appreciated to have an example that uses only the most current classes. Thanks!

readBookmark will find bookmarks of other users too

If you put an id of a bookmark belonging to an user different than the one specified in the path, it will return it.

That happens because inside this method you are only searching by the id and not filtering by the user id:

@RequestMapping(method = RequestMethod.GET, value = "/{bookmarkId}")
Bookmark readBookmark(@PathVariable String userId, @PathVariable Long bookmarkId) {
	this.validateUser(userId);
	return this.bookmarkRepository.findOne(bookmarkId);
}

It should be corrected to something like this:

@RequestMapping(method = RequestMethod.GET, value = "/{bookmarkId}")
Bookmark readBookmark(@PathVariable String userId, @PathVariable Long bookmarkId) {
	this.validateUser(userId);
	return this.bookmarkRepository.findByAccountUsername(userId).stream()
	           .filter(bookmark -> bookmark.getId() == bookmarkId)
	           .findFirst()
	           .orElse(null);
}

Tutorial text does not mention including Spring Security Starter

New to Spring Boot and Java and following the tutorial step by step.

IntelliJ threw errors about not finding symbols: GlobalAuthenticationConfigurerAdapter and AuthorizationServerConfigurerAdapter

I spent time trying to figure this out and I believe I needed to include spring security to my pom file. Adding it fixed GlobalAuthenticationConfigurerAdapter.

But I'm still trying to figure out GlobalAuthenticationConfigurerAdapter.

The tutorial does not mention to include spring-security when using the initilizer.

Getting Started - Missing Link

Under the Getting Started subtitle, you mention a link to a video demonstrating STS and Spring Boot but it is not provided.

Clarify CommandLineRunner in Application.java

[Copied in from personal blog comment]
Hello Greg,

A great tutorial on Spring REST with Spring Boot (http://spring.io/guides/tutorials/bookmarks/). I have a question that requires the clarity. In "Application.java", how does the line that has "return (evt) -> ..." returns the CommandLineRunner type? I was searching for everywhere how this works but most point to "implements CommandLineRunner" format...

UserNotFoundException code does not contain imports

The code should be:

package bookmarks;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
class UserNotFoundException extends RuntimeException {

public UserNotFoundException(String userId) {
	super("could not find user '" + userId + "'.");
}

}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.