> Markdown version of [Browser-Callable Services](https://vaadin.com/docs/next/hilla/guides/browser-callable-services). Section index: [llms.txt](https://vaadin.com/docs/next/hilla/llms.txt)

# Browser-Callable Services for Accessing Java Backend

A *browser-callable service* in Hilla is a class that defines one or more public methods, and is annotated with the `@BrowserCallable` annotation.

Hilla bridges browser-callable Java services and a TypeScript frontend. It generates TypeScript clients to call the Java backend in a type-checkable way. The [TypeScript generator](https://vaadin.com/docs/next/hilla/reference/typescript-generator.md) reference page contains details about the generator itself.

> **Important: Browser-callable services depend on Spring Boot auto-configuration.**
>
> Browser-callable services don’t work if auto-configuration is disabled, such as when you use `@EnableWebMvc`. As a workaround, remove the `@EnableWebMvc` annotation, as described in the [Spring Boot documentation](https://docs.spring.io/spring-boot/docs/2.3.4.RELEASE/reference/html/spring-boot-features.html#boot-features-spring-mvc-auto-configuration). If you have a suggestion as to how to make it more useful, please share your idea on [GitHub](https://github.com/vaadin/hilla/issues/new/).

## <a id="enabling"></a>Enabling Browser-Callable Services in a Vaadin Project

Browser-callable services are part of Vaadin, but a project needs the Hilla Spring Boot starter for them to be generated. Add the dependency to the project:

`pom.xml`

```xml
<dependency>
    <groupId>com.vaadin</groupId>
    <artifactId>hilla-spring-boot-starter</artifactId>
</dependency>
```

The version comes from the `vaadin-bom` dependency management that the project already has. No other build configuration is needed: the Vaadin Maven plugin generates the TypeScript clients into the `src/main/frontend/generated` directory as part of the build.

If you don’t have a project yet, see [Getting Started](https://vaadin.com/docs/next/getting-started.md). For a step-by-step guide to calling a service from a React view, see [Call Java Services from React Views](https://vaadin.com/docs/next/building-apps/react/call-services.md).

## <a id="creating"></a>Creating a Browser-Callable Service

A browser-callable service is a Java class annotated with `@BrowserCallable`:

`CounterService.java`

```java
/**
 * A service that counts numbers.
 */
@BrowserCallable
@AnonymousAllowed
public class CounterService {

    /**
     * A method that adds one to the argument.
     */
    public int addOne(int number) {
        return number + 1;
    }
}
```

When the application starts, Hilla scans the classpath for `@BrowserCallable`-annotated classes. For each request to access a public method in such a service, a permission check is carried out. `@AnonymousAllowed` means that Hilla permits anyone to call the method from the client side.

Refer to the [Security](https://vaadin.com/docs/next/hilla/guides/security.md) article for details of configuring service access.

<!-- vale Vaadin.BrowserCallableService = NO -->

### <a id="endpoint-alias"></a>The Endpoint Alias

The `@Endpoint` annotation is an older alias for `@BrowserCallable` and works the same way. It’s still supported, but `@BrowserCallable` is preferred, since the name *'Endpoint'* is easily confused with the so-called *REST Endpoints*.

Example Using `@Endpoint` Instead of `@BrowserCallable`:

`CounterEndpoint.java`

```java
/**
 * A browser-callable service that counts numbers.
 */
@Endpoint
@AnonymousAllowed
public class CounterEndpoint {
    /**
     * A method that adds one to the argument.
     */
    public int addOne(int number) {
        return number + 1;
    }
}
```

The only difference is that `@Endpoint` supports a `value` attribute for changing the name under which the service is exposed. With `@BrowserCallable`, that name is always the same as the class name.

<!-- vale Vaadin.BrowserCallableService = YES -->

### <a id="extending-other-classes"></a>Extending Other Classes

Browser-callable services are standard Java classes and can extend other classes and implement interfaces. However, to prevent unintentional exposure, methods inherited from superclasses aren’t generated by default.

To expose methods from a superclass, use the `@EndpointExposed` annotation.

The following example demonstrates that only methods from the service itself and annotated superclasses are generated. The same principle applies to interfaces.

```java
@EndpointExposed
public class ExposedClass {
    public String fromExposedClass() { // (1)
        return "Hello from ExposedClass";
    }
}

public class NonExposedClass extends ExposedClass {
    public String fromNonExposedClass() { // (2)
        return "Hello from NonExposedClass";
    }
}

@BrowserCallable
public class ServiceClass extends NonExposedClass {
    public String fromServiceClass() { // (3)
        return "Hello from ServiceClass";
    }
}
```

1. This method is generated in `ServiceClass`.

2. This method is not generated, as its class is not annotated.

3. This method is generated, as it’s in the service class.

## <a id="modules-generated-from-browser-callable-services"></a>Modules Generated from Browser-Callable Services

Hilla generates a TypeScript module for every browser-callable service on the backend. Each such module exports all of the methods in the service.

You can import an entire module from the [barrel](https://basarat.gitbook.io/typescript/main-1/barrel) file, import all methods as a module from the service file, or select individual service methods. For example, the `CounterService.ts` could be used as in the following snippets:

`index.ts` (import the whole service module object from the barrel file)

`barrel.ts`

```typescript
import { CounterService } from 'Frontend/generated/endpoints';

CounterService.addOne(1).then((result) => console.log(result));
```

> **Note:** The barrel file exports all of the services at once. Therefore, you can import multiple services using a single import.

`index.ts` (import all imports as a service object)

`asterisk.ts`

```typescript
import * as CounterService from 'Frontend/generated/CounterService';

CounterService.addOne(1).then((result) => console.log(result));
```

`index.ts` (import a single service method)

`method.ts`

```typescript
import { addOne } from 'Frontend/generated/CounterService';

addOne(1).then((result) => console.log(result));
```

<!-- vale Vaadin.Terms-FrontendBackend = NO -->

> **Note: Frontend Directory Alias**
>
> The `'Frontend/'` path prefix is an alias for the `{project.basedir}/src/main/frontend` directory in your project.
>
> Hilla has this path alias in [the default TypeScript compiler configuration](https://vaadin.com/docs/next/hilla/reference/configuration.md#ts-compiler-options) (`tsconfig.json`); the Vite configuration file (`vite.generated.js`) respects the `tsconfig` aliases by default.
>
> Using this path alias is recommended since it allows for absolute import paths, rather than traversing the directory hierarchy in relative imports.

<!-- vale Vaadin.Terms-FrontendBackend = YES -->

Hilla generates the TypeScript modules automatically when you compile the application, as well as when the application is running in development mode.

By default, the generated files are located under `{project.basedir}/src/main/frontend/generated`. You can change the folder by providing the path for the generator in the `generatedTsFolder` property for the Hilla Maven plugin.

Hilla handles conversion between Java and TypeScript types. For more information about supported types, see [Type conversion](https://vaadin.com/docs/next/hilla/reference/type-conversion.md).

## <a id="typescript-module-content-example"></a>TypeScript Module Content Example

The generated TypeScript module for the browser-callable service defined in [`CounterService.java`](#creating), for example, would look as follows:

`CounterService.ts`

`CounterService.ts`

```typescript
import { EndpointRequestInit as EndpointRequestInit_1 } from "@vaadin/hilla-frontend";
import client_1 from "./connect-client.default.js";
async function addOne_1(number: number, init?: EndpointRequestInit_1): Promise<number> { return client_1.call("CounterService", "addOne", { number }, init); }
export { addOne_1 as addOne };
```

## <a id="objects"></a>Objects

A service method can return or receive a parameter as an object (i.e., a non-primitive type). In this case, the generator also creates a TypeScript interface for the object.

An object can be defined in the following ways:

- In a separate class that belongs to the project.

- In a class that belongs to the project dependency.

- In an inner class of a service or any other class.

`City.java`

`City.java`

```java
package com.vaadin.demo.fusion.accessingbackend;

/**
 * An entity that contains an information about a city.
 */
public class City {
    private final String country;
    private final String name;

    public City(String name, String country) {
        this.country = country;
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public String getCountry() {
        return country;
    }
}
```

`CountryService.java`

`CountryService.java`

```java
package com.vaadin.demo.fusion.accessingbackend;

import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.hilla.BrowserCallable;

import java.util.Arrays;
import java.util.List;

/**
 * A browser-callable service that shows principles of work with entities.
 */
@BrowserCallable
@AnonymousAllowed
public class CountryService {
    private final List<City> cities = Arrays.asList(
            new City("Turku", "Finland"), new City("Berlin", "Germany"),
            new City("London", "UK"), new City("New York", "USA"));

    /**
     * A method that returns a collection of entities.
     */
    public List<City> getCities(Query query) {
        return query.getNumberOfCities() <= cities.size()
                ? cities.subList(0, query.getNumberOfCities() - 1)
                : cities;
    }

    /**
     * An entity specified as an inner class.
     */
    public static class Query {
        private final int numberOfCities;

        public Query(final int numberOfCities) {
            this.numberOfCities = numberOfCities;
        }

        public int getNumberOfCities() {
            return numberOfCities;
        }
    }
}
```

The TypeScript output is the following:

`City.ts`

`City.ts`

```typescript
interface City {
    name?: string;
    country?: string;
}
export default City;
```

`Query.ts`

`Query.ts`

```typescript
interface Query {
    numberOfCities: number;
}
export default Query;
```

`CountryService.ts`

`CountryService.ts`

```typescript
import { EndpointRequestInit as EndpointRequestInit_1 } from "@vaadin/hilla-frontend";
import type City_1 from "./com/vaadin/demo/fusion/accessingbackend/City.js";
import type Query_1 from "./com/vaadin/demo/fusion/accessingbackend/CountryService/Query.js";
import client_1 from "./connect-client.default.js";
async function getCities_1(query: Query_1 | undefined, init?: EndpointRequestInit_1): Promise<Array<City_1 | undefined> | undefined> { return client_1.call("CountryService", "getCities", { query }, init); }
export { getCities_1 as getCities };
```

### <a id="nullable-non-nullable-types"></a>Nullable & Non-Nullable Types

See [Type nullability](https://vaadin.com/docs/next/hilla/reference/type-nullability.md) for more information about how the nullability algorithm works and how to make types non-nullable.

## <a id="cancelling-requests"></a>Cancelling Service Requests

Sometimes it can be useful to cancel programmatically some requests. For example, when a request is taking too long or is waiting in a queue despite no longer being needed. Hilla allows developers to cancel requests using standard [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).

All service requests support an optional parameter that can be used to pass an `AbortSignal` which allows them to be cancelled. It’s worth remembering, though, that a single `AbortController` can be used to cancel multiple requests by passing its `signal` to all of them.

The following example creates two buttons: one that makes requests to a slow service method, and another that triggers the cancellation of previously created requests. If a request is subject to cancellation, errors must be handled as the service method throws `AbortError`.

```tsx
import { useRef } from 'react';
import { Button, Notification } from '@vaadin/react-components';
import { HelloWorldService } from 'Frontend/generated/endpoints';

export default function CancellingView() {
  const abortController = useRef(new AbortController());

  async function makeRequest() {
    try {
      const serverResponse = await HelloWorldService.slow({
        signal: abortController.current.signal,
      });
      Notification.show(serverResponse);
    } catch (error) {
      if (error instanceof Error && error.name === 'AbortError') {
        Notification.show('Request aborted');
      } else {
        throw error;
      }
    }
  }

  function cancelRequest() {
    abortController.current.abort(); // (1)
    abortController.current = new AbortController(); // (2)
  }

  return (
    <>
      <Button onClick={makeRequest}>Make a request</Button>
      <Button onClick={cancelRequest}>Cancel pending requests</Button>
    </>
  );
}
```

1. Cancels all pending requests that were given this controller’s signal.

2. A controller can’t be reused after aborting, so create a new one for the requests that follow.

## <a id="service-urls"></a>Service URLs

Hilla automatically generates the URLs and wraps them in the generated TypeScript API so the developer doesn’t have to worry about them.

Even though you can access any public method in any browser-callable service with the `https://${base_url}/${prefix}/${service_name}/${method_name}` URL format, don’t use those URLs directly. Instead use the TypeScript methods.

1. The `${base_url}` is the base URL of the application, depending on the framework used. For instance, for the Spring framework the default URL, if the application is started locally, is `http://localhost:8080`. If the application is started with a context, it should be added to the end: such as, `http://localhost:8080/my-app`.

2. The `${prefix}` is the URL common part that every exposed service contains. By default, `connect` is used, but this can be configured in the application properties.

3. The `${service_name}` is by default the corresponding Java class name which exposes methods, although this can be changed with the `@Endpoint` annotation value.

4. The `${method_name}` is the public method name from the Java class.

For an application started locally with the `CounterService` service defined as shown, the URL is: `http://localhost:8080/connect/counterservice/addone`

```java
@BrowserCallable
public class CounterService {

    public int addOne(int number) {
        return number + 1;
    }
}
```

> **Note: Service URLs Aren’t Case-Sensitive**
>
> The service name and the method name aren’t case-sensitive in Hilla. Therefore, the URL shown is the same as `http://localhost:8080/connect/CounterService/addOne` or `http://localhost:8080/connect/COUNTERSERVICE/ADDONE`, or any other case combination for the service and method name.

### <a id="configuring-service-urls"></a>Configuring Service URLs

You can configure the following parts of the URL:

- `${prefix}`

  The default value is `connect`. To change it to some other value, provide an `application.properties` file in the project resources (`src/main/resources/application.properties`) and set the `vaadin.endpoint.prefix` property to the new value.

- `${service_name}`

  By default, the simple name of the Java class is taken. It’s possible to specify a value in the `@Endpoint` annotation to override the default one (`@Endpoint("customName")`). In this case, the `customName` value is used as a `${service_name}` to accept incoming requests. It’s also case-insensitive.

## <a id="service-method-validation"></a>Service Method Validation

The parameters of a service method are automatically validated and, if validation fails, a corresponding response is sent back to the browser.

<!-- vale Vaadin.Abbr = NO -->

Whenever a service method is invoked, its parameters are automatically validated using the [JSR 380](https://beanvalidation.org/2.0/) Bean validation specification after they’re deserialized from the request body.

<!-- vale Vaadin.Abbr = YES -->

This is useful in eliminating the boilerplate needed for the initial request validation. The framework automatically checks the constraints placed on beans and sends the response back to the client side if the validation fails. The browser raises an `EndpointValidationError` when it receives the corresponding response from the server.

### <a id="built-in-validation-constraints"></a>Built-In Validation Constraints

The built-in validation constraints are the set of annotations provided by the `jakarta.validation.validation-api` dependency. They’re intended to be placed on Java beans on the server side.

You can find a full list of the constraints at <https://beanvalidation.org/2.0/spec/#builtinconstraints>

To use these annotations, add them to the class field or method parameter. For example:

```java
public class Account {

  @Positive
  private Long id;

  @NotEmpty(message = "Each account must have a non-empty username")
  private String username;

  private void sendAccountData(@NotNull String destination) {
    // ...
  }
}
```

### <a id="custom-validation-constraints"></a>Custom Validation Constraints

It’s possible to create custom constraints. To do this, you need to create a custom annotation and a custom validator.

See the [official documentation](https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#validator-customconstraints) for more details.

### <a id="manual-validation"></a>Manual Validation

Since all of the dependencies needed for validating beans and methods are present, you can reuse them in any part of your project — not only in the service methods. For example:

```java
// A validator for validating beans
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
// non-empty set if there are any constraint validation errors
Set<ConstraintViolation<Object>> violations = validator.validate(bean);

// A validator for validating methods and constructors (return values, parameters)
ExecutableValidator executableValidator = validator.forExecutables();
// non-empty set if there are any constraint validation errors
Set<ConstraintViolation<Object>> violations = executableValidator.validateReturnValue(object, method, returnValue);
```

If required, you can throw an `EndpointValidationException` from a service method. This exception is caught by TypeScript and the corresponding `EndpointValidationError` is raised.

See the official documentation for more details on [validating bean constraints](https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-validating-bean-constraints) and [validating method constraints](https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-validating-executable-constraints).

### <a id="hilla-validation-implementation-details"></a>Hilla Validation Implementation Details

Hilla validates only the beans and method parameters that are used in the browser-callable service classes (i.e., classes with the `@BrowserCallable` annotation). No other types are validated, even if they have constraint annotations.

If any validation errors occur, a non-`200` response is sent back, which is interpreted in TypeScript as a reason to throw an `EndpointValidationError`. A similar effect is achieved if an `EndpointValidationException` is thrown by any of the Java service methods.

## <a id="error-handling"></a>Error Handling

A robust client implementation should be able to handle invalid service calls, errors on the server side, and network outages.

Hilla determines the success of a service call by inspecting the HTTP status code. The server returns the [200 OK](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200) code when it’s able successfully to process the request, deserialize the method body, find and execute the particular method in the service, 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 in the next sub-sections.

### <a id="missing-service"></a>Missing Service

If the request addresses a service or a method name not present on the backend, the server responds with [404 Not Found](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404) and Hilla raises an error of type `EndpointError`.

### <a id="parameter-validation-error"></a>Parameter Validation Error

If the method called in the request exists on the backend, but the parameter count and types don’t match the service method, the server responds with [400 Bad Request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) and Hilla raises an error of type `EndpointValidationException`. The error instance contains a field `validationErrorData` holding validation error information for each invalid parameter. See [Type conversion between JavaScript and Java](https://vaadin.com/docs/next/hilla/reference/type-conversion.md) for more details about the type conversion rules.

For example, the following service expects a `java.time.LocalDate` parameter:

`DateService.java`

```java
package com.vaadin.demo.fusion.errorhandling;

import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.hilla.BrowserCallable;

import java.time.LocalDate;

@BrowserCallable
public class DateService {

    @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:

`catch-invalid-args.ts`

```typescript
import { EndpointValidationError } from '@vaadin/hilla-frontend';
import { DateService } from 'Frontend/generated/endpoints';

export async function callService() {
  try {
    // pass an illegal date
    const tomorrow = await DateService.getTomorrow('2021-02-29');
    console.log(tomorrow);
    // handle result...
  } catch (error) {
    if (error instanceof EndpointValidationError) {
      error.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](https://vaadin.com/docs/next/hilla/guides/forms/binder-validation.md), validation exceptions from the server are handled automatically by the form binder.

### <a id="server-side-errors"></a>Server-Side Errors

If the service exists and its parameters could be passed, but its execution raises a Java runtime exception, the server responds with [500 Internal Server Error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500). When this happens, 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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400). Then the exception type and message passed to the `EndpointException` in Java are available in the `EndpointError` instance via the `type` and `message` attributes.

The following service implementation is an example of this:

`DataService.java`

```java
package com.vaadin.demo.pwa.offline;

import com.vaadin.hilla.BrowserCallable;
import com.vaadin.hilla.exception.EndpointException;

@BrowserCallable
public class DataService {

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

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

`catch-error.ts`

```typescript
import { EndpointError } from '@vaadin/hilla-frontend';
import { DataService } from 'Frontend/generated/endpoints';

export async function callService() {
  try {
    await DataService.getViewData();
  } catch (error) {
    if (error instanceof EndpointError) {
      console.warn(error.message); // "Not implemented"
      console.warn(error.type); // "com.vaadin.hilla.exception.EndpointException"
    }
  }
}
```

### <a id="network-errors"></a>Network Errors

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

`ts-view-with-service.ts`

```typescript
import { EndpointError } from '@vaadin/hilla-frontend';
// Import the remote service
import { DataService } from 'Frontend/generated/endpoints';

// Wrap service calls to return fallback data when offline
export async function getViewData() {
  try {
    return await DataService.getViewData();
  } catch (e) {
    if (!(e instanceof EndpointError)) {
      // Network failure: return fallback data
      return [];
    }

    // Service reached but returned abnormal status code:
    // pass exception on to caller
    throw e;
  }
}
```

See the documentation about [caching service data in local storage](https://vaadin.com/docs/next/hilla/guides/client-caching.md) using a generic wrapper.

### <a id="unexpected-response-contents"></a>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 a message and the [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object in the `response` field.

### <a id="cancelled-requests"></a>Cancelled Requests

When a request is cancelled programmatically, the service call promise resolves with an `AbortError`. This happens only when you explicitly cancel a request, as explained in [Cancelling Service Requests](#cancelling-requests).

## <a id="request-options"></a>Request Options

All regular Hilla requests support some options as the last parameter. They’re listed in the sections here.

### <a id="abort-signal"></a>Abort Signal

You can pass an [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal) object to the call to cancel a request. This is useful when you want to cancel a request, perhaps because you’ve decide you don’t need it after all or because it’s taking too long.

`request-options.ts`

```typescript
const controller = new AbortController();

SlowService.takesTime({ signal: controller.signal }).then((msg) => console.log(msg));

// The `AbortController` can be used to abort the request, for example after a timeout
// or an user action like a button click
const timeoutId = setTimeout(() => {
  controller.abort();
}, 5000);
```

### <a id="suppress-connection-state"></a>Suppress Connection State

By default, when a request takes too long, Hilla shows a progress bar to the user at the top of the page. You can suppress this by setting the `mute` option to `true`.

`request-options.ts`

```typescript
SlowService.takesTime({ mute: true }).then((msg) => console.log(msg));
```

## <a id="code-completion-in-ides"></a>Code Completion in IDEs

As you can see in the earlier `CounterService.ts` example, the Javadoc for the `@BrowserCallable` class is copied to the generated TypeScript file, and the type definitions are maintained. This helps code completion to work — at least in Visual Studio Code and IntelliJ IDEA Ultimate Edition.

[Image: Code-completion]

Code Completion in Visual Studio Code
