Private static members in sealed-interfaces?

Swaranga Sarma sarma.swaranga at gmail.com
Mon Jun 23 15:12:10 UTC 2025


Ah, yes. When you said members, I was thinking of non-static instance
fields. Yes, this is all about access control.

> My point is that I think the anomaly you are really raising is that
access control for interface members is not uniform, but without a clear
justification other than history.  Private _methods_ were introduced into
interfaces in Java 8 (to go with default methods), but there's no good
reason why private fields (interfaces already have fields, they are static)
or private nested classes did not get the same treatment.  This is a topic
for potential cleanup at some point.
Thank you.

> But, if I understand your clarification, you are saying: "sealed classes
let me treat all the implementations of a type as being a single big
implementation, and sometimes these might want to share state, and the
sealed class is a sensible place to put state shared across all
implementations"?  (And then taking advantage of the nestmate status of
co-declared classes, so that "private" isn't really private to the
interface, but private to the source file.)
Yes, a much better way to say what I was trying to emphasize with my
example using the sealed classes hierarchy.

Regards
Swaranga


On Mon, Jun 23, 2025 at 5:19 AM Brian Goetz <brian.goetz at oracle.com> wrote:

> Interfaces are allowed to have (public) fields today, but they are always
> static.  So we're just talking about access control.
>
> My point is that I think the anomaly you are really raising is that access
> control for interface members is not uniform, but without a clear
> justification other than history.  Private _methods_ were introduced into
> interfaces in Java 8 (to go with default methods), but there's no good
> reason why private fields (interfaces already have fields, they are static)
> or private nested classes did not get the same treatment.  This is a topic
> for potential cleanup at some point.
>
> But, if I understand your clarification, you are saying: "sealed classes
> let me treat all the implementations of a type as being a single big
> implementation, and sometimes these might want to share state, and the
> sealed class is a sensible place to put state shared across all
> implementations"?  (And then taking advantage of the nestmate status of
> co-declared classes, so that "private" isn't really private to the
> interface, but private to the source file.)
>
> On 6/23/2025 8:04 AM, Swaranga Sarma wrote:
>
> Hi Brian, I am afraid I am not seeing how I am making a case for private
> members in interfaces. That would be introducing state into an interface
> and seems pretty large change to the language spec.
>
> As for the suggestion, yes, the post does make it seem like it is specific
> to sealed interfaces but wasn’t intended as such. I was using it as a real
> example from my work which uses a sealed hierarchy. Although now that I
> think about it, for regular interfaces I don’t see how it would be helpful
> if these fields are private to the interface.
>
> I have felt its need more in sealed interfaces where I want to share
> common static fields and methods across the sub interfaces/records.
>
> Regards
> Swaranga
>
>
> On Mon, Jun 23, 2025 at 4:50 AM Brian Goetz <brian.goetz at oracle.com>
> wrote:
>
>> What I see here is an argument for private members in interfaces, but
>> this has nothing to do with sealed interfaces.  Why is sealing relevant to
>> this suggestion?
>>
>>
>> On 6/22/2025 11:18 PM, Swaranga Sarma wrote:
>>
>> Traditionally Java has not allowed private static members inside of an
>> interface but now with sealed interfaces, I am wondering if there is a case
>> to be made. Here is a real-world use-case where I felt this was needed
>> recently.
>>
>> I have a sealed interface hierarchy for a simple state machine transition
>> result like below:
>>
>> ```
>> sealed interface TransitionCheckResult {
>>   sealed interface BootstrapTransitionCheckResult
>> extends TransitionCheckResult {
>>     record AllowedTransition(String destinationStateId, Map<String,
>> String> metadata) {}
>>     record DisallowedTransition(Map<String, String> metadata) {}
>>   }
>>
>>
>>   sealed interface IntermediateTransitionCheckResult
>> extends TransitionCheckResult {
>>     record AllowedTransition(String destinationStateId,
>>                                              Map<String, String>
>> metadata,
>>                                              List<Actions>
>> cleanupActions) {}
>>     record DisallowedTransition(Map<String, String> metadata) {}
>>   }
>> }
>> ```
>>
>> Basically the "transition check result" is broken up into a
>> bootstrap transition check result for the initial state transition check
>> and an "intermediary transition check result" for an intermediate node in
>> the state machine each of which have their allowed and disallowed
>> transitions with slightly varying attributes. So a sealed interface with
>> records seemed to fit the bill here.
>>
>> Now for the destinationStateId, there are some constraints like length
>> and Pattern (among others). I wanted to add a private static final Pattern
>> variable in the TransitionCheckResult interface which I then use to
>> validate the passed in destinationStateId. But unfortunately, this is not
>> allowed. I think I have the following options none of which seem great:
>> 1. Make the Pattern public; no-body else needs access to the Pattern
>> field so this feels unnecessary.
>> 2. Move the pattern field to inside the record class - this would work
>> but I have two records that require the same validation and either I have
>> to repeat myself or add the pattern to one of them and reference it in the
>> other. Again, feels like I shouldn't have to introduce this otherwise
>> unnecessary dependency.
>> 3. Create a DestinationStateId record where I can fully encapsulate the
>> validation in one place and change the AllowedTransition record
>> declarations to use this record. This feels like the best approach but then
>> in another part of the app, I lose the ability to do switch on the
>> destinationStateId like this:
>>
>> ```
>> static final RESOLVED_STATE_ID = new PolicyStateId("resolved);
>> PolicyStateId stateId = ...;
>> switch(stateId.name()) {
>>   case RESOLVED_STATE_ID.name() -> ... // not allowed
>>   ...
>> }
>> ```
>> There are slight variations of these approaches (like using a when clause
>> to address the switch problem or using a static inner Validator class
>> inside the top-level sealed interface) but it feels verbose.
>>
>> Thoughts? Especially now with sealed interfaces and records, there could
>> be more cases where this could be useful.
>>
>> Regards
>> Swaranga
>>
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20250623/6e3dc8e2/attachment.htm>


More information about the amber-dev mailing list