25

I have updated a Spring Boot application from 1.4.x to 1.5.1 and the Spring Actuator endpoints return a different MIME type now:

For example, /health is now application/vnd.spring-boot.actuator.v1+json instead simply application/json.

How can I change this back?

kap
  • 927
  • 2
  • 12
  • 19

6 Answers6

20

The endpoints return a content type that honours what the client's request says it can accept. You will get an application/json response if the client send an Accept header that asks for it:

Accept: application/json
Andy Wilkinson
  • 85,432
  • 19
  • 215
  • 204
  • 3
    Ok, that at least works for my own applications and the others that behave good. For Firefox I have to look for some setting, it will always download endpoints instead displaying them. Thanks. – kap Feb 24 '17 at 08:35
  • 4
    I was just bitten by this as well. May I ask why it was changed? It's a bit of a PITA to change the settings of all the browsers in use in our company. I haven't even found out yet how to do this in Safari or browers on smart phones. Does the change by any chance has anything to do with the following issue? https://github.com/spring-projects/spring-boot/issues/7648 – Thomas Oellrich Mar 07 '17 at 13:44
  • This still changes the content-type received in clients that don't provide application/json. Why didn't you keep the behaviour for v1 and just change it in v2? See https://github.com/spring-projects/spring-boot/issues/7967, "We're going to break things in 2.0 so returning the content type in 1.x would be a good thing to do." (so lets break thinks in v1 also >:‑) ) Anyway custom health endpoint implemented now. – hirro Apr 29 '17 at 05:09
  • "Break things" means changing the structure of the response body. A well-written client should be unaffected by the change made in 1.5. – Andy Wilkinson Apr 29 '17 at 06:21
  • 1
    Thanks, but I don't think I will forward that information to the mobile app developers :-) Will instead override the health API like below to keep things smooth. – hirro May 02 '17 at 08:43
  • 3
    @kap The trick with Firefox is that newer versions of the browser have a nice built-it JSON viewer, but don't acknowledge the `application/vnd.spring-boot.actuator.v1+json` as an actual JSON type. You can use the JSONView plugin, with checking two options there: `Include "application/json" in the HTTP Accept header for requests` and `Use built-in Firefox JSON viewer`. This will make the browser to ask for `application/json` type in response, but JSONView won't interfere with reading the response, it will be read by Firefox as a normal JSON. – jihor Jun 17 '17 at 22:23
  • For firefox Quantum users you may display the json properly using this extension : https://addons.mozilla.org/en-US/firefox/addon/json-content-type-override/ – SeB.Fr Nov 21 '17 at 11:40
14

In response to the comment of https://stackoverflow.com/users/2952093/kap (my reputation is to low to create a comment): when using Firefox to check endpoints that return JSON I use the Add-on JSONView. In the settings there is an option to specify alternate JSON content types, just add application/vnd.spring-boot.actuator.v1+jsonand you'll see the returned JSON in pretty print inside your browser.

Community
  • 1
  • 1
loïc
  • 662
  • 5
  • 13
5

As you noticed the content type for actuators have changed in 1.5.x.

If you in put "application/json" in the "Accept:" header you should get the usual content-type.

But if you don't have any way of modifying the clients, this snippet returns health (without details) and original content-type (the 1.4.x way).

@RestController
@RequestMapping(value = "/health", produces = MediaType.APPLICATION_JSON_VALUE)
public class HealthController {

    @Inject
    HealthEndpoint healthEndpoint;
    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity<Health > health() throws IOException {
        Health health = healthEndpoint.health();
        Health nonSensitiveHealthResult = Health.status(health.getStatus()).build();
        if (health.getStatus().equals(Status.UP)) {
            return ResponseEntity.status(HttpStatus.OK).body(nonSensitiveHealthResult);
        } else {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(nonSensitiveHealthResult);
        }
    }
}

Configuration (move away existing health)

endpoints.health.path: internal/health
hirro
  • 574
  • 10
  • 14
4

Based on the code in https://github.com/spring-projects/spring-boot/issues/2449 (which also works fine but completely removes the new type) I came up with

@Component
public class ActuatorCustomizer implements EndpointHandlerMappingCustomizer {

    static class Fix extends HandlerInterceptorAdapter {


        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            Object attribute = request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
            if (attribute instanceof LinkedHashSet) {
                @SuppressWarnings("unchecked")
                LinkedHashSet<MediaType> lhs = (LinkedHashSet<MediaType>) attribute;
                if (lhs.remove(ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON)) {
                    lhs.add(ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON);
                }
            }
            return true;
        }

    }

    @Override
    public void customize(EndpointHandlerMapping mapping) {
        mapping.setInterceptors(new Object[] {new Fix()});
    }
}

which puts the new vendor-mediatype last so that it will use application/json for all actuator endpoints when nothing is specified.

Tested with spring-boot 1.5.3

zapl
  • 60,293
  • 10
  • 115
  • 141
  • Quick question: `if (...was removed...) { ...re-add...}`? `HashSet.remove()` [removes only if the element was present](https://docs.oracle.com/javase/8/docs/api/java/util/HashSet.html#remove-java.lang.Object-). This works, but why? :-) – crusy May 23 '17 at 12:19
  • Your code switches `[application/vnd.spring-boot.actuator.v1+json, application/json]` to `[application/json, application/vnd.spring-boot.actuator.v1+json]`. What works as well (and might be less confusing): `lhs.remove(ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON); lhs.add(MediaType.APPLICATION_JSON);` – crusy May 23 '17 at 12:34
  • 1
    @crusy I don't know what that special new type is used / intended for so I don't want to remove it. It can stay in the list of supported mediatypes so code that wants it can still request it, but it should come after `application/json` for me because I want to use a default browser to get json. Since a `LinkedHashSet` keeps insertion oder, I simply remove it and re-add it to sort it to the back. With an `if` to make sure I don't modify stuff that didn't have this new type in the first place. – zapl May 23 '17 at 14:10
  • Works for me in Spring Boot 1.5.4.RELEASE and Grails 3.3.0.RC1, but only if I change the customize method to instead contain `mapping.setInterceptors(new Fix())` – jerryb Jul 10 '17 at 05:06
4

Since SpringBoot 2.0.x the suggested solution in implementing the EndpointHandlerMappingCustomizer doesn't work any longer.

The good news is, the solution is simpler now.

The Bean EndpointMediaTypes needs to be provided. It is provided by the SpringBoot class WebEndpointAutoConfiguration by default.

Providing your own could look like this:

@Configuration
public class ActuatorEndpointConfig {

    private static final List<String> MEDIA_TYPES = Arrays
        .asList("application/json", ActuatorMediaType.V2_JSON);

    @Bean
    public EndpointMediaTypes endpointMediaTypes() {
        return new EndpointMediaTypes(MEDIA_TYPES, MEDIA_TYPES);
    }
}
CyclingSir
  • 93
  • 8
2

To support application/vnd.spring-boot.actuator.v1+json in Firefox's built in JSON viewer, you can install this addon: json-content-type-override. It will convert content types that contain "json" to "application/json".

Update: Firefox 58+ has built-in support for these mime types, and no addon is needed anymore. See https://bugzilla.mozilla.org/show_bug.cgi?id=1388335

Bian Jiaping
  • 888
  • 8
  • 20
  • It's says it won't be necessary for Firefox 58+: "The purpose of this addon has been natively integrated into Firefox 58." – AndreLDM Dec 14 '17 at 17:46
  • 1
    How do we turn on the override in Firefox 58+? I'm on 62 and firefox still requests to save the file rather than just displaying it – dan carter Sep 30 '18 at 19:52