Thoughts on non-public @BrowserCallable services?

Hello all,

At the moment, all @BrowserCallable classes must be public for Hilla to generate a TypeScript client for it. Furthermore, only the public methods of the @BrowserCallable class are included in the generated client. The reasoning behind this has been that since the browser callable class is a part of the application’s public API, the Java class should also be public.

Spring’s REST controllers do not have to be public, even though they are also a part of the application’s public API. This makes sense because you typically don’t want any other Java code to call the REST controller.

What are your thoughts on supporting package-private @BrowserCallable services?

-Petter-

1 Like

My personal opinion: Java access modifiers define the access control of the Java API. The @BrowserCallable annotations control a different access dimension, and that is not connected to the Java access control mechanism.

1 Like

I would appreciate it if we had the choice. However, I have not yet had a situation where it was absolutely necessary to declare the browser callable class as private.

If we make package-private classes / methods available when annotated with @BrowserCallable, then that also raises a question about indirectly referenced types.

@BrowserCallable
class MyService {
  static class Data {
     public String getFoo() { return null; }
     public void setFoo(String foo) {}

     String getBar() { return null; }
     void setBar(String bar) {}
  }

  Data getData() {
    return new Data();
  }
}

If we assume that MyService.getData would be available to the client even though the service class and the method are package-private, then what would the Data type look like in TypeScript. In particular, should it contain a bar property or not? What about 3rd party types with package-private members that are referenced from application code?

1 Like

I guess this ticket is related: protected setter/getter is in the generated typescript · Issue #2587 · vaadin/hilla · GitHub

If I have private, protected or package protected methods, why should it be exposed to the most public API = client side?

Client side is not in the same package.

If you have a Data class with package private methods and expose it. Does this mean the client side could access this method but not the service itself (if it’s not in the same package).

The data classes are basically just JSON data structures, right? So why not let Jackson handle it? Properties that would be included by Jackson are included in the generated TypeScript class. Then you can customize it any way you like.

For third party types, you would have to plug in a custom serializer and deserializer if you are not happy with the defaults.

The way I see it, the client in this case is written in a different language (TypeScript), and runs in a different process (the browser), on the other side of a network connection. Java visibility is about Java, and extending it across the JVM boundary feels confusing.

The @BrowserCallable annotation is what we use to control accessibility from the outside and this does not have anything to do with Java visibility. I may have a @BrowserCallable service that I don’t want any Java code to call.

Spring has had problems like this before that are now getting fixed. In earlier versions, @Transactional only worked on public methods, for instance. If you put it on a package private method, it would silently be ignored. I was made aware of this when an application was behaving strangely in production.

If you can add a Java annotation to a class or method, it should not matter what the visibility of that class or method is - the end result should be the same. And if it matters, you should receive an error during build time or at the very latest when the application starts up.

2 Likes

But the BrowserCallable works on the class. How do you not expose a method in the class?

What should be exposed?

  • private
  • protected
  • package-protected
  • public

Java visibility is about Java, and extending it across the JVM boundary feels confusing.

Does this mean private method should be exposed?

Since @BrowserCallable is a class-annotation, we can decide what methods to expose. My preferred choice would be that it only exposes public methods, even if the class itself is package-protected.

If we had a similar annotation for methods (which I don’t think we need), and that annotation was placed on a private method, then that private method should be exposed.

1 Like

My default visibility is package private. Especially on RestController classes because I don’t want to expose them.

I try to keep the visibility as narrow as possible.

IMO BrowserCallable should behave as RestControllers

3 Likes

IMO BrowserCallable should behave as RestControllers

Agreed. I think we should align with Spring practices where we can so things work as expected for most developers.

1 Like

Created a ticket: Support package visibility for @BrowserCallable services · Issue #2843 · vaadin/hilla · GitHub

3 Likes