If you omit the ‘reason’ attribute in the @ResponseStatus annotation on a custom exception,
@ResponseStatus(value = HttpStatus.CONFLICT) // 409
public class ChildDataExists extends RuntimeException {
...
then throw the exception – in your service layer. Thus you don’t need a catch and throw something else or catch in the controller to set the response directly to some HTTP status code.
throw new ChildDataExists("Can't delete parent if child row exists.");
The exception’s message comes through as the ‘message’ of the ‘data’ in the JSON output. It seems the ‘reason’ in the annotation overrides the custom behavior. So you can have say one basic exception for a given context and use it in a dozen places, each with a slightly differing message where it is thrown and all get’s handled properly out to the REST interface.