Support Content Negotiation with @ExceptionHandler
Prior to this commit, `@ExceptionHandler` annotated controller methods
could be mapped using the exception type declaration as an annotation
attribute, or as a method parameter.
While such methods support a wide variety of method arguments and return
types, it was not possible to declare the same exception type on
different methods (in the same controller/controller advice).
This commit adds a new `produces` attribute on `@ExceptionHandler`; with
that, applications can vary the HTTP response depending on the exception
type and the requested content-type by the client:
```
@ExceptionHandler(produces = "application/json")
public ResponseEntity<ErrorMessage> handleJson(IllegalArgumentException exc) {
return ResponseEntity.badRequest().body(new ErrorMessage(exc.getMessage(), 42));
}
@ExceptionHandler(produces = "text/html")
public String handle(IllegalArgumentException exc, Model model) {
model.addAttribute("error", new ErrorMessage(exc.getMessage(), 42));
return "errorView";
}
```
This commit implements support in both Spring MVC and Spring WebFlux.
Closes gh-31936
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webflux.controller.webfluxanncontrollerexceptions;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
@Controller
|
||||
public class SimpleController {
|
||||
|
||||
@ExceptionHandler(IOException.class)
|
||||
public ResponseEntity<String> handle() {
|
||||
return ResponseEntity.internalServerError().body("Could not read file storage");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webflux.controller.webfluxannexceptionhandlermedia;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
@Controller
|
||||
public class MediaTypeController {
|
||||
|
||||
// tag::mediatype[]
|
||||
@ExceptionHandler(produces = "application/json")
|
||||
public ResponseEntity<ErrorMessage> handleJson(IllegalArgumentException exc) {
|
||||
return ResponseEntity.badRequest().body(new ErrorMessage(exc.getMessage(), 42));
|
||||
}
|
||||
|
||||
@ExceptionHandler(produces = "text/html")
|
||||
public String handle(IllegalArgumentException exc, Model model) {
|
||||
model.addAttribute("error", new ErrorMessage(exc.getMessage(), 42));
|
||||
return "errorView";
|
||||
}
|
||||
// end::mediatype[]
|
||||
|
||||
static record ErrorMessage(String message, int code) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webmvc.mvccontroller.mvcannexceptionhandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
@Controller
|
||||
public class SimpleController {
|
||||
|
||||
@ExceptionHandler(IOException.class)
|
||||
public ResponseEntity<String> handle() {
|
||||
return ResponseEntity.internalServerError().body("Could not read file storage");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webmvc.mvccontroller.mvcannexceptionhandlerexc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystemException;
|
||||
import java.rmi.RemoteException;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
@Controller
|
||||
public class ExceptionController {
|
||||
|
||||
// tag::narrow[]
|
||||
@ExceptionHandler({FileSystemException.class, RemoteException.class})
|
||||
public ResponseEntity<String> handleIoException(IOException ex) {
|
||||
return ResponseEntity.internalServerError().body(ex.getMessage());
|
||||
}
|
||||
// end::narrow[]
|
||||
|
||||
|
||||
// tag::general[]
|
||||
@ExceptionHandler({FileSystemException.class, RemoteException.class})
|
||||
public ResponseEntity<String> handleExceptions(Exception ex) {
|
||||
return ResponseEntity.internalServerError().body(ex.getMessage());
|
||||
}
|
||||
// end::general[]
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webmvc.mvccontroller.mvcannexceptionhandlermedia;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
@Controller
|
||||
public class MediaTypeController {
|
||||
|
||||
// tag::mediatype[]
|
||||
@ExceptionHandler(produces = "application/json")
|
||||
public ResponseEntity<ErrorMessage> handleJson(IllegalArgumentException exc) {
|
||||
return ResponseEntity.badRequest().body(new ErrorMessage(exc.getMessage(), 42));
|
||||
}
|
||||
|
||||
@ExceptionHandler(produces = "text/html")
|
||||
public String handle(IllegalArgumentException exc, Model model) {
|
||||
model.addAttribute("error", new ErrorMessage(exc.getMessage(), 42));
|
||||
return "errorView";
|
||||
}
|
||||
// end::mediatype[]
|
||||
|
||||
static record ErrorMessage(String message, int code) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webflux.controller.webfluxanncontrollerexceptions;
|
||||
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import java.io.IOException
|
||||
|
||||
@Controller
|
||||
class SimpleController {
|
||||
|
||||
@ExceptionHandler(IOException::class)
|
||||
fun handle() : ResponseEntity<String> {
|
||||
return ResponseEntity.internalServerError().body("Could not read file storage")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webflux.controller.webfluxannexceptionhandlermedia
|
||||
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.ui.Model
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
|
||||
@Controller
|
||||
class MediaTypeController {
|
||||
|
||||
// tag::mediatype[]
|
||||
@ExceptionHandler(produces = ["application/json"])
|
||||
fun handleJson(exc: IllegalArgumentException): ResponseEntity<ErrorMessage> {
|
||||
return ResponseEntity.badRequest().body(ErrorMessage(exc.message, 42))
|
||||
}
|
||||
|
||||
@ExceptionHandler(produces = ["text/html"])
|
||||
fun handle(exc: IllegalArgumentException, model: Model): String {
|
||||
model.addAttribute("error", ErrorMessage(exc.message, 42))
|
||||
return "errorView"
|
||||
}
|
||||
// end::mediatype[]
|
||||
|
||||
@JvmRecord
|
||||
data class ErrorMessage(val message: String?, val code: Int)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webmvc.mvccontroller.mvcannexceptionhandler;
|
||||
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import java.io.IOException
|
||||
|
||||
@Controller
|
||||
class SimpleController {
|
||||
|
||||
@ExceptionHandler(IOException::class)
|
||||
fun handle() : ResponseEntity<String> {
|
||||
return ResponseEntity.internalServerError().body("Could not read file storage")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webmvc.mvccontroller.mvcannexceptionhandlerexc
|
||||
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import java.io.IOException
|
||||
import java.rmi.RemoteException
|
||||
|
||||
@Controller
|
||||
class ExceptionController {
|
||||
|
||||
// tag::narrow[]
|
||||
@ExceptionHandler(FileSystemException::class, RemoteException::class)
|
||||
fun handleIoException(ex: IOException): ResponseEntity<String> {
|
||||
return ResponseEntity.internalServerError().body(ex.message)
|
||||
}
|
||||
// end::narrow[]
|
||||
|
||||
|
||||
// tag::general[]
|
||||
@ExceptionHandler(FileSystemException::class, RemoteException::class)
|
||||
fun handleExceptions(ex: Exception): ResponseEntity<String> {
|
||||
return ResponseEntity.internalServerError().body(ex.message)
|
||||
}
|
||||
// end::general[]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webmvc.mvccontroller.mvcannexceptionhandlermedia
|
||||
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.ui.Model
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
|
||||
@Controller
|
||||
class MediaTypeController {
|
||||
|
||||
// tag::mediatype[]
|
||||
@ExceptionHandler(produces = ["application/json"])
|
||||
fun handleJson(exc: IllegalArgumentException): ResponseEntity<ErrorMessage> {
|
||||
return ResponseEntity.badRequest().body(ErrorMessage(exc.message, 42))
|
||||
}
|
||||
|
||||
@ExceptionHandler(produces = ["text/html"])
|
||||
fun handle(exc: IllegalArgumentException, model: Model): String {
|
||||
model.addAttribute("error", ErrorMessage(exc.message, 42))
|
||||
return "errorView"
|
||||
}
|
||||
// end::mediatype[]
|
||||
|
||||
@JvmRecord
|
||||
data class ErrorMessage(val message: String?, val code: Int)
|
||||
}
|
||||
Reference in New Issue
Block a user