HttpClient should allow different responseTypes for success response and error response
I'm submitting a...
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[ x ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
Current behavior
Currently, HttpClient expects the same responseType for both, success responses as well as error responses. This brings up issues when a WEB API returns e. g. JSON but just an (non JSON based) error string in the case of an error.
Expected behavior
In see two solutions: Provide an own errorResponseType field or assign the received message as a string when JSON parsing does not work (as a fallback)
Minimal reproduction of the problem with instructions
The following test case is using such an Web API. It returns json for the success case and plain text in the case of an error. Both tests lead to an 400 error due to validation issues. In the first case where we are requesting plain text, the text based error message is shown; in the second case where we are requesting json (which would be the case if the call succeeded) we are just getting null.
import { TestBed } from '@angular/core/testing';
import { HttpClient, HttpClientModule, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
fdescribe('Different responseTypes for success and error case', () => {
let http: HttpTestingController;
let httpClient: HttpClient;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpClientModule ]
});
httpClient = TestBed.get(HttpClient);
});
it('should send an HttpErrorResponse where the error property is the received body', (complete: Function) => {
let actualBody;
httpClient.post('http://www.angular.at/api/flight', {}, { responseType: 'text' }).subscribe(null, (resp: HttpErrorResponse) => {
actualBody = resp.error;
expect(actualBody).not.toBeNull();
complete();
});
});
it('should send an HttpErrorResponse where the error property is the body of the flushed response', (complete: Function) => {
let actualBody;
httpClient.post('http://www.angular.at/api/flight', {}, { responseType: 'json' }).subscribe(null, (resp: HttpErrorResponse) => {
actualBody = resp.error;
expect(actualBody).not.toBeNull();
complete();
});
});
});
What is the motivation / use case for changing the behavior?
Using existing Web APIs more seamlessly
Environment
Angular version: 4.3
For Tooling issues:
- Platform: Windows
This would be really great! I have a Spring Service that usually returns an Excel file (HttpClient uses responseType 'blob'), but might return JSON with error details if the generation fails.
In case of an Error the HttpErrorResponse.error is undefined when handling it in HttpInterceptor, so I cannot use the additional informations returned by the Backend.
I don't see any good workaround for doing this now:
- Move error messages from Backend to Frontend? -> Bad because only Backend knows all error details
- Use 2 requests, one for the actual request and one to get the last error message? -> Bad
- Don't use 'blob' but wrap the Response in JSON, encoding the blob content with Base64? -> More traffic, additional overhead in programming, linking to files directly ("/foo/bar.xls") wouldn't work anymore because JSON is returned.
So both thumbs up for this feature request!
A year later with Angular 6 still an issue. In my opinion this is a bug, not a feature.
Any news on this?
@lppedd This issue has already been fixed by https://github.com/angular/angular/pull/19773 since 4.4.6, an error body will always fallback to string when unable to parse as JSON.
Please provide repro if you have different case other than OP.
@trotyl See this example.
Spring @RestController handler method.
@GetMapping(
path = "/export/xls",
produces = MediaType.APPLICATION_OCTET_STREAM
)
public ResponseEntity<Resource> exportXls() {
final var file = ....;
final var httpHeaders = new HttpHeaders();
httpHeaders.add(
HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getName() + "\""
);
return ResponseEntity.ok()
.headers(httpHeaders)
.body(new FileSystemResource(file));
}
This returns a Blob object, basically.
Then, the Angular HttpClient part.
return this.httpClient
.get(url, { observe: 'response', responseType: 'blob' })
.pipe(catchError(e => handleError(e)))
This works if the request is successful. However, in case of exception thrown from the Spring handler method, the body is set to Blob.

The Spring ExceptionHandler is pretty standard, and should produce a JSON body.
@ResponseStatus(HttpStatus.BAD_REQUEST)
Result<Void> handleBadRequest(final Exception exception) {
logger.error(exception.getMessage(), exception);
return new Result<>(1, exception.getMessage());
}
@lppedd I see your case now, but the problem is, what Angular did is just pass responseType: 'blob' to XMLHttpRequest.responseType, and the parsing process is performed by browser.
The Blob your got is already the raw response provided by browser, regardless of statusCode, as there's no XMLHttpRequest.responseTypeOnSuccess and XMLHttpRequest.responseTypeOnError.
@trotyl I "temporary" solved by integrating a blob > string transformation. Do you think Angular will ever introduce the responseTypeOnError HttpClient option?
@lppedd I'd rather Angular drop the entire responseType concept of old XHR and switch to transform-style operation like fetch API, currently the static typing is already bloated with all this options and hard to control.
Hello again in 2020, I have this issue but cannot use the temporary solution described here. The problem is I want to synchronously get the error to check if the jwt token really got expired and do a refersh call API. This cannot be done using FileReader, and FileReaderSync is only available in Workers. I'm trying to find a hackaround for this but it's really not promising. Please fix this issue.
Also have had an issue with this. I will try workaround for now.
isn't the success type solved with the generic HttpClient we got a while back? https://angular.io/api/common/http/HttpClient#get

That at least solves it for me.
However the error response type is still any | null. Please fix :)
https://angular.io/api/common/http/HttpErrorResponse#error
Keep in mind that each http return code could return a different type. Http return code 400 and 500 probably doesn't have the same format.
My workaround is to add an interceptor with:
return next.handle(request).pipe(
catchError((err: HttpErrorResponse) => {
if (request.responseType === 'blob' && err.error instanceof Blob) {
return from(Promise.resolve(err).then(async x => { throw new HttpErrorResponse({ error: JSON.parse(await x.error.text()), headers: x.headers, status: x.status, statusText: x.statusText, url: x.url ?? undefined })}));
}
if (request.responseType === 'text' && typeof err.error === 'string') {
return from(Promise.resolve(err).then(async x => { throw new HttpErrorResponse({ error: JSON.parse(await x.error), headers: x.headers, status: x.status, statusText: x.statusText, url: x.url ?? undefined })}));
}
return throwError(err);
}),
EDIT: improved version that handles responseType 'text'. Also this interceptor should be added last since response intercepting is handled in reverse order.
My workaround is to add an interceptor with:
return next.handle(request).pipe( catchError((err: HttpErrorResponse) => { if (request.responseType === 'blob' && err.error instanceof Blob) { return from(Promise.resolve(err).then(async x => { throw new HttpErrorResponse({ error: JSON.parse(await x.error.text()), headers: x.headers, status: x.status, statusText: x.statusText, url: x.url ?? undefined })})); } return throwError(err); }),
Worked like a charm, thank you so much!
It would be great if Angular would have such a feature where you can set different response types for success or error responses.
For me, I use a synchronous way to convert Blob to json. I wrote a post about it here: https://stackoverflow.com/questions/48500822/how-to-handle-error-for-response-type-blob-in-httprequest/70977054#70977054
This is how it works:
//service class
public doGetCall(): void {
this.httpClient.get('/my-endpoint', {observe: 'body', responseType: 'blob'}).subscribe(
() => console.log('200 OK'),
(error: HttpErrorResponse) => {
const errorJson = JSON.parse(this.blobToString(error.error));
...
});
}
private blobToString(blob): string {
const url = URL.createObjectURL(blob);
xmlRequest = new XMLHttpRequest();
xmlRequest.open('GET', url, false);
xmlRequest.send();
URL.revokeObjectURL(url);
return xmlRequest.responseText;
}
// test case
it('test error case', () => {
const response = new Blob([JSON.stringify({error-msg: 'get call failed'})]);
myService.doGetCall();
const req = httpTestingController.expectOne('/my-endpoint');
expect(req.request.method).toBe('GET');
req.flush(response, {status: 500, statusText: ''});
... // expect statements here
});
The parsed errorJson in the error clause will now contain {error-msg: 'get call failed'}.
Hello in 2022. This is still a problem, 5 years later...
Hello in 2023 😀 With Angular 15. Still the same stuff
Hey peps ✌️ I also ran into the same issue yesterday.
My app is requesting a ZIP from the API, but receives JSON in case of an error.
I was wondering if responseType could be conditional based on the Content-Type of the response.
type ResponseType = 'arraybuffer' | 'blob' | 'json' | 'text'
{
responseType: ResponseType | {[key: string]: ResponseType}
}
get<T>(url: string, options: {
headers?: HttpHeaders | {
[header: string]: string | string[];
};
context?: HttpContext;
observe?: 'body';
params?: HttpParams | {
[param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>;
};
reportProgress?: boolean;
responseType: {[key: string]: ResponseType},
withCredentials?: boolean;
}): Observable<T>;
The requesting code would look something like this:
this.http.get('{url}', {
responseType: {
'application/json': 'json',
'application/zip': 'arraybuffer',
'*/*': 'text',
},
})
Hey peps ✌️
I also ran into the same issue yesterday.
My app is requesting a ZIP from the API, but receives JSON in case of an error.
I was wondering if
responseTypecould be conditional based on theContent-Typeof the response.type ResponseType = 'arraybuffer' | 'blob' | 'json' | 'text' { responseType: ResponseType | {[key: string]: ResponseType} }get<T>(url: string, options: { headers?: HttpHeaders | { [header: string]: string | string[]; }; context?: HttpContext; observe?: 'body'; params?: HttpParams | { [param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>; }; reportProgress?: boolean; responseType: {[key: string]: ResponseType}, withCredentials?: boolean; }): Observable<T>;The requesting code would look something like this:
this.http.get('{url}', { responseType: { 'application/json': 'json', 'application/zip': 'arraybuffer', '*/*': 'text', }, })
Nice suggestion. +1 for this
Hello in 2024... Angular 17... still having this issue. 😅
yes this is very bad
Angular 18.. how great it would be to have this issue solved
This issue should be more visible in https://angular.dev if not already. Spent a while tracking down this bug in our app to end up seeing that it is an open Angular issue.