[External] : Re: Exploratory proposal: First-class namespace construct for data-oriented / functional Java
Øyvind Kvien
oyvind at kvien.no
Thu Feb 26 08:41:19 UTC 2026
Thank you for the thoughtful response.
I agree that removing a private constructor and a few static modifiers
alone would not justify introducing a new top-level construct. If this were
purely about shaving off ceremony, I agree that it would not meet the bar.
The distinction I am trying to surface is slightly different. It is less
about boilerplate and more about the semantic intent of grouping vs. the
semantic intent of identity.
A class—and similarly a declared singleton—introduces a type with identity,
a lifecycle, and the conceptual possibility of state. Even a "utility
class" is technically a type that can be used in reflection, as a generic
bounds carrier, or as a synchronization lock.
By contrast, a namespace construct would model pure structural grouping. It
would allow us to group related functions and constants without also
introducing the semantics of a type. In this sense, it decouples the
organization of code from the object model.
I appreciate the Scala-style object direction. It is a powerful way to
reify statics into the instance world. However, an object still introduces
an entity with instance semantics. If I have a collection of pure,
stateless mathematical functions, elevating them to an "instance" (a
singleton) introduces object-model semantics that are not conceptually
required for simple structural grouping.
The deeper question for Java might be: Must every grouping of code be a
type?
If the answer is yes, then the Scala-style object is indeed the superior
evolution. But if there is value in a "type-less" container that exists
purely for structural organization, name resolution, and the developer's
mental model, then a namespace offers a path that doesn't just "double
down" on statics, but moves them into a more appropriate, restricted
context.
On Wed, Feb 25, 2026 at 6:47 PM Brian Goetz <brian.goetz at oracle.com> wrote:
> The frictions you outline here are real. What's less clear is whether
> they are a big enough deal to be worth adding new top-level concepts to the
> language; I think most developers are willing to shrug this off and say
> "yeah, I have to use classes to simulate namepaces, and that means I have
> to engage in one or two bits of ceremony, but ... fine, I'll do that and
> get on with my life." Which is an entirely pragmatic viewpoint; the extra
> boilerplate of an inaccessible constructor, and the sprinkling of `static`,
> is a relatively small bit of ceremony.
>
> What I find uncompelling about this proposal is that it is _purely_ about
> the "code golf" of eliminating this bit of boilerplate, mostly doubling
> down on one of the weakest parts of the language -- static members. It
> doesn't offer us any new way to abstract over anything, it just removes
> some boilerplate from the "bag of static members" classes.
>
> If I felt these were a bigger deal, though, there is a _much_ better
> solution in evidence for these problems and more: something like Scala's
> `object` declaration. This achieves the boilerplate goals through solving
> some real semantic problems: lifecycle management of singletons, plus
> elevating static behavior to instance behavior. (This strengthens statics
> rather than just doubling down on them.)
>
> (Notably, though, even though this solution is better in pretty much every
> way, and significantly better in some ways, I'm still not sure whether it
> meets the bar for being worth working on.)
>
> For those who are unfamiliar: you can declare a singleton object much like
> you do a class:
>
> object Patient { // could implement interfaces here
> public final String PATIENT_REFERENCE_PREFIX = "Patient/";
> public final Pattern PATIENT_IDENTIFIER_PATTERN =
> Pattern.compile(...);
> // methods too
> }
>
> Here, the name `Patient` is bound to the singleton _instance_, so you use
> the same use-site syntax:
>
> String s = Patient.PATIENT_REFERENCE_PREFIX + ...
>
> but instead of this being intepreted as <class>.<static-member>, it is
> interpreted as <instance>.<instance-member>. So they look like static
> members, but we can abstract over them through interfaces:
>
> object FooBehaviors implements Comparator<Foo> {
> int compare(Foo a, Foo b) { ... }
> }
>
> ...
>
> foos.sort(FooBehaviors);
>
> (Perhaps a more familiar spelling for "object" here might be "singleton".)
>
> So:
>
> - Not sure the problem is worth solving
> - If it is worth solving, declared singletons dominate namespaces by a lot
> - Even with the much bigger payback of declared singletons, still not
> sure its worth solving
>
>
>
> On 2/13/2026 8:11 AM, Øyvind Kvien wrote:
>
> Dear Amber team,
>
> I would like to explore the idea of introducing a first-class namespace
> construct in Java, intended to support data-oriented and functional
> programming styles without repurposing classes or interfaces as containers
> for static members.
>
> Motivation
>
> In data-oriented and mostly functional Java codebases, it is common to
> group:
>
> -
>
> Stateless utility functions
> -
>
> Related constants
> -
>
> Small domain records
> -
>
> Parsing and validation logic
>
> Today, the idiomatic way to express this grouping is:
>
> public final class Patient {
>
> private Patient() {}
>
> public static final String PATIENT_REFERENCE_PREFIX = "Patient/";
> public static final Pattern PATIENT_IDENTIFIER_PATTERN =
> Pattern.compile("^Patient/(.*)", Pattern.CASE_INSENSITIVE);
>
> public record PatientIdentifier(String id) {}
>
> public static PatientIdentifier getPatientIdentifier(@Nullable Reference patientRef) {
> if (patientRef == null || patientRef.getReference() == null)
> return new PatientIdentifier(Str.EMPTY);
>
> var matcher = PATIENT_IDENTIFIER_PATTERN.matcher(patientRef.getReference());
> return new PatientIdentifier(matcher.find() ? matcher.group(1) : Str.EMPTY);
> }
> }
>
> This works, but it introduces conceptual and syntactic friction:
>
> -
>
> The type is not meant to be instantiated.
> -
>
> The constructor must be manually suppressed.
> -
>
> Everything must be explicitly marked static.
> -
>
> The type is not modeling an object or abstraction; it is modeling a
> namespace.
>
> In practice, such classes act as packages within packages.
>
> An alternative is to use an interface as a namespace:
>
> public interface Patient {
> String PATIENT_REFERENCE_PREFIX = "Patient/";
> record PatientIdentifier(String id) {}
> static PatientIdentifier getPatientIdentifier(...) { ... }
> }
>
> However, interfaces are primarily intended to model polymorphic contracts
> and substitution. Repurposing them as namespace containers can blur intent
> and introduce conceptual confusion. The well-known “constant interface”
> anti-pattern illustrates this discomfort.
>
> In short, Java lacks a first-class way to express “this is a namespace for
> related functions and data types.”
>
> Design Goal
>
> Provide a language-level construct that:
>
> -
>
> Represents a pure namespace (not a type with identity).
> -
>
> Cannot be instantiated or extended.
> -
>
> Groups related functions, constants, and nested types.
> -
>
> Supports access modifiers.
> -
>
> Avoids boilerplate such as private constructors and repeated static.
> -
>
> Preserves Java’s explicitness and readability.
>
> Strawman Syntax
>
> One possible direction:
>
> public namespace Patient {
>
> String PATIENT_REFERENCE_PREFIX = "Patient/";
> Pattern PATIENT_IDENTIFIER_PATTERN =
> Pattern.compile("^Patient/(.*)", Pattern.CASE_INSENSITIVE);
>
> record PatientIdentifier(String id) {}
>
> PatientIdentifier getPatientIdentifier(@Nullable Reference patientRef) {
> ...
> }
> }
>
> Semantics (strawman)
>
> -
>
> namespace introduces a named scope at top level.
> -
>
> Members are implicitly static.
> -
>
> Fields are implicitly static final unless specified otherwise.
> -
>
> Nested types are implicitly static.
> -
>
> The namespace itself:
> -
>
> Cannot be instantiated.
> -
>
> Cannot implement or extend anything.
> -
>
> Cannot declare instance state.
> -
>
> Initialization semantics follow class static initialization rules.
> -
>
> At the bytecode level, the namespace may be compiled to a synthetic
> final class, preserving JVM compatibility.
>
> Why Not Just Use Classes?
>
> Using final classes with private constructors is serviceable but
> semantically misleading:
>
> -
>
> A class suggests instantiability, inheritance relationships, or type
> abstraction.
> -
>
> Namespace-only classes are often flagged as utility classes.
> -
>
> The pattern is common enough that it arguably deserves first-class
> support.
>
> Why Not Just Use Interfaces?
>
> Interfaces are designed primarily for polymorphic abstraction. Using them
> as namespace containers:
>
> -
>
> Conflates two distinct concepts (contract vs grouping).
> -
>
> Introduces ambiguity in API design intent.
> -
>
> Encourages patterns that may confuse less experienced developers.
>
> Providing a dedicated construct allows interfaces to remain focused on
> substitution and abstraction.
>
> Interaction With Existing Features
>
> Questions for exploration include:
>
> -
>
> Should namespace members require explicit static, or be implicitly
> static?
> -
>
> Should access modifiers default to the namespace’s modifier?
> -
>
> How do annotations apply to the namespace?
> -
>
> Should nested namespaces be allowed?
> -
>
> How does reflection expose namespaces?
> -
>
> How should Javadoc render them?
>
> A minimal version could require explicit modifiers and treat namespaces as
> a restricted form of top-level type compiled to a synthetic final class.
>
> Summary
>
> As Java evolves toward stronger support for data-oriented programming
> (records, pattern matching, etc.), it may be worth revisiting how we
> express stateless domain logic and function groupings.
>
> A first-class namespace construct could:
>
> -
>
> Reduce boilerplate.
> -
>
> Clarify intent.
> -
>
> Preserve the role of classes and interfaces.
> -
>
> Improve expressiveness for functional-style Java.
>
> I would be interested in feedback on:
>
> 1.
>
> Whether this problem is considered significant enough.
> 2.
>
> Whether a namespace construct fits Java’s philosophy.
> 3.
>
> Whether there are smaller or more incremental ways to address the
> issue.
>
> Best regards,
> Øyvind Kvien
>
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20260226/6f9edc1d/attachment-0001.htm>
More information about the amber-dev
mailing list