diff a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp
--- a/src/hotspot/share/opto/doCall.cpp
+++ b/src/hotspot/share/opto/doCall.cpp
@@ -36,10 +36,11 @@
 #include "logging/logStream.hpp"
 #include "opto/addnode.hpp"
 #include "opto/callGenerator.hpp"
 #include "opto/castnode.hpp"
 #include "opto/cfgnode.hpp"
+#include "opto/inlinetypenode.hpp"
 #include "opto/mulnode.hpp"
 #include "opto/parse.hpp"
 #include "opto/rootnode.hpp"
 #include "opto/runtime.hpp"
 #include "opto/subnode.hpp"
@@ -595,11 +596,11 @@
     speculative_receiver_type = receiver_type != nullptr ? receiver_type->speculative_type() : nullptr;
   }
 
   // Additional receiver subtype checks for interface calls via invokespecial or invokeinterface.
   ciKlass* receiver_constraint = nullptr;
-  if (iter().cur_bc_raw() == Bytecodes::_invokespecial && !orig_callee->is_object_initializer()) {
+  if (iter().cur_bc_raw() == Bytecodes::_invokespecial && !orig_callee->is_object_constructor()) {
     ciInstanceKlass* calling_klass = method()->holder();
     ciInstanceKlass* sender_klass = calling_klass;
     if (sender_klass->is_interface()) {
       receiver_constraint = sender_klass;
     }
@@ -725,11 +726,11 @@
       // Be careful here with return types.
       if (ctype != rtype) {
         BasicType rt = rtype->basic_type();
         BasicType ct = ctype->basic_type();
         if (ct == T_VOID) {
-          // It's OK for a method  to return a value that is discarded.
+          // It's OK for a method to return a value that is discarded.
           // The discarding does not require any special action from the caller.
           // The Java code knows this, at VerifyType.isNullConversion.
           pop_node(rt);  // whatever it was, pop it
         } else if (rt == T_INT || is_subword_type(rt)) {
           // Nothing.  These cases are handled in lambda form bytecode.
@@ -783,10 +784,63 @@
     }
     BasicType ct = ctype->basic_type();
     if (is_reference_type(ct)) {
       record_profiled_return_for_speculation();
     }
+    if (rtype->is_inlinetype() && !peek()->is_InlineType()) {
+      Node* retnode = pop();
+      retnode = InlineTypeNode::make_from_oop(this, retnode, rtype->as_inline_klass(), !gvn().type(retnode)->maybe_null());
+      push_node(T_OBJECT, retnode);
+    }
+
+    // Note that:
+    // - The caller map is the state just before the call of the currently parsed method with all arguments
+    //   on the stack. Therefore, we have caller_map->arg(0) == this.
+    // - local(0) contains the updated receiver after calling an inline type constructor.
+    // - Abstract value classes are not ciInlineKlass instances and thus abstract_value_klass->is_inlinetype() is false.
+    //   We use the bottom type of the receiver node to determine if we have a value class or not.
+    const bool is_current_method_inline_type_constructor =
+        // Is current method a constructor (i.e <init>)?
+        _method->is_object_constructor() &&
+        // Is the holder of the current constructor method an inline type?
+        _caller->map()->argument(_caller, 0)->bottom_type()->is_inlinetypeptr();
+    assert(!is_current_method_inline_type_constructor || !cg->method()->is_object_constructor() || receiver != nullptr,
+           "must have valid receiver after calling another constructor");
+    if (is_current_method_inline_type_constructor &&
+        // Is the just called method an inline type constructor?
+        cg->method()->is_object_constructor() && receiver->bottom_type()->is_inlinetypeptr() &&
+         // AND:
+         // 1) ... invoked on the same receiver? Then it's another constructor on the same object doing the initialization.
+        (receiver == _caller->map()->argument(_caller, 0) ||
+         // 2) ... abstract? Then it's the call to the super constructor which eventually calls Object.<init> to
+         //                    finish the initialization of this larval.
+         cg->method()->holder()->is_abstract() ||
+         // 3) ... Object.<init>? Then we know it's the final call to finish the larval initialization. Other
+         //        Object.<init> calls would have a non-inline-type receiver which we already excluded in the check above.
+         cg->method()->holder()->is_java_lang_Object())
+        ) {
+      assert(local(0)->is_InlineType() && receiver->bottom_type()->is_inlinetypeptr() && receiver->is_InlineType() &&
+             _caller->map()->argument(_caller, 0)->bottom_type()->inline_klass() == receiver->bottom_type()->inline_klass(),
+             "Unexpected receiver");
+      InlineTypeNode* updated_receiver = local(0)->as_InlineType();
+      InlineTypeNode* cloned_updated_receiver = updated_receiver->clone_if_required(&_gvn, _map);
+      cloned_updated_receiver->set_is_larval(false);
+      cloned_updated_receiver = _gvn.transform(cloned_updated_receiver)->as_InlineType();
+      // Receiver updated by the just called constructor. We need to update the map to make the effect visible. After
+      // the super() call, only the updated receiver in local(0) will be used from now on. Therefore, we do not need
+      // to update the original receiver 'receiver' but only the 'updated_receiver'.
+      replace_in_map(updated_receiver, cloned_updated_receiver);
+
+      if (_caller->has_method()) {
+        // If the current method is inlined, we also need to update the exit map to propagate the updated receiver
+        // to the caller map.
+        Node* receiver_in_caller = _caller->map()->argument(_caller, 0);
+        assert(receiver_in_caller->bottom_type()->inline_klass() == receiver->bottom_type()->inline_klass(),
+               "Receiver type mismatch");
+        _exits.map()->replace_edge(receiver_in_caller, cloned_updated_receiver, &_gvn);
+      }
+    }
   }
 
   // Restart record of parsing work after possible inlining of call
 #ifndef PRODUCT
   parse_histogram()->set_initial_state(bc());
