diff --git a/client/pom.xml b/client/pom.xml
index f907964..2d87b21 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <groupId>info.frostfs.sdk</groupId>
         <artifactId>frostfs-sdk-java</artifactId>
-        <version>0.3.0</version>
+        <version>${revision}</version>
     </parent>
 
     <artifactId>client</artifactId>
@@ -21,17 +21,17 @@
         <dependency>
             <groupId>info.frostfs.sdk</groupId>
             <artifactId>cryptography</artifactId>
-            <version>0.3.0</version>
+            <version>${revision}</version>
         </dependency>
         <dependency>
             <groupId>info.frostfs.sdk</groupId>
             <artifactId>models</artifactId>
-            <version>0.3.0</version>
+            <version>${revision}</version>
         </dependency>
         <dependency>
             <groupId>info.frostfs.sdk</groupId>
             <artifactId>exceptions</artifactId>
-            <version>0.3.0</version>
+            <version>${revision}</version>
         </dependency>
         <dependency>
             <groupId>commons-codec</groupId>
diff --git a/client/src/main/java/info/frostfs/sdk/FrostFSClient.java b/client/src/main/java/info/frostfs/sdk/FrostFSClient.java
index 31a76b7..e6704ed 100644
--- a/client/src/main/java/info/frostfs/sdk/FrostFSClient.java
+++ b/client/src/main/java/info/frostfs/sdk/FrostFSClient.java
@@ -24,6 +24,9 @@ import info.frostfs.sdk.jdo.parameters.container.PrmContainerDelete;
 import info.frostfs.sdk.jdo.parameters.container.PrmContainerGet;
 import info.frostfs.sdk.jdo.parameters.container.PrmContainerGetAll;
 import info.frostfs.sdk.jdo.parameters.object.*;
+import info.frostfs.sdk.jdo.parameters.object.patch.PrmObjectPatch;
+import info.frostfs.sdk.jdo.parameters.object.patch.PrmRangeGet;
+import info.frostfs.sdk.jdo.parameters.object.patch.PrmRangeHashGet;
 import info.frostfs.sdk.jdo.parameters.session.PrmSessionCreate;
 import info.frostfs.sdk.jdo.result.ObjectHeaderResult;
 import info.frostfs.sdk.pool.SessionCache;
@@ -33,6 +36,7 @@ import info.frostfs.sdk.services.impl.*;
 import info.frostfs.sdk.services.impl.interceptor.Configuration;
 import info.frostfs.sdk.services.impl.interceptor.MonitoringClientInterceptor;
 import info.frostfs.sdk.services.impl.rwhelper.ObjectWriter;
+import info.frostfs.sdk.services.impl.rwhelper.RangeReader;
 import info.frostfs.sdk.utils.Validator;
 import io.grpc.Channel;
 import io.grpc.ClientInterceptors;
@@ -164,6 +168,21 @@ public class FrostFSClient implements CommonClient {
         return objectClientImpl.searchObjects(args, ctx);
     }
 
+    @Override
+    public RangeReader getRange(PrmRangeGet args, CallContext ctx) {
+        return objectClientImpl.getRange(args, ctx);
+    }
+
+    @Override
+    public byte[][] getRangeHash(PrmRangeHashGet args, CallContext ctx) {
+        return objectClientImpl.getRangeHash(args, ctx);
+    }
+
+    @Override
+    public ObjectId patchObject(PrmObjectPatch args, CallContext ctx) {
+        return objectClientImpl.patchObject(args, ctx);
+    }
+
     @Override
     public byte[] addChain(PrmApeChainAdd args, CallContext ctx) {
         return apeManagerClient.addChain(args, ctx);
@@ -213,6 +232,7 @@ public class FrostFSClient implements CommonClient {
         return accountingClient.getBalance(ctx);
     }
 
+    @Override
     public String dial(CallContext ctx) {
         accountingClient.getBalance(ctx);
         return null;
diff --git a/client/src/main/java/info/frostfs/sdk/jdo/parameters/object/patch/PrmObjectPatch.java b/client/src/main/java/info/frostfs/sdk/jdo/parameters/object/patch/PrmObjectPatch.java
new file mode 100644
index 0000000..9f93c9a
--- /dev/null
+++ b/client/src/main/java/info/frostfs/sdk/jdo/parameters/object/patch/PrmObjectPatch.java
@@ -0,0 +1,42 @@
+package info.frostfs.sdk.jdo.parameters.object.patch;
+
+import info.frostfs.sdk.annotations.NotNull;
+import info.frostfs.sdk.dto.object.ObjectAttribute;
+import info.frostfs.sdk.dto.object.patch.Address;
+import info.frostfs.sdk.dto.object.patch.Range;
+import info.frostfs.sdk.dto.session.SessionToken;
+import info.frostfs.sdk.jdo.parameters.session.SessionContext;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+@Getter
+@Builder
+@AllArgsConstructor
+public class PrmObjectPatch implements SessionContext {
+    @NotNull
+    private Address address;
+
+    @NotNull
+    private Range range;
+
+    @NotNull
+    private InputStream payload;
+
+    private List<ObjectAttribute> newAttributes;
+    private boolean replaceAttributes;
+    private int maxChunkLength;
+    private SessionToken sessionToken;
+    private Map<String, String> xHeaders;
+
+    public PrmObjectPatch(Address address, Range range, InputStream payload, int maxChunkLength) {
+        this.address = address;
+        this.range = range;
+        this.payload = payload;
+        this.maxChunkLength = maxChunkLength;
+    }
+}
diff --git a/client/src/main/java/info/frostfs/sdk/jdo/parameters/object/patch/PrmRangeGet.java b/client/src/main/java/info/frostfs/sdk/jdo/parameters/object/patch/PrmRangeGet.java
new file mode 100644
index 0000000..add15b7
--- /dev/null
+++ b/client/src/main/java/info/frostfs/sdk/jdo/parameters/object/patch/PrmRangeGet.java
@@ -0,0 +1,35 @@
+package info.frostfs.sdk.jdo.parameters.object.patch;
+
+import info.frostfs.sdk.annotations.NotNull;
+import info.frostfs.sdk.dto.container.ContainerId;
+import info.frostfs.sdk.dto.object.ObjectId;
+import info.frostfs.sdk.dto.object.patch.Range;
+import info.frostfs.sdk.dto.session.SessionToken;
+import info.frostfs.sdk.jdo.parameters.session.SessionContext;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+import java.util.Map;
+
+@Getter
+@Builder
+@AllArgsConstructor
+public class PrmRangeGet implements SessionContext {
+    @NotNull
+    private ContainerId containerId;
+    @NotNull
+    private ObjectId objectId;
+    @NotNull
+    private Range range;
+
+    private boolean raw;
+    private SessionToken sessionToken;
+    private Map<String, String> xHeaders;
+
+    public PrmRangeGet(ContainerId containerId, ObjectId objectId, Range range) {
+        this.containerId = containerId;
+        this.objectId = objectId;
+        this.range = range;
+    }
+}
diff --git a/client/src/main/java/info/frostfs/sdk/jdo/parameters/object/patch/PrmRangeHashGet.java b/client/src/main/java/info/frostfs/sdk/jdo/parameters/object/patch/PrmRangeHashGet.java
new file mode 100644
index 0000000..d860133
--- /dev/null
+++ b/client/src/main/java/info/frostfs/sdk/jdo/parameters/object/patch/PrmRangeHashGet.java
@@ -0,0 +1,38 @@
+package info.frostfs.sdk.jdo.parameters.object.patch;
+
+import info.frostfs.sdk.annotations.NotNull;
+import info.frostfs.sdk.dto.container.ContainerId;
+import info.frostfs.sdk.dto.object.ObjectId;
+import info.frostfs.sdk.dto.object.patch.Range;
+import info.frostfs.sdk.dto.session.SessionToken;
+import info.frostfs.sdk.jdo.parameters.session.SessionContext;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+import java.util.List;
+import java.util.Map;
+
+@Getter
+@Builder
+@AllArgsConstructor
+public class PrmRangeHashGet implements SessionContext {
+    @NotNull
+    private ContainerId containerId;
+    @NotNull
+    private ObjectId objectId;
+    @NotNull
+    private List<Range> ranges;
+    @NotNull
+    private byte[] salt;
+
+    private SessionToken sessionToken;
+    private Map<String, String> xHeaders;
+
+    public PrmRangeHashGet(ContainerId containerId, ObjectId objectId, List<Range> ranges, byte[] salt) {
+        this.containerId = containerId;
+        this.objectId = objectId;
+        this.ranges = ranges;
+        this.salt = salt;
+    }
+}
diff --git a/client/src/main/java/info/frostfs/sdk/pool/Pool.java b/client/src/main/java/info/frostfs/sdk/pool/Pool.java
index 74143a2..45b818e 100644
--- a/client/src/main/java/info/frostfs/sdk/pool/Pool.java
+++ b/client/src/main/java/info/frostfs/sdk/pool/Pool.java
@@ -26,12 +26,16 @@ import info.frostfs.sdk.jdo.parameters.container.PrmContainerDelete;
 import info.frostfs.sdk.jdo.parameters.container.PrmContainerGet;
 import info.frostfs.sdk.jdo.parameters.container.PrmContainerGetAll;
 import info.frostfs.sdk.jdo.parameters.object.*;
+import info.frostfs.sdk.jdo.parameters.object.patch.PrmObjectPatch;
+import info.frostfs.sdk.jdo.parameters.object.patch.PrmRangeGet;
+import info.frostfs.sdk.jdo.parameters.object.patch.PrmRangeHashGet;
 import info.frostfs.sdk.jdo.parameters.session.PrmSessionCreate;
 import info.frostfs.sdk.jdo.pool.NodeParameters;
 import info.frostfs.sdk.jdo.pool.PoolInitParameters;
 import info.frostfs.sdk.jdo.result.ObjectHeaderResult;
 import info.frostfs.sdk.services.CommonClient;
 import info.frostfs.sdk.services.impl.rwhelper.ObjectWriter;
+import info.frostfs.sdk.services.impl.rwhelper.RangeReader;
 import info.frostfs.sdk.utils.FrostFSMessages;
 import info.frostfs.sdk.utils.WaitUtil;
 import lombok.Getter;
@@ -198,6 +202,7 @@ public class Pool implements CommonClient {
         return address + key;
     }
 
+    @Override
     public String dial(CallContext ctx) {
         InnerPool[] inner = new InnerPool[rebalanceParams.getNodesParams().length];
         boolean atLeastOneHealthy = false;
@@ -479,6 +484,24 @@ public class Pool implements CommonClient {
         return client.getClient().searchObjects(args, ctx);
     }
 
+    @Override
+    public RangeReader getRange(PrmRangeGet args, CallContext ctx) {
+        ClientWrapper client = connection();
+        return client.getClient().getRange(args, ctx);
+    }
+
+    @Override
+    public byte[][] getRangeHash(PrmRangeHashGet args, CallContext ctx) {
+        ClientWrapper client = connection();
+        return client.getClient().getRangeHash(args, ctx);
+    }
+
+    @Override
+    public ObjectId patchObject(PrmObjectPatch args, CallContext ctx) {
+        ClientWrapper client = connection();
+        return client.getClient().patchObject(args, ctx);
+    }
+
     @Override
     public byte[] addChain(PrmApeChainAdd args, CallContext ctx) {
         ClientWrapper client = connection();
diff --git a/client/src/main/java/info/frostfs/sdk/services/CommonClient.java b/client/src/main/java/info/frostfs/sdk/services/CommonClient.java
index e0482fb..ce0dcba 100644
--- a/client/src/main/java/info/frostfs/sdk/services/CommonClient.java
+++ b/client/src/main/java/info/frostfs/sdk/services/CommonClient.java
@@ -1,5 +1,9 @@
 package info.frostfs.sdk.services;
 
+import info.frostfs.sdk.jdo.parameters.CallContext;
+
 public interface CommonClient extends
         AccountingClient, ApeManagerClient, ContainerClient, NetmapClient, ObjectClient, SessionClient, ToolsClient {
+
+    String dial(CallContext ctx);
 }
diff --git a/client/src/main/java/info/frostfs/sdk/services/ObjectClient.java b/client/src/main/java/info/frostfs/sdk/services/ObjectClient.java
index 86901b5..5e8362c 100644
--- a/client/src/main/java/info/frostfs/sdk/services/ObjectClient.java
+++ b/client/src/main/java/info/frostfs/sdk/services/ObjectClient.java
@@ -4,8 +4,12 @@ import info.frostfs.sdk.dto.object.ObjectFrostFS;
 import info.frostfs.sdk.dto.object.ObjectId;
 import info.frostfs.sdk.jdo.parameters.CallContext;
 import info.frostfs.sdk.jdo.parameters.object.*;
+import info.frostfs.sdk.jdo.parameters.object.patch.PrmObjectPatch;
+import info.frostfs.sdk.jdo.parameters.object.patch.PrmRangeGet;
+import info.frostfs.sdk.jdo.parameters.object.patch.PrmRangeHashGet;
 import info.frostfs.sdk.jdo.result.ObjectHeaderResult;
 import info.frostfs.sdk.services.impl.rwhelper.ObjectWriter;
+import info.frostfs.sdk.services.impl.rwhelper.RangeReader;
 
 public interface ObjectClient {
     ObjectHeaderResult getObjectHead(PrmObjectHeadGet args, CallContext ctx);
@@ -21,4 +25,10 @@ public interface ObjectClient {
     void deleteObject(PrmObjectDelete args, CallContext ctx);
 
     Iterable<ObjectId> searchObjects(PrmObjectSearch args, CallContext ctx);
+
+    RangeReader getRange(PrmRangeGet args, CallContext ctx);
+
+    byte[][] getRangeHash(PrmRangeHashGet args, CallContext ctx);
+
+    ObjectId patchObject(PrmObjectPatch args, CallContext ctx);
 }
diff --git a/client/src/main/java/info/frostfs/sdk/services/impl/ObjectClientImpl.java b/client/src/main/java/info/frostfs/sdk/services/impl/ObjectClientImpl.java
index 203d1e6..266b630 100644
--- a/client/src/main/java/info/frostfs/sdk/services/impl/ObjectClientImpl.java
+++ b/client/src/main/java/info/frostfs/sdk/services/impl/ObjectClientImpl.java
@@ -14,16 +14,18 @@ import info.frostfs.sdk.jdo.ClientEnvironment;
 import info.frostfs.sdk.jdo.PutObjectResult;
 import info.frostfs.sdk.jdo.parameters.CallContext;
 import info.frostfs.sdk.jdo.parameters.object.*;
+import info.frostfs.sdk.jdo.parameters.object.patch.PrmObjectPatch;
+import info.frostfs.sdk.jdo.parameters.object.patch.PrmRangeGet;
+import info.frostfs.sdk.jdo.parameters.object.patch.PrmRangeHashGet;
 import info.frostfs.sdk.jdo.parameters.session.SessionContext;
 import info.frostfs.sdk.jdo.result.ObjectHeaderResult;
 import info.frostfs.sdk.mappers.container.ContainerIdMapper;
 import info.frostfs.sdk.mappers.object.*;
+import info.frostfs.sdk.mappers.object.patch.AddressMapper;
+import info.frostfs.sdk.mappers.object.patch.RangeMapper;
 import info.frostfs.sdk.services.ContextAccessor;
 import info.frostfs.sdk.services.ObjectClient;
-import info.frostfs.sdk.services.impl.rwhelper.ObjectReaderImpl;
-import info.frostfs.sdk.services.impl.rwhelper.ObjectStreamer;
-import info.frostfs.sdk.services.impl.rwhelper.ObjectWriter;
-import info.frostfs.sdk.services.impl.rwhelper.SearchReader;
+import info.frostfs.sdk.services.impl.rwhelper.*;
 import info.frostfs.sdk.tools.RequestConstructor;
 import info.frostfs.sdk.tools.Verifier;
 import org.apache.commons.collections4.CollectionUtils;
@@ -200,6 +202,77 @@ public class ObjectClientImpl extends ContextAccessor implements ObjectClient {
         return new ObjectId(grpcObject.getObjectId().getValue().toByteArray());
     }
 
+    @Override
+    public RangeReader getRange(PrmRangeGet args, CallContext ctx) {
+        validate(args);
+
+        var request = createGetRangeRequest(args, ctx);
+
+        var service = deadLineAfter(objectServiceBlockingClient, ctx.getTimeout(), ctx.getTimeUnit());
+        return new RangeReader(service.getRange(request));
+    }
+
+    @Override
+    public byte[][] getRangeHash(PrmRangeHashGet args, CallContext ctx) {
+        validate(args);
+
+        var request = createGetRangeHashRequest(args, ctx);
+
+        var service = deadLineAfter(objectServiceBlockingClient, ctx.getTimeout(), ctx.getTimeUnit());
+        var response = service.getRangeHash(request);
+
+        Verifier.checkResponse(response);
+
+        return response.getBody().getHashListList().stream().map(ByteString::toByteArray).toArray(byte[][]::new);
+    }
+
+    @Override
+    public ObjectId patchObject(PrmObjectPatch args, CallContext ctx) {
+        validate(args);
+
+        var request = createInitPatchRequest(args);
+        var protoToken = RequestConstructor.createObjectTokenContext(
+                getOrCreateSession(args, ctx),
+                request.getBody().getAddress(),
+                frostfs.session.Types.ObjectSessionContext.Verb.PATCH,
+                getContext().getKey()
+        );
+
+        var currentPos = args.getRange().getOffset();
+        var chunkSize = args.getMaxChunkLength();
+        byte[] chunkBuffer = new byte[chunkSize];
+
+        var service = deadLineAfter(objectServiceClient, ctx.getTimeout(), ctx.getTimeUnit());
+        PatchStreamer writer = new PatchStreamer(service);
+
+        var bytesCount = readNBytes(args.getPayload(), chunkBuffer, chunkSize);
+        while (bytesCount > 0) {
+            var range = Service.Range.newBuilder()
+                    .setOffset(currentPos)
+                    .setLength(bytesCount)
+                    .build();
+
+            Service.PatchRequest.Body.Patch.newBuilder()
+                    .setChunk(ByteString.copyFrom(chunkBuffer, 0, bytesCount))
+                    .setSourceRange(range)
+                    .build();
+
+            currentPos += bytesCount;
+
+            RequestConstructor.addMetaHeader(request, args.getXHeaders(), protoToken);
+            sign(request, getContext().getKey());
+
+            writer.write(request.build());
+            bytesCount = readNBytes(args.getPayload(), chunkBuffer, chunkSize);
+        }
+
+        var response = writer.complete();
+
+        Verifier.checkResponse(response);
+
+        return ObjectIdMapper.toModel(response.getBody().getObjectId());
+    }
+
     private ObjectFrostFS getObject(Service.GetRequest request, CallContext ctx) {
         var reader = getObjectInit(request, ctx);
 
@@ -371,7 +444,6 @@ public class ObjectClientImpl extends ContextAccessor implements ObjectClient {
     }
 
     private Service.GetRequest createGetRequest(PrmObjectGet args, CallContext ctx) {
-
         var address = Types.Address.newBuilder()
                 .setContainerId(ContainerIdMapper.toGrpcMessage(args.getContainerId()))
                 .setObjectId(ObjectIdMapper.toGrpcMessage(args.getObjectId()))
@@ -510,4 +582,70 @@ public class ObjectClientImpl extends ContextAccessor implements ObjectClient {
 
         return request.build();
     }
+
+    private Service.GetRangeRequest createGetRangeRequest(PrmRangeGet args, CallContext ctx) {
+        var address = Types.Address.newBuilder()
+                .setContainerId(ContainerIdMapper.toGrpcMessage(args.getContainerId()))
+                .setObjectId(ObjectIdMapper.toGrpcMessage(args.getObjectId()))
+                .build();
+        var body = Service.GetRangeRequest.Body.newBuilder()
+                .setAddress(address)
+                .setRange(RangeMapper.toGrpcMessage(args.getRange()))
+                .setRaw(args.isRaw())
+                .build();
+        var request = Service.GetRangeRequest.newBuilder()
+                .setBody(body);
+
+        var sessionToken = getOrCreateSession(args, ctx);
+        var protoToken = RequestConstructor.createObjectTokenContext(
+                sessionToken,
+                address,
+                frostfs.session.Types.ObjectSessionContext.Verb.RANGE,
+                getContext().getKey()
+        );
+
+        RequestConstructor.addMetaHeader(request, args.getXHeaders(), protoToken);
+        sign(request, getContext().getKey());
+
+        return request.build();
+    }
+
+    private Service.GetRangeHashRequest createGetRangeHashRequest(PrmRangeHashGet args, CallContext ctx) {
+        var address = Types.Address.newBuilder()
+                .setContainerId(ContainerIdMapper.toGrpcMessage(args.getContainerId()))
+                .setObjectId(ObjectIdMapper.toGrpcMessage(args.getObjectId()))
+                .build();
+        var body = Service.GetRangeHashRequest.Body.newBuilder()
+                .setAddress(address)
+                .setType(Types.ChecksumType.SHA256)
+                .setSalt(ByteString.copyFrom(args.getSalt()))
+                .addAllRanges(RangeMapper.toGrpcMessages(args.getRanges()))
+                .build();
+        var request = Service.GetRangeHashRequest.newBuilder()
+                .setBody(body);
+
+        var sessionToken = getOrCreateSession(args, ctx);
+        var protoToken = RequestConstructor.createObjectTokenContext(
+                sessionToken,
+                address,
+                frostfs.session.Types.ObjectSessionContext.Verb.RANGEHASH,
+                getContext().getKey()
+        );
+
+        RequestConstructor.addMetaHeader(request, args.getXHeaders(), protoToken);
+        sign(request, getContext().getKey());
+
+        return request.build();
+    }
+
+    private Service.PatchRequest.Builder createInitPatchRequest(PrmObjectPatch args) {
+        var address = AddressMapper.toGrpcMessage(args.getAddress());
+        var body = Service.PatchRequest.Body.newBuilder()
+                .setAddress(address)
+                .setReplaceAttributes(args.isReplaceAttributes())
+                .addAllNewAttributes(ObjectAttributeMapper.toGrpcMessages(args.getNewAttributes()))
+                .build();
+        return Service.PatchRequest.newBuilder()
+                .setBody(body);
+    }
 }
diff --git a/client/src/main/java/info/frostfs/sdk/services/impl/rwhelper/ObjectStreamer.java b/client/src/main/java/info/frostfs/sdk/services/impl/rwhelper/ObjectStreamer.java
index 0556fb5..cadfbf1 100644
--- a/client/src/main/java/info/frostfs/sdk/services/impl/rwhelper/ObjectStreamer.java
+++ b/client/src/main/java/info/frostfs/sdk/services/impl/rwhelper/ObjectStreamer.java
@@ -7,11 +7,11 @@ import info.frostfs.sdk.utils.WaitUtil;
 import io.grpc.stub.StreamObserver;
 import lombok.Getter;
 
+import static info.frostfs.sdk.constants.AppConst.DEFAULT_POLL_INTERVAL;
 import static info.frostfs.sdk.constants.ErrorConst.PROTO_MESSAGE_IS_EMPTY_TEMPLATE;
 import static java.util.Objects.isNull;
 
 public class ObjectStreamer {
-    private static final long POLL_INTERVAL = 10;
     private final StreamObserver<Service.PutRequest> requestObserver;
     private final PutResponseCallback responseObserver;
 
@@ -35,7 +35,7 @@ public class ObjectStreamer {
         requestObserver.onCompleted();
 
         while (isNull(responseObserver.getResponse())) {
-            WaitUtil.sleep(POLL_INTERVAL);
+            WaitUtil.sleep(DEFAULT_POLL_INTERVAL);
         }
 
         return responseObserver.getResponse();
diff --git a/client/src/main/java/info/frostfs/sdk/services/impl/rwhelper/PatchStreamer.java b/client/src/main/java/info/frostfs/sdk/services/impl/rwhelper/PatchStreamer.java
new file mode 100644
index 0000000..4cdee35
--- /dev/null
+++ b/client/src/main/java/info/frostfs/sdk/services/impl/rwhelper/PatchStreamer.java
@@ -0,0 +1,62 @@
+package info.frostfs.sdk.services.impl.rwhelper;
+
+import frostfs.object.ObjectServiceGrpc;
+import frostfs.object.Service;
+import info.frostfs.sdk.exceptions.ProcessFrostFSException;
+import info.frostfs.sdk.utils.WaitUtil;
+import io.grpc.stub.StreamObserver;
+import lombok.Getter;
+
+import static info.frostfs.sdk.constants.AppConst.DEFAULT_POLL_INTERVAL;
+import static info.frostfs.sdk.constants.ErrorConst.PROTO_MESSAGE_IS_EMPTY_TEMPLATE;
+import static java.util.Objects.isNull;
+
+public class PatchStreamer {
+    private final StreamObserver<Service.PatchRequest> requestObserver;
+    private final PatchResponseCallback responseObserver;
+
+    public PatchStreamer(ObjectServiceGrpc.ObjectServiceStub objectServiceStub) {
+        PatchResponseCallback responseObserver = new PatchResponseCallback();
+
+        this.responseObserver = responseObserver;
+        this.requestObserver = objectServiceStub.patch(responseObserver);
+    }
+
+    public void write(Service.PatchRequest request) {
+        if (isNull(request)) {
+            throw new ProcessFrostFSException(
+                    String.format(PROTO_MESSAGE_IS_EMPTY_TEMPLATE, Service.PutRequest.class.getName())
+            );
+        }
+        requestObserver.onNext(request);
+    }
+
+    public Service.PatchResponse complete() {
+        requestObserver.onCompleted();
+
+        while (isNull(responseObserver.getResponse())) {
+            WaitUtil.sleep(DEFAULT_POLL_INTERVAL);
+        }
+
+        return responseObserver.getResponse();
+    }
+
+    @Getter
+    private static class PatchResponseCallback implements StreamObserver<Service.PatchResponse> {
+        private Service.PatchResponse response;
+
+        @Override
+        public void onNext(Service.PatchResponse patchResponse) {
+            this.response = patchResponse;
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            throw new ProcessFrostFSException(throwable);
+        }
+
+        @Override
+        public void onCompleted() {
+        }
+    }
+}
diff --git a/client/src/main/java/info/frostfs/sdk/services/impl/rwhelper/RangeReader.java b/client/src/main/java/info/frostfs/sdk/services/impl/rwhelper/RangeReader.java
new file mode 100644
index 0000000..75c4c64
--- /dev/null
+++ b/client/src/main/java/info/frostfs/sdk/services/impl/rwhelper/RangeReader.java
@@ -0,0 +1,25 @@
+package info.frostfs.sdk.services.impl.rwhelper;
+
+import frostfs.object.Service;
+import info.frostfs.sdk.tools.Verifier;
+
+import java.util.Iterator;
+
+public class RangeReader {
+    public Iterator<Service.GetRangeResponse> call;
+
+    public RangeReader(Iterator<Service.GetRangeResponse> call) {
+        this.call = call;
+    }
+
+    public byte[] readChunk() {
+        if (!call.hasNext()) {
+            return null;
+        }
+
+        var response = call.next();
+        Verifier.checkResponse(response);
+
+        return response.getBody().getChunk().toByteArray();
+    }
+}
diff --git a/cryptography/pom.xml b/cryptography/pom.xml
index ac56a86..e9e2c6b 100644
--- a/cryptography/pom.xml
+++ b/cryptography/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <groupId>info.frostfs.sdk</groupId>
         <artifactId>frostfs-sdk-java</artifactId>
-        <version>0.3.0</version>
+        <version>${revision}</version>
     </parent>
 
     <artifactId>cryptography</artifactId>
@@ -21,7 +21,7 @@
         <dependency>
             <groupId>info.frostfs.sdk</groupId>
             <artifactId>exceptions</artifactId>
-            <version>0.3.0</version>
+            <version>${revision}</version>
         </dependency>
         <dependency>
             <groupId>com.google.protobuf</groupId>
diff --git a/exceptions/pom.xml b/exceptions/pom.xml
index 4cb8bcc..c3b4546 100644
--- a/exceptions/pom.xml
+++ b/exceptions/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <groupId>info.frostfs.sdk</groupId>
         <artifactId>frostfs-sdk-java</artifactId>
-        <version>0.3.0</version>
+        <version>${revision}</version>
     </parent>
 
     <artifactId>exceptions</artifactId>
diff --git a/models/pom.xml b/models/pom.xml
index 32cade4..e913463 100644
--- a/models/pom.xml
+++ b/models/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <groupId>info.frostfs.sdk</groupId>
         <artifactId>frostfs-sdk-java</artifactId>
-        <version>0.3.0</version>
+        <version>${revision}</version>
     </parent>
 
     <artifactId>models</artifactId>
@@ -21,17 +21,17 @@
         <dependency>
             <groupId>info.frostfs.sdk</groupId>
             <artifactId>cryptography</artifactId>
-            <version>0.3.0</version>
+            <version>${revision}</version>
         </dependency>
         <dependency>
             <groupId>info.frostfs.sdk</groupId>
             <artifactId>protos</artifactId>
-            <version>0.3.0</version>
+            <version>${revision}</version>
         </dependency>
         <dependency>
             <groupId>info.frostfs.sdk</groupId>
             <artifactId>exceptions</artifactId>
-            <version>0.3.0</version>
+            <version>${revision}</version>
         </dependency>
     </dependencies>
 
diff --git a/models/src/main/java/info/frostfs/sdk/constants/AppConst.java b/models/src/main/java/info/frostfs/sdk/constants/AppConst.java
index 35d64c6..feb1ca5 100644
--- a/models/src/main/java/info/frostfs/sdk/constants/AppConst.java
+++ b/models/src/main/java/info/frostfs/sdk/constants/AppConst.java
@@ -13,6 +13,7 @@ public class AppConst {
     public static final int SHA256_HASH_LENGTH = 32;
     public static final int UUID_BYTE_ARRAY_LENGTH = 16;
     public static final int DEFAULT_GRPC_TIMEOUT = 5;
+    public static final long DEFAULT_POLL_INTERVAL = 10;
 
     private AppConst() {
     }
diff --git a/models/src/main/java/info/frostfs/sdk/dto/object/patch/Address.java b/models/src/main/java/info/frostfs/sdk/dto/object/patch/Address.java
new file mode 100644
index 0000000..ae30880
--- /dev/null
+++ b/models/src/main/java/info/frostfs/sdk/dto/object/patch/Address.java
@@ -0,0 +1,13 @@
+package info.frostfs.sdk.dto.object.patch;
+
+import info.frostfs.sdk.dto.container.ContainerId;
+import info.frostfs.sdk.dto.object.ObjectId;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public class Address {
+    private final ObjectId objectId;
+    private final ContainerId containerId;
+}
diff --git a/models/src/main/java/info/frostfs/sdk/dto/object/patch/Range.java b/models/src/main/java/info/frostfs/sdk/dto/object/patch/Range.java
new file mode 100644
index 0000000..67a56aa
--- /dev/null
+++ b/models/src/main/java/info/frostfs/sdk/dto/object/patch/Range.java
@@ -0,0 +1,11 @@
+package info.frostfs.sdk.dto.object.patch;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public class Range {
+    private long offset;
+    private long length;
+}
diff --git a/models/src/main/java/info/frostfs/sdk/mappers/object/ObjectAttributeMapper.java b/models/src/main/java/info/frostfs/sdk/mappers/object/ObjectAttributeMapper.java
index 687aa86..f29c284 100644
--- a/models/src/main/java/info/frostfs/sdk/mappers/object/ObjectAttributeMapper.java
+++ b/models/src/main/java/info/frostfs/sdk/mappers/object/ObjectAttributeMapper.java
@@ -2,6 +2,10 @@ package info.frostfs.sdk.mappers.object;
 
 import frostfs.object.Types;
 import info.frostfs.sdk.dto.object.ObjectAttribute;
+import org.apache.commons.collections4.CollectionUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
 
 import static java.util.Objects.isNull;
 
@@ -10,6 +14,14 @@ public class ObjectAttributeMapper {
     private ObjectAttributeMapper() {
     }
 
+    public static List<Types.Header.Attribute> toGrpcMessages(List<ObjectAttribute> attributes) {
+        if (CollectionUtils.isEmpty(attributes)) {
+            return null;
+        }
+
+        return attributes.stream().map(ObjectAttributeMapper::toGrpcMessage).collect(Collectors.toList());
+    }
+
     public static Types.Header.Attribute toGrpcMessage(ObjectAttribute attribute) {
         if (isNull(attribute)) {
             return null;
diff --git a/models/src/main/java/info/frostfs/sdk/mappers/object/patch/AddressMapper.java b/models/src/main/java/info/frostfs/sdk/mappers/object/patch/AddressMapper.java
new file mode 100644
index 0000000..1510b7a
--- /dev/null
+++ b/models/src/main/java/info/frostfs/sdk/mappers/object/patch/AddressMapper.java
@@ -0,0 +1,25 @@
+package info.frostfs.sdk.mappers.object.patch;
+
+import frostfs.refs.Types;
+import info.frostfs.sdk.dto.object.patch.Address;
+import info.frostfs.sdk.mappers.container.ContainerIdMapper;
+import info.frostfs.sdk.mappers.object.ObjectIdMapper;
+
+import static java.util.Objects.isNull;
+
+public class AddressMapper {
+
+    private AddressMapper() {
+    }
+
+    public static Types.Address toGrpcMessage(Address address) {
+        if (isNull(address)) {
+            return null;
+        }
+
+        return Types.Address.newBuilder()
+                .setContainerId(ContainerIdMapper.toGrpcMessage(address.getContainerId()))
+                .setObjectId(ObjectIdMapper.toGrpcMessage(address.getObjectId()))
+                .build();
+    }
+}
diff --git a/models/src/main/java/info/frostfs/sdk/mappers/object/patch/RangeMapper.java b/models/src/main/java/info/frostfs/sdk/mappers/object/patch/RangeMapper.java
new file mode 100644
index 0000000..f428545
--- /dev/null
+++ b/models/src/main/java/info/frostfs/sdk/mappers/object/patch/RangeMapper.java
@@ -0,0 +1,35 @@
+package info.frostfs.sdk.mappers.object.patch;
+
+import frostfs.object.Service;
+import info.frostfs.sdk.dto.object.patch.Range;
+import org.apache.commons.collections4.CollectionUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static java.util.Objects.isNull;
+
+public class RangeMapper {
+
+    private RangeMapper() {
+    }
+
+    public static List<Service.Range> toGrpcMessages(List<Range> ranges) {
+        if (CollectionUtils.isEmpty(ranges)) {
+            return null;
+        }
+
+        return ranges.stream().map(RangeMapper::toGrpcMessage).collect(Collectors.toList());
+    }
+
+    public static Service.Range toGrpcMessage(Range range) {
+        if (isNull(range)) {
+            return null;
+        }
+
+        return Service.Range.newBuilder()
+                .setOffset(range.getOffset())
+                .setLength(range.getLength())
+                .build();
+    }
+}
diff --git a/models/src/test/java/info/frostfs/sdk/mappers/object/ObjectAttributeMapperTest.java b/models/src/test/java/info/frostfs/sdk/mappers/object/ObjectAttributeMapperTest.java
index 4ae2902..160af7a 100644
--- a/models/src/test/java/info/frostfs/sdk/mappers/object/ObjectAttributeMapperTest.java
+++ b/models/src/test/java/info/frostfs/sdk/mappers/object/ObjectAttributeMapperTest.java
@@ -4,10 +4,39 @@ import frostfs.object.Types;
 import info.frostfs.sdk.dto.object.ObjectAttribute;
 import org.junit.jupiter.api.Test;
 
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.*;
 
 public class ObjectAttributeMapperTest {
 
+    @Test
+    void toGrpcMessages_success() {
+        //Given
+        var objectAttribute1 = new ObjectAttribute("key1", "value1");
+        var objectAttribute2 = new ObjectAttribute("key2", "value2");
+
+        //When
+        var result = ObjectAttributeMapper.toGrpcMessages(Arrays.asList(objectAttribute1, objectAttribute2));
+
+        //Then
+        assertNotNull(result);
+        assertThat(result).isNotNull().hasSize(2);
+        assertEquals(objectAttribute1.getKey(), result.get(0).getKey());
+        assertEquals(objectAttribute1.getValue(), result.get(0).getValue());
+        assertEquals(objectAttribute2.getKey(), result.get(1).getKey());
+        assertEquals(objectAttribute2.getValue(), result.get(1).getValue());
+    }
+
+    @Test
+    void toGrpcMessages_null() {
+        //When + Then
+        assertNull(ObjectAttributeMapper.toGrpcMessages(null));
+        assertNull(ObjectAttributeMapper.toGrpcMessages(Collections.emptyList()));
+    }
+
     @Test
     void toGrpcMessage_success() {
         //Given
diff --git a/models/src/test/java/info/frostfs/sdk/mappers/object/patch/AddressMapperTest.java b/models/src/test/java/info/frostfs/sdk/mappers/object/patch/AddressMapperTest.java
new file mode 100644
index 0000000..b53ff87
--- /dev/null
+++ b/models/src/test/java/info/frostfs/sdk/mappers/object/patch/AddressMapperTest.java
@@ -0,0 +1,39 @@
+package info.frostfs.sdk.mappers.object.patch;
+
+import info.frostfs.sdk.dto.container.ContainerId;
+import info.frostfs.sdk.dto.object.ObjectId;
+import info.frostfs.sdk.dto.object.patch.Address;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class AddressMapperTest {
+
+    @Test
+    void toGrpcMessage_success() {
+        //Given
+        var objectId = new ObjectId("85orCLKSu3X1jGiTFmwmTUsBU88RBARNwuRwrEy5pyww");
+        var containerId = new ContainerId("EQGx2QeYHJb53uRwYGzcQaW191sZpdNrjutk6veUSV2R");
+        var address = new Address(objectId, containerId);
+
+        //When
+        var result = AddressMapper.toGrpcMessage(address);
+
+        //Then
+        assertNotNull(result);
+        assertEquals(
+                address.getContainerId().getValue(),
+                new ContainerId(result.getContainerId().getValue().toByteArray()).getValue()
+        );
+        assertEquals(
+                address.getObjectId().getValue(),
+                new ObjectId(result.getObjectId().getValue().toByteArray()).getValue()
+        );
+    }
+
+    @Test
+    void toGrpcMessage_null() {
+        //When + Then
+        assertNull(AddressMapper.toGrpcMessage(null));
+    }
+}
diff --git a/models/src/test/java/info/frostfs/sdk/mappers/object/patch/RangeMapperTest.java b/models/src/test/java/info/frostfs/sdk/mappers/object/patch/RangeMapperTest.java
new file mode 100644
index 0000000..771a9ac
--- /dev/null
+++ b/models/src/test/java/info/frostfs/sdk/mappers/object/patch/RangeMapperTest.java
@@ -0,0 +1,58 @@
+package info.frostfs.sdk.mappers.object.patch;
+
+import info.frostfs.sdk.dto.object.patch.Range;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class RangeMapperTest {
+
+    @Test
+    void toGrpcMessages_success() {
+        //Given
+        var range1 = new Range(1, 10);
+        var range2 = new Range(2, 20);
+
+        //When
+        var result = RangeMapper.toGrpcMessages(Arrays.asList(range1, range2));
+
+        //Then
+        assertNotNull(result);
+        assertThat(result).isNotNull().hasSize(2);
+        assertEquals(range1.getOffset(), result.get(0).getOffset());
+        assertEquals(range1.getLength(), result.get(0).getLength());
+        assertEquals(range2.getOffset(), result.get(1).getOffset());
+        assertEquals(range2.getLength(), result.get(1).getLength());
+    }
+
+    @Test
+    void toGrpcMessages_null() {
+        //When + Then
+        assertNull(RangeMapper.toGrpcMessages(null));
+        assertNull(RangeMapper.toGrpcMessages(Collections.emptyList()));
+    }
+
+    @Test
+    void toGrpcMessage_success() {
+        //Given
+        var range = new Range(1, 10);
+
+        //When
+        var result = RangeMapper.toGrpcMessage(range);
+
+        //Then
+        assertNotNull(result);
+        assertEquals(range.getOffset(), result.getOffset());
+        assertEquals(range.getLength(), result.getLength());
+    }
+
+    @Test
+    void toGrpcMessage_null() {
+        //When + Then
+        assertNull(RangeMapper.toGrpcMessage(null));
+    }
+}
diff --git a/pom.xml b/pom.xml
index 72eb9eb..d390c12 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 
     <groupId>info.frostfs.sdk</groupId>
     <artifactId>frostfs-sdk-java</artifactId>
-    <version>0.3.0</version>
+    <version>${revision}</version>
     <packaging>pom</packaging>
     <modules>
         <module>client</module>
@@ -17,6 +17,8 @@
     </modules>
 
     <properties>
+        <revision>0.4.0</revision>
+
         <maven.compiler.source>11</maven.compiler.source>
         <maven.compiler.target>11</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
diff --git a/protos/pom.xml b/protos/pom.xml
index d3a2b3b..f5ea90a 100644
--- a/protos/pom.xml
+++ b/protos/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <groupId>info.frostfs.sdk</groupId>
         <artifactId>frostfs-sdk-java</artifactId>
-        <version>0.3.0</version>
+        <version>${revision}</version>
     </parent>
 
     <artifactId>protos</artifactId>