Exploratory proposal: First-class namespace construct for data-oriented / functional Java

Brian Goetz brian.goetz at oracle.com
Wed Feb 25 17:47:57 UTC 2026


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:
>
>      o
>
>         Cannot be instantiated.
>
>      o
>
>         Cannot implement or extend anything.
>
>      o
>
>         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/20260225/68510a38/attachment-0001.htm>


More information about the amber-dev mailing list