Exploratory proposal: First-class namespace construct for data-oriented / functional Java
Øyvind Kvien
oyvind at kvien.no
Fri Feb 13 13:11:01 UTC 2026
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/20260213/d8a95146/attachment-0001.htm>
More information about the amber-dev
mailing list