In case, you want to make some sanity checks (or other additions) in factory method, you can use Compact constructor of record (i.e. constructor without parameters part). For example:

record HelloWorld(String message) {
    public HelloWorld {
        java.util.Objects.requireNonNull(message);
    }
}

Through it will not hide canonical constructor, but it will "append" it with code provided (sanity checks, automatic registrations, ...)

Record cannot hide its canonical constructor like it is possible in case class from scala. "java" way for doing this is using class with private constructor:

public class Foo {
    private final Integer a;
    private Foo(Integer a) {
        this.a = a;
    }
    public Foo of(Integer left, Integer right) {
        return new Foo(left + right);
    }
}

Through using opinionated lombok library, you can lower down boilerplate to scala level:

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class Foo {
    private final Integer a;

    public Foo of(Integer left, Integer right) {
        return new Foo(left + right);
    }
}

If you are already using lombok, you can be interested in its immutable annotation called @Value.

Answer from Lubo on Stack Overflow
🌐
Baeldung
baeldung.com › home › java › core java › custom constructor in java records
Custom Constructor in Java Records | Baeldung
January 15, 2026 - Java Records are a concise way to define immutable data containers in Java 14. In this article, we’ll explore how custom constructors in Java Records give us greater control during object initialization by allowing for data validation and error handling.
Discussions

How to hide constructor on a Java record that offers a public static factory method? - Stack Overflow
Especially versions of Java from 8 forward. As such, your answer is still largely incorrect. 2024-09-05T16:00:03.693Z+00:00 ... Save this answer. ... Show activity on this post. How to hide the implicit constructor of a record if the compiler forbids marking it as private? More on stackoverflow.com
🌐 stackoverflow.com
Exception when deserialization of `private` record with default constructor
RecordIssue related to JDK17 ... to JDK17 java.lang.Record supporthas-failing-testIndicates that there exists a test case (under `failing/`) to reproduce the issueIndicates that there exists a test case (under `failing/`) to reproduce the issue ... I searched in the issues and found nothing similar. ... com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of ... (no Creators, like default constructor, exist): cannot ... More on github.com
🌐 github.com
7
October 25, 2023
Regular classes to have a constructor similar to Records
Maybe, but one thing to note about records -- probably the most important thing -- is that they're not about reducing boilerplate, although that is a nice side-effect -- but about guaranteeing certain semantics. The compact constructor definition is available for the canonical constructor, which is special in multiple ways. For one, it matches the components, allowing construction and deconstruction to be dual operators (automatically!); for another, it cannot be circumvented by serialization, i.e., the only way to construct a record is through its canonical constructor (perhaps going through another constructor, first, though), which has ramifications for correctness and even security. So various elements from record classes can, and might well be, expanded to regular classes, but the important thing about records is not their syntax, but their simple and consistent semantics. Of course, the simple semantics allow for simple syntax; the more complex semantics of regular classes might make the syntax more elaborate, too. Recgular classes don't have components but fields, so are the fields defined with a compact declaration final? volatile? private, package-private, protected, or public? What about their accessors? Could they be, say, package-private? How about synchronized? These questions have simple answers for records, but not for regular classes. More on reddit.com
🌐 r/java
41
2
March 31, 2021
Private constructor
A private constructor stops other classes from directly constructing an object and forcing them to go via a static method, often referred to as a static factory method. The example you provided is a singleton pattern. The static factory method ensures that there is only ever one instance of that class in the application, if one already exists already it is returned, if it doesn't exist the private constructor will be called to create one. This is necessary because a constructor always creates a new instance, which is the opposite of what you want when creating a singleton. Static factory methods also have other uses, like being able to change the response type being returned. A constructor always returns an instance of the class, while a static method calling the private constructor can return anything. I use private constructors a lot to wrap my new objects in a Result type. E.g: public class User { private User() {...} // Returns User object public static Result Create() { // Do validation // return Result.Fail if validation fails // return Result.Ok(new User()) if validation passes } } Result types are essentially an alternative to throwing an exception in a constructor of an object that can't/shouldn't be created based on input parameters. Other use cases may include something like a "TryCreate" pattern, e.g. static bool TryCreate(out User user, ...). TLDR: private constructors are typically used to provide advanced instantiation logic through static methods. More on reddit.com
🌐 r/csharp
6
0
January 1, 2024
🌐
Medium
medium.com › @mak0024 › a-comprehensive-guide-to-java-records-2e8edcbd9c75
A Comprehensive Guide to Java Records | by AM | Medium
January 4, 2024 - Finally, the visibility of the canonical constructor must not be more restrictive than the visibility of the record itself. This means that a record marked as private may have a constructor marked as public — but a record declared as public may not have a private constructor.
🌐
Oracle
docs.oracle.com › en › java › javase › 14 › language › records.html
Java Language Updates
A public constructor whose signature is derived from the record components list. The constructor initializes each private field from the corresponding argument.
🌐
DEV Community
dev.to › ankit_sood_66861d56d8e42a › demystifying-java-records-a-developers-guide-h0d
Demystifying Java Records: A Developer's Guide - DEV Community
August 7, 2025 - This means that once the values are set via the canonical constructor, they cannot be changed. Also, since the fields are private, they cannot be accessed directly from outside the record.
🌐
Mike my bytes
mikemybytes.com › 2022 › 02 › 16 › java-records-and-compact-constructors
Java records & compact constructors | Mike my bytes
February 16, 2022 - Additionally, at the time of writing (Java 17), an explicit canonical constructor is not allowed to have a more restrictive access level than the record itself. // this won't compile as well! record Name(String name) { // package-private // fails with: 'invalid canonical constructor in record Name // (attempting to assign stronger access privileges; // was package)' private Name(String name) { this.name = name; } static Name of(String name) { return new Name(name); } } So if you’re a fan of using static factory methods to avoid the new keyword, you either have to deal with the existence of the canonical one or switch to a standard class.
🌐
HappyCoders.eu
happycoders.eu › java › records
Java Records (with Examples)
June 12, 2025 - public record Point(int x, int y) { public Point(int x, int y) { this.x = x; this.y = x; // Assigning this.y to x here - and ignoring y } }Code language: Java (java) Fortunately, modern IDEs recognize this. IntelliJ, for example, warns that “'x' should probably not be assigned to 'y'”. Finally, the visibility of the canonical constructor must not be more restrictive than the visibility of the record itself. This means that a record marked as private may have a constructor marked as public – but a record declared as public may not have a private constructor – the following is, therefore, not allowed:
🌐
Coderanch
coderanch.com › t › 776593 › code-reviews › engineering › Java-Record-Builder-Practice
Java Record Builder Best Practice (Code Reviews forum at Coderanch)
September 16, 2023 - . . can't have private constructors. . . . I tried that last week and got a compiler error. A record should have a constructor with the same access as the class or more permissive access. So you can only have a private constructor in a private record class and that means a private nested class.
Find elsewhere
Top answer
1 of 5
17

This is essentially impossible, in the sense that this just isn't how records work. You may be able to hack it together but that is very much not what records were meant to do, and as a consequence, if you do, the code will be confusing, the API will need considerable extra documentation to explain it doesn't work the way you think it does, and future lang features will probably make your API instantly obsoleted (it feels out of date and works even more weirdly now).

The essential nature of records

Records are designed to be deconstructable and reconstructable, and to support these features intrinsically, as in, without the need to write any code to enable any of this. Records just 'get all that stuff', for free, but at a cost: They are inherently defined by their 'parts'. This has all sorts of effects - they cannot extend anything (Because that would mean they are defined by a combination of their parts and the parts their supertype declares, that's more complex than is intended), and the parts are treated as final, and you can't make it act like somehow it isn't actually just a thing that groups together its parts, which is the problem with your code.

Let's make that 'if you try to do it, future language features will ruin your day' aspect of it and focus on deconstruction and the with concept.

Yes, this mostly isn't part of java yet, but the record feature is specifically designed to be expanded to encompass deconstruction and all the features that it brings, and work is far along. Specifically, Brian Goetz, who is in charge of this feature and various features that expand on a more holistic idea (records is merely a small part of that idea), really loves this stuff and has repeatedly written about it. Including quite complete feature proposals.

Specifically, for records, you are soon going to be able to write this (note, as is usual with OpenJDK feature proposals, don't focus on the syntax or about concerns such as '.. but, does that mean 'with' is now a keyword?' - actual syntax is the very last thing that is fleshed out.

CopyDatePair dp = ....;

DatePair newDp = dp with {
  days = 20;
}

See JEP 468: Derived Record Creation (Preview).

The concept of a 'deconstruction' is the same as a constructor, but in reverse: Take an object and break it apart into its constituent parts. For records, this is obvious, and in fact (and this is the key, why you can't do what you want in this way), baked in - records deconstruct by way of the elements you listed for the record (so, here, start, end, and days) and you probably won't be able to change this.

The obj with {block;} operation is syntax sugar for:

  • Deconstruct obj.
  • For each item that the deconstruction has produced, declare a local variable.
  • Run block. The local vars are available, and aren't final - change whatever you want.
  • Construct a new object of the same type as obj, using those local vars to pass to its constructor.

Your idea can only work if the deconstructor deconstructs solely into LocalDate start and LocalDate end, leaving long days out of it entirely. It would require records to be able to state: Actually, this is my constructor, with different arguments from the record's component list, and once you open the door to writing your own constructor, you therefore then also have to write your own deconstructor.

The thing is, there is no syntax for deconstructors right now. Thus, if records did allow you to write your own constructor (with a different list of params than the record components), that means that if in the future a language feature is released such as with, that requires a deconstructor, that some records can't support it. That's annoying to the java lang team: They'd love to say: "We introduced with, which works with all records already! In the future once we release the deconstructor feature it should also be usable for classes that have a deconstructor".

That is to say, while I can't say I 100% know exactly what Brian and the OpenJDK team is thinking, I'd be incredibly surprised if they aren't thinking like the above.

The point of the above dive into OpenJDK's plans for near-future java is to explain that [A] why you can't make your own constructor in records, and [B] why there won't be a language feature coming along that will, unless it is part of a set of very significant updates (including deconstructor syntax, and that won't happen unless there are features that use it).

Good news!

Fortunately, there are very simple alternate strategies you can use here.

This seems to be the best fit for your needs, usable in today's java:

Copypublic record DatePair(LocalDate start, LocalDate end) {
  public long days() {
    return ChronoUnit.DAYS.between(start, end);
  }
}

This removes days from being treated as a component part of DatePair, but then, that is the point - components in records fundamentally can be changed independently of the other parts of it (possibly you can add code that then says the new state is invalid, but now you force folks to set start and days both simultaneously, you can't 'calculate it out', which seems like API so bad you wouldn't want such a thing).

It also suffers from the notion that days is now calculated every time instead of being 'cached'. You can solve that by writing your own cache e.g. with guava cachebuilder but that's quite a big bazooka to kill a mosquito. If this truly is the calculation you need, it's relatively cheap, I'd just write it like this and not worry about performance unless you are holding a profiler report that says this days calculation is the key culprit.

If this still isn't acceptable, then the thing you want just isn't what record represents. You might as well ask how you represent arbitrary strings with an enum. You just cannot do that - that is not what enums are about. Hence, you end up at:

Copyimport lombok.*;

@Value
@lombok.experimental.Accessors(fluent = true)
public class DatePair {
  @With private final LocalDate start, end;
  @With private final long days;

  public DatePair(LocalDate start, LocalDate end) {
    this.start = start;
    this.end = end;
    this.days = ChronoUnit.DAYS.between(start, end);
  }
}

I strongly recommend you don't add the accessors line (this turns getStart() into just start(). Records notwithstanding, get is just better (it plays far better with auto-complete which is ubiquitous in java editor environments, and is more common. In fact, the java core libs themselves do it 'right' and use get, see e.g. java.time.LocalDate). But, if you really really want it - that's how you do it. Or add a lombok.config file and say there that you want fluent style accessor names.

Or let your IDE generate it all, which will be a ton of code you'll have to maintain. I understand the 'draw' of using records here, but records can't do lots of things. They can't extend anything either. They can't memoize calculated stuff by way of a field either.

2 of 5
1

How to hide the implicit constructor of a record if the compiler forbids marking it as private?

I would argue that the canonical constructor of a record is very explicit (but that may be semantics). The record specification makes it clear that the canonical constructor is public. Changing that would go against the record's reason to exist.

You can achieve what you want with a regular class.

🌐
Abhinavpandey
abhinavpandey.dev › blog › java-records
A Guide to Records in Java - Abhinav Pandey
May 15, 2022 - Before moving on to Records, let's look at the problem Records solve. To understand this, let's examine how value objects were created before Java 14. Value objects are an integral part of Java applications. They store data that needs to be transferred between layers of the application. A value object contains fields, constructors and methods to access those fields. Below is an example of a value object: public class Contact { private final String name; private final String email; public Contact(String name, String email) { this.name = name; this.email = email; } public String getName() { return name; } public String getEmail() { return email; } }
🌐
Baeldung
baeldung.com › home › java › core java › java record keyword
Java Record Keyword | Baeldung
November 5, 2025 - As of JDK 14, we can replace our repetitious data classes with records. Records are immutable data classes that require only the type and name of fields. The equals, hashCode, and toString methods, as well as the private, final fields and public constructor, are generated by the Java compiler.
🌐
Oracle
docs.oracle.com › javase › specs › jls › se15 › preview › specs › records-jls.html
Records
March 16, 2026 - If the record class is private, then the canonical constructor may be declared with any accessibility.
🌐
javathinking
javathinking.com › blog › private-canonical-constructor-for-record
How to Enforce Private Canonical Constructors in Java Records: Factory Method Instantiation Like Scala Case Classes — javathinking.com
By making the canonical constructor private, you force users to instantiate records through controlled factory methods, similar to how Scala case classes use `apply()` methods in companion objects. In this blog, we’ll explore how to implement this pattern in Java, drawing parallels to Scala’s ...
🌐
Dev.java
dev.java › learn › using-record-to-model-immutable-data
Using Records to Model Immutable Data
January 5, 2024 - Getting to know the basics of the Java language. ... Defining your own classes, declaring member variables, methods, and constructors. ... How to model your immutable data with records to make your code simpler and more readable.
🌐
GitHub
github.com › FasterXML › jackson-databind › issues › 4175
Exception when deserialization of `private` record with default constructor · Issue #4175 · FasterXML/jackson-databind
October 25, 2023 - When I add compact constructor with @JsonCreator to TestObject, it works correctly: private record TestObject(String text) { @JsonCreator private TestObject { } } It works correctly in version 2.15.3 · tested on java 21 · May be related to Exception when deserialization uses a record with a constructor property with access=READ_ONLY #4119 ·
Author   FasterXML
🌐
Xebia
xebia.com › home › blog › how to use java records
Java Records: Default Values, Validation & Constructors | Xebia
2 weeks ago - Records simplify code while supporting validation, normalization, and data integrity through constructors. ... Java records are immutable data carriers and do not support default values directly.
🌐
GeeksforGeeks
geeksforgeeks.org › java › what-are-java-records-and-how-to-use-them-alongside-constructors-and-methods
What are Java Records and How to Use them Alongside Constructors and Methods? - GeeksforGeeks
June 22, 2022 - To create such a simple class, you'd need to define its constructor, getter, and setter methods, and if you want to use the object with data structures like HashMap or print the contents of its objects as a string, we would need to override methods such as equals(), hashCode(), and toString(). ... // Java Program Illustrating Program Without usage of // Records // A sample Employee class class Employee { // Member variables of this class private String firstName; private String lastName; private int Id; // Constructor of this class public Employee(String firstName, String lastName, int Id) { /