Hilla Documentation

Error Handling

Implementing error handling on the client side of a Hilla application.

This article covers error handling on the client side of a Hilla application. A robust client implementation should be able to deal with the most common cases. This includes invalid endpoint calls, errors on the server side, and network outages.

Endpoint Errors

Hilla determines the success of an endpoint call by inspecting the HTTP status code. The server returns the 200 OK code when it’s able to process the request successfully, deserialize the method body, find and execute the particular method in the endpoint, and serialize its return value into a response. If the status code of the response isn’t 200 OK, Hilla throws an error on the client side. The available parameters in the error and the specific class of the thrown error depend on the failure mode. The most common ones are described below.

Missing Endpoint

If the request addresses an endpoint or a method name not present on the backend, the server responds with 404 Not Found and Hilla raises an error of type EndpointError.

Parameter Validation Error

If the method called in the request exists on the backend, but the parameter count and types don’t match the endpoint method (see Type conversion between JavaScript and Java for more details about the type conversion rules), the server responds with 400 Bad Request and Hilla raises an error of type EndpointValidationException. The error instance contains a field validationErrorData holding validation error information for each invalid parameter.

For example, given the following endpoint expecting a java.time.LocalDate parameter:

package com.vaadin.demo.fusion.errorhandling;

import java.time.LocalDate;

import dev.hilla.Endpoint;
import com.vaadin.flow.server.auth.AnonymousAllowed;

@Endpoint
public class DateEndpoint {

    @AnonymousAllowed
    public LocalDate getTomorrow(LocalDate date) {
        return date.plusDays(1);
    }
}

A call with an illegal data parameter raises an EndpointValidationException with information about which parameters failed validation:

import { EndpointValidationError } from '@hilla/frontend';

import { DateEndpoint } from 'Frontend/generated/endpoints';

export async function callEndpoint() {
  try {
    // pass an illegal date
    const tomorrow = await DateEndpoint.getTomorrow('2021-02-29');
    console.log(tomorrow);
    // handle result...
  } catch (error) {
    if (error instanceof EndpointValidationError) {
      (error as EndpointValidationError).validationErrorData.forEach(
        ({ parameterName, message }) => {
          console.warn(parameterName); // "date"
          console.warn(message); // "Unable to deserialize an endpoint method parameter into type 'java.time.LocalDate'"
        }
      );
    } else {
      // handle other error types...
    }
  }
}

Note that when using server-side form validation, validation exceptions from the server are handled automatically by the form binder.

Server Side Errors

If the endpoint exists and its parameters could be passed, but its execution raises a Java runtime exception, the server responds with 500 Internal Server Error and Hilla raises an error of type EndpointError. As a special case, if the server-side exception is an instance of dev.hilla.exception.EndpointException or a subclass, the server instead responds with 400 Bad Request and the exception type and message passed to the EndpointException in Java are available in the EndpointError instance via the type and message attributes. For example, given the following endpoint implementation:

package com.vaadin.demo.pwa.offline;

import dev.hilla.Endpoint;
import dev.hilla.exception.EndpointException;

@Endpoint
public class DataEndpoint {

    public String getViewData() {
        throw new EndpointException("Not implemented");
    }
}

The following client-side call to the endpoint method logs the error message and exception type:

import { EndpointError } from '@hilla/frontend';

import { DataEndpoint } from 'Frontend/generated/endpoints';

export async function callEndpoint() {
  try {
    await DataEndpoint.getViewData();
  } catch (error) {
    if (error instanceof EndpointError) {
      console.warn((error as EndpointError).message); // "Not implemented"
      console.warn((error as EndpointError).type); // "dev.hilla.exception.EndpointException"
    }
  }
}

Network Errors

When the server isn’t reachable due to outage or network disruption, an endpoint call results in a low-level network error, different from EndpointError. Applications that support offline mode can wrap endpoint calls with exception-handling code returning a fallback value, by distinguishing between the error classes as follows:

import { EndpointError } from '@hilla/frontend';

// import the remote endpoint
import { DataEndpoint } from 'Frontend/generated/endpoints';

// wrap endpoint calls to return fallback data when offline
export async function getViewData() {
  try {
    return await DataEndpoint.getViewData();
  } catch (e) {
    if (!(e instanceof EndpointError)) {
      // network failure: return fallback data
      return [];
    } else {
      // endpoint reached but returned abnormal status code:
      // pass exception on to caller
      throw e;
    }
  }
}

Also see this article on caching endpoint data in local storage using a generic wrapper.

Unexpected Response Contents

If the server replies with a response other than 200 OK, and the string contained in the response isn’t valid JSON, an EndpointResponseError is raised. The exception contains the response text as message and the Response object in the response field.

Cancelled Requests

When a request is cancelled programmatically, the endpoint call promise will resolve with an AbortError. This happens only when you explicitly cancel a request, as explained in this article.