Accessing Java Backend

A server-side Java endpoint is a backend method that is exposed for calling from client-side TypeScript code. An endpoint in Vaadin is a class that defines one or more public methods.

Vaadin bridges Java backend endpoints and a TypeScript frontend. It generates TypeScript clients to call the Java backend in a type-checkable way.

Warning
Vaadin endpoints depend on Spring Boot auto configuration.
They do not work if the auto configuration is disabled, for example, when you use @EnableWebMvc. As a workaround, remove the @EnableWebMvc annotation, as stated in Spring Boot documentation. If you have an idea how to make it more useful for you, please share it on GitHub.

Creating an Endpoint

An endpoint is a Java class annotated with @Endpoint:

/**
 * A Vaadin endpoint that counts numbers.
 */
@Endpoint
@AnonymousAllowed
public class CounterEndpoint {
    /**
     * A method that adds one to the argument.
     */
    public int addOne(int number) {
        return number + 1;
    }
}

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

Please refer to the Security for configuring endpoint access.

TypeScript Modules

In JavaScript and TypeScript, modules are files that follow the module syntax. This syntax has the following properties:

  • Top-level declarations (variables, functions, classes, etc.) are scoped inside the module, meaning that they are by default not available outside it.

  • A module supports top-level export statements, which make declarations available to other modules.

  • A module supports top-level import statements, which load and execute other modules, and can bring exported declarations.

The following example demonstrates the city.ts module:

// Declare and export an interface
export default interface City {
  country: string;
  name: string;
}
// Import and use a declaration from another module
import type City from './city';
const cityObject: City = {
  country: 'Finland',
  name: 'Turku',
};
// Note: cityObject is not exported, thus it is only available in this file

In Vaadin applications, the index.ts (or, optionally, index.js) file is also a module.

Modules Generated From Vaadin Endpoints

Fusion generates a TypeScript module for every Vaadin endpoint on the backend. Each such module exports all the methods in the endpoint.

You can either import an entire module from the barrel file, import all methods as a module from the endpoint file, or select individual endpoint methods. For example, the CounterEndpoint.ts could be used as in the following snippets:

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

CounterEndpoint.addOne(1).then((result) => console.log(result));
Note
Barrel file exports all the endpoints at once, so you can import multiple endpoints using a single import.
import * as CounterEndpoint from 'Frontend/generated/CounterEndpoint';

CounterEndpoint.addOne(1).then((result) => console.log(result));
import { addOne } from 'Frontend/generated/CounterEndpoint';

addOne(1).then((result) => console.log(result));
Note
The “Frontend” directory alias

The 'Frontend/' path prefix is an alias for the {project.basedir}/frontend directory in your application project.

Vaadin has this path alias in the default TypeScript compiler configuration (tsconfig.json); webpack configuration file (webpack.generated.js) respects the tsconfig aliases by default.

Using this path alias is recommended, as it allows for absolute import paths, rather than traversing the directory hierarchy in relative imports.

Fusion 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}/frontend/generated. You can change the folder by providing the path for the generator in the generatedFrontendDirectory property for Vaadin Maven plugin.

Vaadin takes care of type conversion between Java and TypeScript types. For more information about supported types, see Type Conversion.

Example TypeScript Module Contents

For example, the generated TypeScript module for the Java endpoint defined in CounterEndpoint.java would look as follows:

/**
 * A Vaadin endpoint that counts numbers.
 *
 * This module is generated from CounterEndpoint.java
 * All changes to this file are overridden. Please consider to make changes in the corresponding Java file if necessary.
 * @see {@link file:///srv/jenkins/workspace/documentation-site-latest/docs/src/main/java/com/vaadin/demo/fusion/accessingbackend/CounterEndpoint.java}
 * @module CounterEndpoint
 */

// @ts-ignore
import client from './connect-client.default';

/**
 * A method that adds one to the argument.
 *
 * @param _number
 *
 */
function _addOne(
 _number: number
): Promise<number> {
 return client.call('CounterEndpoint', 'addOne', {_number});
}
export {
  _addOne as addOne,
};

Entities

An Endpoint method can return or receive a parameter as an entity (non-primitive type). In this case, the generator also creates a TypeScript interface for the entity.

Entity 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 an endpoint or any other class).

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;
    }
}
package com.vaadin.demo.fusion.accessingbackend;

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

import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.fusion.Endpoint;

/**
 * A Vaadin endpoint that shows principles of work with entities.
 */
@Endpoint
@AnonymousAllowed
public class CountryEndpoint {
    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:

/**
 * An entity that contains an information about a city.
 * This module is generated from com.vaadin.demo.fusion.accessingbackend.City.
 * All changes to this file are overridden. Please consider to make changes in the corresponding Java file if necessary.
 * @see {@link file:///srv/jenkins/workspace/documentation-site-latest/docs/src/main/java/com/vaadin/demo/fusion/accessingbackend/City.java}
 */

export default interface City {
  readonly country?: string;
  readonly name?: string;
}
/**
 * An entity specified as an inner class.
 * This module is generated from com.vaadin.demo.fusion.accessingbackend.CountryEndpoint.Query.
 * All changes to this file are overridden. Please consider to make changes in the corresponding Java file if necessary.
 * @see {@link file:///srv/jenkins/workspace/documentation-site-latest/docs/src/main/java/com/vaadin/demo/fusion/accessingbackend/CountryEndpoint.java}
 */

export default interface Query {
  readonly numberOfCities: number;
}
/**
 * A Vaadin endpoint that shows principles of work with entities.
 *
 * This module is generated from CountryEndpoint.java
 * All changes to this file are overridden. Please consider to make changes in the corresponding Java file if necessary.
 * @see {@link file:///srv/jenkins/workspace/documentation-site-latest/docs/src/main/java/com/vaadin/demo/fusion/accessingbackend/CountryEndpoint.java}
 * @module CountryEndpoint
 */

// @ts-ignore
import client from './connect-client.default';
import type City from './com/vaadin/demo/fusion/accessingbackend/City';
import type Query from './com/vaadin/demo/fusion/accessingbackend/CountryEndpoint/Query';

/**
 * A method that returns a collection of entities.
 *
 * @param query
 *
 */
function _getCities(
 query: Query | undefined
): Promise<ReadonlyArray<City | undefined> | undefined> {
 return client.call('CountryEndpoint', 'getCities', {query});
}
export {
  _getCities as getCities,
};

Read-Only Access

According to TypeScript best practices, the data in the application must be immutable. You shouldn’t mutate your data implicitly, especially if the data came from the backend. Due to it, all the generated entities provide read-only access to their properties. All collections are immutable as well. If you want to change the data, it is better to create a new structure and make a shallow copy of the original. For arrays, you can also use array immutable operators: filter, map, reduce, etc.

const user: User = { first: 'John', last: 'Doe' };

// Making a shallow copy of "user" data with changes.
const changed = { ...user, first: 'Jane' };

// You can do the same for arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

const unitedArr = [...arr1, ...arr2];
const onlyEvenArr = unitedArr.filter((val) => val % 2 === 0);

However, sometimes it may be necessary to mutate the data or send it to a method that accepts only mutable structures (it often happens to ReadonlyArray that cannot be implicitly cast to the Array). In this case, you can cast the data structure manually.

const collection: ReadonlyArray<string | undefined> = [];
const city: City = { name: 'Turku', country: 'Finland' };

type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

const mutableCollection = collection as Array<string | undefined>;
const mutableCity = city as Mutable<City>;

Instead of declaring Mutable helper by hands, you can also use the type-fest library which provides it along with many other useful type helpers.

Nullable and Non-Nullable Types

Please refer to Type Nullability to get more information about nullability algorithm works and how to make types non-nullable.

Code Completion in IDEs

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

Code Completion in Visual Studio Code

Code-completion